pax_global_header00006660000000000000000000000064147573035640014530gustar00rootroot0000000000000052 comment=5cb70ffdb6ef6bb5e614a916812526a8104e377a pcm-202502/000077500000000000000000000000001475730356400124035ustar00rootroot00000000000000pcm-202502/.bdsignore.all000066400000000000000000000006251475730356400151320ustar00rootroot00000000000000.bdsignore.all Makefile Doxyfile LICENSE README ChangeLog TODO Debug Release Release64 Backup Intel_SSA .vs My Inspector XE Results - pcm .gitignore .gitattributes .github .*\.txt .*\.pdf .*\.docx .*\.rtf .*\.vcproj .*\.xcodeproj .*\.sh .*\.htm .*\.bat .*\.strings .*\.vcxproj .*\.sln .*\.sdf .*\.vcxproj.user .*\.log .*\.vcxproj.filters .*\.md .codedocs .cproject .project .dockerignore makefile sources pcm-202502/.codedocs000066400000000000000000000115261475730356400141740ustar00rootroot00000000000000# CodeDocs.xyz Configuration File # # Rename this example to '.codedocs' and put it in the root directory of your # repository. This file is optional, documentation will still be generated # without it using sensible defaults. #--------------------------------------------------------------------------- # CodeDocs Configuration #--------------------------------------------------------------------------- # Include the Doxygen configuration from another file. # The file must be a relative path with respect to the root of the repository. # If any of the options in this doxyfile include a path (ie, INPUT), these # paths will be considered relative to the root of the repository, not the # location of the DOXYFILE. DOXYFILE = Doxyfile INPUT += README.md USE_MDFILE_AS_MAINPAGE = README.md # Specify external repository to link documentation with. # This is similar to Doxygen's TAGFILES option, but will automatically link to # tags of other repositories already using CodeDocs. List each repository to # link with by giving its location in the form of owner/repository. # For example: # TAGLINKS = doxygen/doxygen CodeDocs/osg # Note: these repositories must already be built on CodeDocs. # TAGLINKS = #--------------------------------------------------------------------------- # Doxygen Configuration #--------------------------------------------------------------------------- # Doxygen configuration may also be placed in this file. # Currently, the following Doxygen configuration options are available. Refer # to http://doxygen.org/manual/config.html for detailed explanation of the # options. To request support for more options, contact support@codedocs.xyz. # # ABBREVIATE_BRIEF = # ALIASES = # ALLEXTERNALS = # ALLOW_UNICODE_NAMES = # ALPHABETICAL_INDEX = # ALWAYS_DETAILED_SEC = # AUTOLINK_SUPPORT = # BRIEF_MEMBER_DESC = # BUILTIN_STL_SUPPORT = # CALLER_GRAPH = # CALL_GRAPH = # CASE_SENSE_NAMES = # CITE_BIB_FILES = # CLASS_DIAGRAMS = # CLASS_GRAPH = # COLLABORATION_GRAPH = # COLS_IN_ALPHA_INDEX = # CPP_CLI_SUPPORT = # DIAFILE_DIRS = # DIRECTORY_GRAPH = # DISABLE_INDEX = # DISTRIBUTE_GROUP_DOC = # DOTFILE_DIRS = # DOT_FONTNAME = # DOT_FONTSIZE = # DOT_GRAPH_MAX_NODES = # DOT_IMAGE_FORMAT = # DOT_TRANSPARENT = # DOXYFILE_ENCODING = # ENABLED_SECTIONS = # ENABLE_PREPROCESSING = # ENUM_VALUES_PER_LINE = # EXAMPLE_PATH = # EXAMPLE_PATTERNS = # EXAMPLE_RECURSIVE = # EXCLUDE = # EXCLUDE_PATTERNS = # EXCLUDE_SYMBOLS = # EXPAND_AS_DEFINED = # EXPAND_ONLY_PREDEF = # EXTENSION_MAPPING = # EXTERNAL_GROUPS = # EXTERNAL_PAGES = # EXTRACT_ALL = # EXTRACT_ANON_NSPACES = # EXTRACT_LOCAL_CLASSES = # EXTRACT_LOCAL_METHODS = # EXTRACT_PACKAGE = # EXTRACT_PRIVATE = # EXTRACT_STATIC = # EXT_LINKS_IN_WINDOW = # FILE_PATTERNS = # FORCE_LOCAL_INCLUDES = # FORMULA_FONTSIZE = # FORMULA_TRANSPARENT = # FULL_PATH_NAMES = # GENERATE_BUGLIST = # GENERATE_DEPRECATEDLIST = # GENERATE_LEGEND = # GENERATE_TESTLIST = # GENERATE_TODOLIST = # GENERATE_TREEVIEW = # GRAPHICAL_HIERARCHY = # GROUP_GRAPHS = # GROUP_NESTED_COMPOUNDS = # HIDE_COMPOUND_REFERENCE= = # HIDE_FRIEND_COMPOUNDS = # HIDE_IN_BODY_DOCS = # HIDE_SCOPE_NAMES = # HIDE_UNDOC_CLASSES = # HIDE_UNDOC_MEMBERS = # HIDE_UNDOC_RELATIONS = # HTML_COLORSTYLE_GAMMA = # HTML_COLORSTYLE_HUE = # HTML_COLORSTYLE_SAT = # HTML_DYNAMIC_SECTIONS = # HTML_EXTRA_FILES = # HTML_EXTRA_STYLESHEET = # HTML_FOOTER = # HTML_HEADER = # HTML_INDEX_NUM_ENTRIES = # HTML_STYLESHEET = # HTML_TIMESTAMP = # IDL_PROPERTY_SUPPORT = # IGNORE_PREFIX = # IMAGE_PATH = # INCLUDED_BY_GRAPH = # INCLUDE_FILE_PATTERNS = # INCLUDE_GRAPH = # INCLUDE_PATH = # INHERIT_DOCS = # INLINE_GROUPED_CLASSES = # INLINE_INFO = # INLINE_INHERITED_MEMB = # INLINE_SIMPLE_STRUCTS = # INLINE_SOURCES = # INPUT = # INPUT_ENCODING = # INTERACTIVE_SVG = # INTERNAL_DOCS = # JAVADOC_AUTOBRIEF = # LAYOUT_FILE = # MACRO_EXPANSION = # MARKDOWN_SUPPORT = # MAX_DOT_GRAPH_DEPTH = # MSCFILE_DIRS = # MULTILINE_CPP_IS_BRIEF = # OPTIMIZE_FOR_FORTRAN = # OPTIMIZE_OUTPUT_FOR_C = # OPTIMIZE_OUTPUT_JAVA = # OPTIMIZE_OUTPUT_VHDL = # OUTPUT_LANGUAGE = # PLANTUML_JAR_PATH = # PREDEFINED = # PROJECT_BRIEF = # PROJECT_LOGO = # PROJECT_NAME = # PROJECT_NUMBER = # QT_AUTOBRIEF = # RECURSIVE = # REFERENCED_BY_RELATION = # REFERENCES_LINK_SOURCE = # REFERENCES_RELATION = # REPEAT_BRIEF = # SEARCHENGINE = # SEARCH_INCLUDES = # SEPARATE_MEMBER_PAGES = # SHORT_NAMES = # SHOW_FILES = # SHOW_GROUPED_MEMB_INC = # SHOW_INCLUDE_FILES = # SHOW_NAMESPACES = # SHOW_USED_FILES = # SIP_SUPPORT = # SKIP_FUNCTION_MACROS = # SORT_BRIEF_DOCS = # SORT_BY_SCOPE_NAME = # SORT_GROUP_NAMES = # SORT_MEMBERS_CTORS_1ST = # SORT_MEMBER_DOCS = # SOURCE_BROWSER = # SOURCE_TOOLTIPS = # STRICT_PROTO_MATCHING = # STRIP_CODE_COMMENTS = # STRIP_FROM_INC_PATH = # STRIP_FROM_PATH = # SUBGROUPING = # TAB_SIZE = # TEMPLATE_RELATIONS = # TREEVIEW_WIDTH = # TYPEDEF_HIDES_STRUCT = # UML_LIMIT_NUM_FIELDS = # UML_LOOK = # USE_MDFILE_AS_MAINPAGE = # VERBATIM_HEADERS = # pcm-202502/.dockerignore000066400000000000000000000000071475730356400150540ustar00rootroot00000000000000/build pcm-202502/.gitattributes000066400000000000000000000001711475730356400152750ustar00rootroot00000000000000src/version.h export-subst .cirrus.yml export-ignore .gitlab-ci.yml export-ignore .travis.yml export-ignore * text=true pcm-202502/.github/000077500000000000000000000000001475730356400137435ustar00rootroot00000000000000pcm-202502/.github/dependabot.yml000066400000000000000000000003031475730356400165670ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: daily - package-ecosystem: docker directory: / schedule: interval: daily pcm-202502/.github/workflows/000077500000000000000000000000001475730356400160005ustar00rootroot00000000000000pcm-202502/.github/workflows/ci-clang-scan.yml000066400000000000000000000014571475730356400211310ustar00rootroot00000000000000name: clang-scan on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-clang-scan if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build scan-build cmake -B ${{ github.workspace }}/build - name: Scan build run: | cd ${{ github.workspace }}/build scan-build --exclude src/simdjson --status-bugs make -j pcm-202502/.github/workflows/ci-cmake-options.yml000066400000000000000000000077521475730356400217000ustar00rootroot00000000000000name: test cmake options on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-gcc10 if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: CMake default install run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake Release install run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=Release cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake Debug install run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=Debug cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake RelWithDebInfo install run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake Custom build install run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_CXX_FLAGS_CUSTOM:STRING="-O2 -g" -DCMAKE_BUILD_TYPE=CUSTOM cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake Include systemd unit run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DLINUX_SYSTEMD=TRUE -DLINUX_SYSTEMD_UNITDIR=${{ github.workspace }}/build/systemd cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake User-flags build install run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_CXX_FLAGS="-O2 -g" cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake env var option build install run: | cmake --version rm -rf ${{ github.workspace }}/build export CXXFLAGS="-grecord-gcc-switches" export CFLAGS="-fstack-protector-strong" export LDFLAGS="-Wl,-z,now" cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_CXX_FLAGS_CUSTOM:STRING="-O2 -g" -DCMAKE_BUILD_TYPE=CUSTOM cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: CMake env var option build install (no custom flags) run: | cmake --version rm -rf ${{ github.workspace }}/build export CXXFLAGS="-grecord-gcc-switches" export CFLAGS="-fstack-protector-strong" export LDFLAGS="-Wl,-z,now" cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=CUSTOM cd ${{ github.workspace }}/build export VERBOSE=1 make install -j$(nproc) - name: Diagnostic run: date pcm-202502/.github/workflows/ci-cov-linux-report.yml000066400000000000000000000017611475730356400223560ustar00rootroot00000000000000name: coverity-linux-and-python-report on: [workflow_dispatch, workflow_call] permissions: contents: read jobs: build: runs-on: ci-kw-linux if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: coverity-python run: | ci-cov-python.sh - name: coverity-linux run: | mkdir build cd build cmake .. ci-cov-linux.sh - name: coverity-linux-and-python-report run: | ci-cov-linux-report.sh PCM.linux.and.python - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: coverity-linux-and-python-report-${{ github.sha }} path: "*-Report.pdf" pcm-202502/.github/workflows/ci-cov-linux.yml000066400000000000000000000011621475730356400210400ustar00rootroot00000000000000name: coverity-linux on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-kw-linux if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: coverity-linux run: | mkdir build cd build cmake .. ci-cov-linux.sh pcm-202502/.github/workflows/ci-cov-python.yml000066400000000000000000000010771475730356400212270ustar00rootroot00000000000000name: coverity-python on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-kw-linux if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: coverity-python run: | ci-cov-python.sh pcm-202502/.github/workflows/ci-cov-report.yml000066400000000000000000000005031475730356400212120ustar00rootroot00000000000000name: coverity-report on: workflow_dispatch permissions: contents: read jobs: linux-report: uses: intel-innersource/applications.analyzers.pcm/.github/workflows/ci-cov-linux-report.yml@main windows-report: uses: intel-innersource/applications.analyzers.pcm/.github/workflows/ci-cov-windows-report.yml@main pcm-202502/.github/workflows/ci-cov-windows-report.yml000066400000000000000000000024671475730356400227150ustar00rootroot00000000000000name: coverity-windows on: [workflow_dispatch, workflow_call] permissions: contents: read jobs: build: runs-on: ci-kw-windows if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 - name: coverity-windows run: | mkdir build cd build cmake .. c:\pcm\ci-cov-windows.ps1 - name: coverity-windows-cs run: | mkdir build-cs cd build-cs cmake .. c:\pcm\ci-cov-windows-cs.ps1 - name: coverity-windows-msr run: | chdir ${{github.workspace}}\src\WinMSRDriver c:\pcm\ci-cov-windows-msr.ps1 - name: coverity-windows-report run: | c:\pcm\ci-cov-windows-report.ps1 PCM.windows-all - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: coverity-windows-all-report-${{ github.sha }} path: "*-Report.pdf" pcm-202502/.github/workflows/ci-cov-windows.yml000066400000000000000000000020271475730356400213740ustar00rootroot00000000000000name: coverity-windows on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-kw-windows if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 - name: coverity-windows run: | mkdir build cd build cmake .. c:\pcm\ci-cov-windows.ps1 - name: coverity-windows-cs run: | mkdir build-cs cd build-cs cmake .. c:\pcm\ci-cov-windows-cs.ps1 - name: coverity-windows-msr run: | chdir ${{github.workspace}}\src\WinMSRDriver c:\pcm\ci-cov-windows-msr.ps1 pcm-202502/.github/workflows/ci-cpack.yml000066400000000000000000000033041475730356400201750ustar00rootroot00000000000000name: CPack check on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: job-build1: runs-on: ci-gcc9 if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build - name: Build and Install run: | g++ --version cd ${{ github.workspace }}/build make -j$(nproc) - name: CPack run: | cd ${{ github.workspace }}/build cpack job-build2: runs-on: ci-test if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build - name: Build and Install run: | g++ --version cd ${{ github.workspace }}/build make -j$(nproc) - name: CPack run: | cd ${{ github.workspace }}/build cpack pcm-202502/.github/workflows/ci-cppcheck.yml000066400000000000000000000011741475730356400206770ustar00rootroot00000000000000name: cppcheck on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-cppcheck if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: cppcheck_script run: | sh ${{ github.workspace }}/scripts/cppcheck.sh . `getconf _NPROCESSORS_ONLN` pcm-202502/.github/workflows/ci-fuzz-micro.yml000066400000000000000000000020001475730356400212110ustar00rootroot00000000000000name: fuzz-micro-job on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: fuzz: runs-on: ci-test if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Build and test run: | cmake --version set -o pipefail mkdir build cd build bash ${{ github.workspace }}/tests/fuzz.sh 5 2>&1 | tee fuzz-log.txt cd .. - name: Show report run: | cat build/report.txt echo "Fuzzing completed" - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: fuzz-log-${{ github.sha }} path: "build/fuzz-log.txt" pcm-202502/.github/workflows/ci-fuzz-short.yml000066400000000000000000000017471475730356400212600ustar00rootroot00000000000000name: fuzz-short-job on: # manual triggering workflow_dispatch: permissions: contents: read jobs: fuzz: runs-on: ci-test if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Build and test run: | cmake --version set -o pipefail mkdir build cd build bash ${{ github.workspace }}/tests/fuzz.sh 10 2>&1 | tee fuzz-log.txt cd .. - name: Show report run: | cat build/report.txt echo "Fuzzing completed" - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: fuzz-log-${{ github.sha }} path: "build/fuzz-log.txt" pcm-202502/.github/workflows/ci-fuzz.yml000066400000000000000000000020601475730356400201100ustar00rootroot00000000000000name: fuzz-job on: schedule: # every monday at midnight - cron: '0 0 * * 1' # also allow manual triggering workflow_dispatch: permissions: contents: read jobs: fuzz: runs-on: ci-test if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Build and test run: | cmake --version set -o pipefail mkdir build cd build bash ${{ github.workspace }}/tests/fuzz.sh 300 2>&1 | tee fuzz-log.txt cd .. - name: Show report run: | cat build/report.txt echo "Fuzzing completed" - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: fuzz-log-${{ github.sha }} path: "build/fuzz-log.txt" pcm-202502/.github/workflows/ci-gcc10.yml000066400000000000000000000015711475730356400200150ustar00rootroot00000000000000name: g++ 10 build on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-gcc10 if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build - name: Build and Install run: | g++ --version cd ${{ github.workspace }}/build make install -j$(nproc) - name: Diagnostic run: datepcm-202502/.github/workflows/ci-gcc7.yml000066400000000000000000000015461475730356400177450ustar00rootroot00000000000000name: g++ 7 build on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-gcc9 if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build CXX=g++-7 CC=gcc-7 cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build - name: Build and Install run: | g++-7 --version cd ${{ github.workspace }}/build make install -j$(nproc) pcm-202502/.github/workflows/ci-gcc9.yml000066400000000000000000000015211475730356400177400ustar00rootroot00000000000000name: g++ 9 build on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-gcc9 if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/build - name: Build and Install run: | g++ --version cd ${{ github.workspace }}/build make install -j$(nproc) pcm-202502/.github/workflows/ci-test.yml000066400000000000000000000055411475730356400201000ustar00rootroot00000000000000name: tests on: push: branches: [ '**' ] pull_request: branches: [ '**' ] permissions: contents: read jobs: build: runs-on: ci-test if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | cmake --version rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DPCM_NO_ASAN=OFF - name: Build run: | g++ --version cd ${{ github.workspace }}/build make -j$(nproc) ldd bin/pcm - name: Test run: | set -o pipefail bash ${{ github.workspace }}/tests/test.sh 2>&1 | tee test-log.txt - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-${{ github.sha }} path: test-log.txt - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-raw-tr-wo_ext-${{ github.sha }} path: build/bin/raw_tr_wo_ext.csv - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-raw-tr-wi_ext-${{ github.sha }} path: build/bin/raw_tr_wi_ext.csv - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: pcm-csv-${{ github.sha }} path: build/bin/pcm.csv - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: pcm-memory-csv-${{ github.sha }} path: build/bin/pcm-memory.csv - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-raw-tr-wi_ext-single_header-${{ github.sha }} path: build/bin/raw_tr_wi_ext_single_header.csv - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-raw-edp-${{ github.sha }} path: build/bin/raw_edp.txt - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-raw-json-${{ github.sha }} path: build/bin/raw_json.json - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: test-log-raw-edp-offlined-cores-${{ github.sha }} path: build/bin/raw_edp_offlined_cores.txt pcm-202502/.github/workflows/ci-windows-ip.yml000066400000000000000000000006251475730356400212170ustar00rootroot00000000000000name: windows-ip on: [workflow_dispatch, workflow_call] permissions: contents: read jobs: build: runs-on: ci-windows if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - name: windows-ip run: | ipconfig pcm-202502/.github/workflows/ci-windows.yml000066400000000000000000000024611475730356400206110ustar00rootroot00000000000000name: MSVC Windows build on: push: branches: [ '**' ] pull_request: branches: [ '**' ] env: BUILD_TYPE: Release permissions: contents: read jobs: build: runs-on: ci-windows if: ${{ github.repository != 'intel/pcm' }} steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Configure CMake run: | if (Test-Path ${{github.workspace}}\build){ Remove-Item ${{github.workspace}}\build -Recurse } cmake -B ${{github.workspace}}\build - name: Build run: | cmake --build ${{github.workspace}}\build --config ${{env.BUILD_TYPE}} --parallel - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 - name: Build MSR driver run: | chdir ${{github.workspace}}\src\WinMSRDriver msbuild MSR.vcxproj /p:Configuration=Release,Platform=x64 /t:Clean,Build /m - name: upload-artifact uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: PCMforWindows path: build/bin/**/* pcm-202502/.github/workflows/clang_scan.yml000066400000000000000000000031761475730356400206220ustar00rootroot00000000000000# This is a basic workflow to help you get started with Actions name: clang_scan # Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] pull_request: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel permissions: contents: read jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-20.04 if: ${{ github.repository == 'intel/pcm' }} # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive # Runs a set of commands using the runners shell - name: preparation run: | sudo apt-get update -qq sudo apt-get install -qq -y make clang clang-tools perl g++ cmake - name: cmake run: | rm -rf ${{ github.workspace }}/build scan-build cmake -B ${{ github.workspace }}/build - name: scan-build run: | cd ${{ github.workspace }}/build scan-build --exclude src/simdjson --status-bugs make -j pcm-202502/.github/workflows/codeql.yml000066400000000000000000000055241475730356400200000ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: ["master"] pull_request: # The branches below must be a subset of the branches above branches: ["master"] schedule: - cron: "0 0 * * 1" permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ["cpp", "python"] # CodeQL supports [ $supported-codeql-languages ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: category: "/language:${{matrix.language}}" pcm-202502/.github/workflows/cppcheck.yml000066400000000000000000000025401475730356400203040ustar00rootroot00000000000000# This is a basic workflow to help you get started with Actions name: cppcheck # Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] pull_request: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel permissions: contents: read jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-20.04 # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive # Runs a set of commands using the runners shell - name: cppcheck run: | sudo apt-get update -qq sudo apt-get install -qq -y cppcheck sh ${{ github.workspace }}/scripts/cppcheck.sh . `getconf _NPROCESSORS_ONLN` pcm-202502/.github/workflows/dependency-review.yml000066400000000000000000000017431475730356400221450ustar00rootroot00000000000000# Dependency Review Action # # This Action will scan dependency manifest files that change as part of a Pull Request, # surfacing known-vulnerable versions of the packages declared or updated in the PR. # Once installed, if the workflow run is marked as required, # PRs introducing known-vulnerable packages will be blocked from merging. # # Source repository: https://github.com/actions/dependency-review-action name: 'Dependency Review' on: pull_request: branches: [ master ] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - name: 'Checkout Repository' uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: 'Dependency Review' uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.1.3 pcm-202502/.github/workflows/docker.yml000066400000000000000000000040761475730356400200010ustar00rootroot00000000000000name: Docker Build on: push: branches: - master permissions: contents: read jobs: build: permissions: packages: write runs-on: ubuntu-20.04 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - name: Get current time uses: 1466587594/get-current-time@060cae3fbd32c181c6841788189a601ac8df8389 # v2.1.2 id: current-time with: format: YYYY-MM-DD--HH - name: Checkout code uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Cache Docker layers uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to the Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: platforms: linux/amd64 push: true tags: | ghcr.io/intel/pcm:latest opcm/pcm:latest cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache pcm-202502/.github/workflows/freebsd_build.yml000066400000000000000000000023161475730356400213160ustar00rootroot00000000000000name: FreeBSD build on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: build: runs-on: ubuntu-24.04 steps: - name: Harden Runner uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: build in FreeBSD VM id: build uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0 with: memory: 2048 shell: sh operating_system: freebsd version: '14.1' run: | sudo mkdir -p /usr/local/etc/pkg/repos sudo sh -c 'echo "FreeBSD: { url: \"https://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", enabled: yes }" > /usr/local/etc/pkg/repos/FreeBSD.conf' sudo pkg update -f sudo pkg upgrade -y sudo pkg install -y curl gmake cmake pwd ls -lah whoami env freebsd-version cmake -B build -DCMAKE_INSTALL_PREFIX=. cd build && gmake install pcm-202502/.github/workflows/freebsd_scan_build.yml000066400000000000000000000024711475730356400223240ustar00rootroot00000000000000name: FreeBSD clang-scan build on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: build: runs-on: ubuntu-24.04 steps: - name: Harden Runner uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: clang scan build in FreeBSD VM id: clang-scan-build uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0 with: memory: 2048 shell: sh operating_system: freebsd version: '14.1' run: | sudo mkdir -p /usr/local/etc/pkg/repos sudo sh -c 'echo "FreeBSD: { url: \"https://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", enabled: yes }" > /usr/local/etc/pkg/repos/FreeBSD.conf' sudo pkg update -f sudo pkg upgrade -y sudo pkg install -y curl gmake cmake devel/llvm llvm pwd ls -lah whoami env freebsd-version scan-build cmake -B build -DCMAKE_INSTALL_PREFIX=. cd build scan-build --exclude src/simdjson --status-bugs gmake pcm-202502/.github/workflows/linux_make.yml000066400000000000000000000051751475730356400206670ustar00rootroot00000000000000name: Linux make on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: build: runs-on: ubuntu-20.04 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: cmake run: | rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - name: make run: | cd ${{ github.workspace }}/build make install -j build-systemd-unit: runs-on: ubuntu-20.04 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: cmake run: | rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} -DLINUX_SYSTEMD=TRUE -DLINUX_SYSTEMD_UNITDIR=${{ github.workspace }}/build/systemd - name: make run: | cd ${{ github.workspace }}/build make install -j build-system-simdjson: runs-on: ubuntu-20.04 container: ubuntu:22.04 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install dependencies run: | apt update apt -y --no-install-recommends install build-essential cmake libsimdjson-dev libssl-dev - name: cmake run: | rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - name: make run: | cd ${{ github.workspace }}/build make install -j build-source-simdjson: runs-on: ubuntu-20.04 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: cmake run: | rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - name: make run: | cd ${{ github.workspace }}/build make install -j pcm-202502/.github/workflows/macos-scan-build.yml000066400000000000000000000015101475730356400216410ustar00rootroot00000000000000name: Mac OS X scan-build on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: build: runs-on: macOS-13 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: cmake run: | rm -rf ${{ github.workspace }}/build $(brew --prefix llvm@15)/bin/scan-build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - name: make run: | cd ${{ github.workspace }}/build $(brew --prefix llvm@15)/bin/scan-build --exclude src/simdjson --status-bugs make -j pcm-202502/.github/workflows/macosx_build.yml000066400000000000000000000013301475730356400211710ustar00rootroot00000000000000name: Mac OS X build on: push: branches: [ master ] pull_request: branches: [ master ] permissions: contents: read jobs: build: runs-on: macOS-13 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: cmake run: | rm -rf ${{ github.workspace }}/build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - name: make run: | cd ${{ github.workspace }}/build sudo make install pcm-202502/.github/workflows/scorecard.yml000066400000000000000000000061401475730356400204710ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: workflow_dispatch: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '42 8 * * 4' push: branches: [ "master" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - name: "Checkout code" uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: sarif_file: results.sarif pcm-202502/.github/workflows/stats-cron-job.yml000066400000000000000000000006501475730356400213710ustar00rootroot00000000000000name: stats-cron-job on: schedule: - cron: '30 23 * * 2' permissions: contents: read jobs: stats: runs-on: ubuntu-20.04 steps: - name: Harden Runner uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 with: egress-policy: audit - name: stats run: | curl https://hetthbszh0.execute-api.us-east-2.amazonaws.com/default/pcm-clones pcm-202502/.gitignore000066400000000000000000000003631475730356400143750ustar00rootroot00000000000000*.o *.x *.d *.a *.so *.xml /.project *.XML *.htm *.html *.dll *.patch *.orig *.out *.log *.sys *.vxd *.exe *.tgz .metadata/ *.sdf *.suo Debug Release Debug64 Release64 .metadata/ html/ latex/ *.swp *.vcxproj.user .vs/ .idea/ build src/simdjsonpcm-202502/.gitmodules000066400000000000000000000005371475730356400145650ustar00rootroot00000000000000[submodule "src/simdjson"] path = src/simdjson url = https://github.com/simdjson/simdjson.git [submodule "perfmon"] path = perfmon url = https://github.com/intel/perfmon [submodule "src/pugixml"] path = src/pugixml url = https://github.com/zeux/pugixml.git [submodule "Intel-PMT"] path = Intel-PMT url = https://github.com/intel/Intel-PMT.git pcm-202502/.pre-commit-config.yaml000066400000000000000000000007571475730356400166750ustar00rootroot00000000000000repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.16.3 hooks: - id: gitleaks - repo: https://github.com/jumanjihouse/pre-commit-hooks rev: 3.0.0 hooks: - id: shellcheck - repo: https://github.com/pocc/pre-commit-hooks rev: v1.3.5 hooks: - id: cpplint - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pylint-dev/pylint rev: v2.17.2 hooks: - id: pylint pcm-202502/CMakeLists.txt000066400000000000000000000230551475730356400151500ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2022-2025, Intel Corporation cmake_minimum_required(VERSION 3.5) project(PCM) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_POSITION_INDEPENDENT_CODE ON) option(PCM_NO_ASAN "Disable address sanitizer" ON) option(PCM_FUZZ "Enable fuzzing" OFF) option(PCM_BUILD_EXECUTABLES "Build PCM utilities" ON) option(PCM_NO_STATIC_LIBASAN "Disable static address sanitizer library" ON) if(MSVC) option(PCM_NO_STATIC_MSVC_RUNTIME_LIBRARY "Disable using static runtime under MSVC" OFF) endif() foreach(opt NO_STATIC_MSVC_RUNTIME_LIBRARY;FUZZ;NO_ASAN;NO_STATIC_LIBASAN) if(${opt}) message(DEPRECATION "Option \"${opt}\" is deprecated and will be removed soon. Please use \"PCM_${opt}\"") set(PCM_${opt} ${opt}) endif() endforeach() include(GNUInstallDirs) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) # Check for pcm*.x artifacts generated by an old build scenario using Makefiles file(GLOB_RECURSE PCM_X_ARTIFACTS ${CMAKE_CURRENT_SOURCE_DIR}/*.x) foreach(file ${PCM_X_ARTIFACTS}) file(REMOVE ${file}) message(STATUS "Removing old artifact from current source directory : ${file}") endforeach() if(PCM_X_ARTIFACTS) message(WARNING " Old pcm utilities (.x) were indicated in build folder.\n" " Old binaries are expected to be installed in system.\n" " Make sure to install the new binaries(run 'cmake --install .') after building.)") endif() message(STATUS "System: ${CMAKE_SYSTEM}") if(UNIX AND NOT APPLE) if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") set(FREE_BSD TRUE) else() set(LINUX TRUE) endif() endif() if(UNIX) # APPLE, LINUX, FREE_BSD if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "initial CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") message(STATUS "initial CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}") # required PCM common flags set (PCM_COMMON_FLAGS "-Wno-unknown-pragmas -DCMAKE_INSTALL_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"") if(LINUX) set (PCM_COMMON_FLAGS "${PCM_COMMON_FLAGS} -Wextra -DPCM_USE_PERF") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set (PCM_COMMON_FLAGS "${PCM_COMMON_FLAGS} -Wl,-z,now") endif() if(NOT DEFINED LINUX_SYSTEMD) set(LINUX_SYSTEMD FALSE) endif() if(NOT DEFINED LINUX_SYSTEMD_UNITDIR) set(LINUX_SYSTEMD_UNITDIR "${CMAKE_INSTALL_LIBDIR}/systemd/system") endif() if(LINUX_SYSTEMD) message(STATUS "A systemd unit file will be generated") message(STATUS "LINUX_SYSTEMD_UNITDIR:${LINUX_SYSTEMD_UNITDIR}") else() message(STATUS "Set LINUX_SYSTEMD=TRUE for a systemd unit file.") endif() endif(LINUX) # adding the required PCM common flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PCM_COMMON_FLAGS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PCM_COMMON_FLAGS}") message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") message(STATUS "CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}") set(PCM_OPTIONAL_FLAGS "-Wall") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set (PCM_DYNAMIC "-rdynamic") elseif() set (PCM_DYNAMIC "") endif() if(APPLE) set(PCM_NO_ASAN ON) message(STATUS "AddressSanitizer is currently disabled on MacOS") endif() if(PCM_NO_ASAN) message(STATUS "AddressSanitizer is disabled") set(PCM_ASAN "") else() message(STATUS "AddressSanitizer is enabled") message(STATUS "To disable AddressSanitizer, use -DPCM_NO_ASAN=1 option") set(PCM_ASAN "-fsanitize=address") endif() set(PCM_HARDENING_FLAGS "-fPIE -fstack-protector -D_FORTIFY_SOURCE=2 -ftrapv ${PCM_ASAN} -fwrapv -fno-delete-null-pointer-checks -fno-strict-overflow -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) message(WARNING "Old gcc compiler (version < 5), -fsanitize=undefined option is not supported.") elseif() set(PCM_HARDENING_FLAGS "${PCM_HARDENING_FLAGS} -fsanitize=undefined") endif() set(PCM_LINKER_HARDENING_FLAGS "${PCM_ASAN}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PCM_LINKER_HARDENING_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PCM_LINKER_HARDENING_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "${PCM_OPTIONAL_FLAGS} -O3 ${PCM_DYNAMIC} ${PCM_HARDENING_FLAGS}") set(CMAKE_CXX_FLAGS_DEBUG "${PCM_OPTIONAL_FLAGS} -O0 -g ${PCM_DYNAMIC} ${PCM_HARDENING_FLAGS}") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${PCM_OPTIONAL_FLAGS} -O3 -g ${PCM_DYNAMIC} ${PCM_HARDENING_FLAGS}") if(FREE_BSD) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -lexecinfo") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -lexecinfo") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -lexecinfo") endif(FREE_BSD) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") message(STATUS "CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") message(STATUS "CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}") message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") endif(UNIX) if(PCM_FUZZ) set(FUZZER_OPTIONS "-fsanitize=fuzzer,address -fprofile-instr-generate -fcoverage-mapping") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FUZZER_OPTIONS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FUZZER_OPTIONS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FUZZER_OPTIONS}") message(STATUS "CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}") message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}") endif(PCM_FUZZ) ####################### # pugixml dependency ####################### add_library(PCM_PUGIXML INTERFACE) # interface library for pugixml if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/pugixml/src/pugixml.cpp") message(STATUS "Local pugixml exists: ${CMAKE_CURRENT_SOURCE_DIR}/src/pugixml/src/pugixml.cpp") set(PCM_PUGIXML_CPP ${CMAKE_CURRENT_SOURCE_DIR}/src/pugixml/src/pugixml.cpp) add_compile_options(-DPCM_PUGIXML_AVAILABLE) else() message(STATUS "Local pugixml doesn't exist") # message(WARNING # " ${CMAKE_CURRENT_SOURCE_DIR}/src/pugixml/src/pugixml.cpp doesn't exist\n" # " Use `git clone --recursive` flag when cloning pcm repository to clone pugixml submodule as well or\n" # " update submodule with command 'git submodule update --init --recursive' or\n" # " run 'git clone https//github.com/zeux/pugixml.git' in 'src' directory to get pugixml library") endif() ####################### # End of pugixml dependency section ####################### ####################### # Install ####################### set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) add_subdirectory(src) add_subdirectory(examples) add_subdirectory(tests) message(STATUS "Install directory: ${CMAKE_INSTALL_PREFIX}") ####################### # CPack (only UNIX) ####################### if(UNIX) if(EXISTS "/etc/debian_version") # for Debian family set(CPACK_GENERATOR "DEB") message(STATUS "CPACK_GENERATOR is DEB") elseif(EXISTS "/etc/redhat-release") # for RHEL, Fedora, CentOs set(CPACK_GENERATOR "RPM") message(STATUS "CPACK_GENERATOR is RPM") else() if(EXISTS "/etc/os-release") file(READ "/etc/os-release" OS_PARAMS) string(REGEX MATCH "suse" OUT ${OS_PARAMS}) # for Suse-like systems if(OUT STREQUAL "suse") set(CPACK_GENERATOR "RPM") message(STATUS "CPACK_GENERATOR is RPM") else() set(CPACK_GENERATOR "TXZ") set(CPACK_SET_DESTDIR ON) message(STATUS "CPACK_GENERATOR is TXZ") endif() else() set(CPACK_GENERATOR "TXZ") set(CPACK_SET_DESTDIR ON) message(STATUS "CPACK_GENERATOR is TXZ") endif() endif() set(CPACK_PACKAGE_CONTACT "intel ") set(CPACK_PACKAGE_NAME "pcm") set(CPACK_PACKAGE_VERSION "0000") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Intel(r) Performance Counter Monitor (Intel(r) PCM)") set(CPACK_PACKAGE_DESCRIPTION "\ Intel(r) Performance Counter Monitor (Intel(r) PCM) is an application programming\n\ interface (API) and a set of tools based on the API to monitor\n\ performance and energy metrics of Intel(r) Core(tm), Xeon(r), Atom(tm)\n\ and Xeon Phi(tm) processors. PCM works on Linux, Windows, Mac OS X,\n\ FreeBSD and DragonFlyBSD operating systems.") set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RPM_PACKAGE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CMAKE_INSTALL_PREFIX}) set(CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_RPM_PACKAGE_RELOCATABLE TRUE) set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_RPM_COMPONENT_INSTALL ON) include (CPack) endif(UNIX) pcm-202502/CONTRIBUTING.md000066400000000000000000000027411475730356400146400ustar00rootroot00000000000000-------------------------------------------------------------------------------- Contributing to Intel® Performance Counter Monitor (Intel® PCM) -------------------------------------------------------------------------------- We warmly welcome contributions from everyone. If you wish to submit patches, please do so using GitHub's pull request (PR) feature. When adding substantial new features, it's crucial to also include relevant tests in our test suite, located in the test/test.sh file. This step helps verify the proper functioning of the new features. -------------------------------------------------------------------------------- License -------------------------------------------------------------------------------- Intel® PCM is licensed using a [BSD 3-clause license](https://github.com/intel/pcm/blob/master/LICENSE). All code submitted to the project is required to carry that license. -------------------------------------------------------------------------------- Coding Style -------------------------------------------------------------------------------- While we don't enforce stringent coding style requirements, we do encourage adherence to standard coding style practices, such as those outlined in the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). When submitting patches, we request that you maintain the style consistency with the surrounding code. We discourage the use of tabs; please use 4 spaces for indentation instead. pcm-202502/Dockerfile000066400000000000000000000014611475730356400143770ustar00rootroot00000000000000FROM fedora:41@sha256:3ec60eb34fa1a095c0c34dd37cead9fd38afb62612d43892fcf1d3425c32bc1e AS builder # Dockerfile for Intel PCM sensor server # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2020-2024 Intel Corporation RUN dnf -y install gcc-c++ git findutils make cmake openssl openssl-devel libasan libasan-static hwdata COPY . /tmp/pcm RUN cd /tmp/pcm && mkdir build && cd build && cmake -DPCM_NO_STATIC_LIBASAN=OFF .. && make -j FROM fedora:41@sha256:3ec60eb34fa1a095c0c34dd37cead9fd38afb62612d43892fcf1d3425c32bc1e COPY --from=builder /tmp/pcm/build/bin/* /usr/local/bin/ COPY --from=builder /tmp/pcm/build/bin/opCode*.txt /usr/local/share/pcm/ COPY --from=builder /usr/share/hwdata/pci.ids /usr/share/hwdata/pci.ids ENV PCM_NO_PERF=1 ENTRYPOINT [ "/usr/local/bin/pcm-sensor-server", "-p", "9738", "-r" ] pcm-202502/Doxyfile000066400000000000000000001437321475730356400141230ustar00rootroot00000000000000# Doxyfile 1.4.6 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = "Intel(r) Performance Counter Monitor" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, # Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, # Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, # Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, # Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # This tag can be used to specify the encoding used in the generated output. # The encoding is not always determined by the language that is chosen, # but also whether or not the output is meant for Windows or non-Windows users. # In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES # forces the Windows encoding (this is the default for the Windows binary), # whereas setting the tag to NO uses a Unix-style encoding (the default for # all platforms other than Windows). USE_WINDOWS_ENCODING = NO # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like the Qt-style comments (thus requiring an # explicit @brief command for a brief description. JAVADOC_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to # include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from the # version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. # INPUT = src # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = *.hpp *.h *.cpp *.c # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compressed HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = YES # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. MAX_DOT_GRAPH_WIDTH = 1024 # The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. MAX_DOT_GRAPH_HEIGHT = 1024 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that a graph may be further truncated if the graph's # image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH # and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), # the graph is not depth-constrained. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. # Warning: Depending on the platform used, enabling this option may lead to # badly anti-aliased labels on the edges of a graph (i.e. they become hard to # read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO pcm-202502/Intel-PMT/000077500000000000000000000000001475730356400141145ustar00rootroot00000000000000pcm-202502/LICENSE000066400000000000000000000030201475730356400134030ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2009-2024, Intel Corporation Copyright (c) 2016-2020, opcm All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pcm-202502/README.md000066400000000000000000000267321475730356400136740ustar00rootroot00000000000000-------------------------------------------------------------------------------- Intel® Performance Counter Monitor (Intel® PCM) -------------------------------------------------------------------------------- [![CodeQL](https://github.com/intel/pcm/actions/workflows/codeql.yml/badge.svg?branch=master)](https://github.com/intel/pcm/security/code-scanning/tools/CodeQL/status) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/intel/pcm/badge)](https://securityscorecards.dev/viewer/?uri=github.com/intel/pcm) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8652/badge)](https://www.bestpractices.dev/projects/8652) [PCM Tools](#pcm-tools) | [Building PCM](#building-pcm-tools) | [Downloading Pre-Compiled PCM](#downloading-pre-compiled-pcm-tools) | [FAQ](#frequently-asked-questions-faq) | [API Documentation](#pcm-api-documentation) | [Environment Variables](#pcm-environment-variables) | [Compilation Options](#custom-compilation-options) Intel® Performance Counter Monitor (Intel® PCM) is an application programming interface (API) and a set of tools based on the API to monitor performance and energy metrics of Intel® Core™, Xeon®, Atom™ and Xeon Phi™ processors. PCM works on Linux, Windows, Mac OS X, FreeBSD, DragonFlyBSD and ChromeOS operating systems. *Github repository statistics:* ![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fhetthbszh0.execute-api.us-east-2.amazonaws.com%2Fdefault%2Fpcm-clones) ![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2F5urjfrshcd.execute-api.us-east-2.amazonaws.com%2Fdefault%2Fpcm-yesterday-clones) ![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fcsqqh18g3l.execute-api.us-east-2.amazonaws.com%2Fdefault%2Fpcm-today-clones) We welcome bug reports and enhancement requests, which can be submitted via the "Issues" section on GitHub. For those interested in contributing to the code, please refer to the guidelines outlined in the CONTRIBUTING.md file. -------------------------------------------------------------------------------- Current Build Status -------------------------------------------------------------------------------- - Linux: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/linux_make.yml?branch=master)](https://github.com/intel/pcm/actions/workflows/linux_make.yml?query=branch%3Amaster) - Windows: [![Build status](https://ci.appveyor.com/api/projects/status/github/intel/pcm?branch=master&svg=true)](https://ci.appveyor.com/project/opcm/pcm) - FreeBSD: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/freebsd_build.yml?branch=master)](https://github.com/intel/pcm/actions/workflows/freebsd_build.yml?query=branch%3Amaster) - OS X: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/macosx_build.yml?branch=master)](https://github.com/intel/pcm/actions/workflows/macosx_build.yml?query=branch%3Amaster) - Docker container: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/docker.yml?branch=master)](doc/DOCKER_README.md) -------------------------------------------------------------------------------- PCM Tools -------------------------------------------------------------------------------- PCM provides a number of command-line utilities for real-time monitoring: - **pcm** : basic processor monitoring utility (instructions per cycle, core frequency (including Intel(r) Turbo Boost Technology), memory and Intel(r) Quick Path Interconnect bandwidth, local and remote memory bandwidth, cache misses, core and CPU package sleep C-state residency, core and CPU package thermal headroom, cache utilization, CPU and memory energy consumption) ![pcm output](https://github.com/intel/pcm/assets/25432609/88485ff5-dc7c-4a1c-974f-8396f03829dc) - **pcm-sensor-server** : pcm collector exposing metrics over http in JSON or Prometheus (exporter text based) format ([how-to](doc/PCM-EXPORTER.md)). Also available as a [docker container](doc/DOCKER_README.md). More info about Global PCM events is [here](doc/PCM-SENSOR-SERVER-README.md). - **pcm-memory** : monitor memory bandwidth (per-channel and per-DRAM DIMM rank) ![pcm-memory output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-memory.x.JPG) - **pcm-accel** : [monitor IntelÂŽ In-Memory Analytics Accelerator (IntelÂŽ IAA), IntelÂŽ Data Streaming Accelerator (IntelÂŽ DSA) and IntelÂŽ QuickAssist Technology (IntelÂŽ QAT) accelerators](doc/PCM_ACCEL_README.md) ![image](https://user-images.githubusercontent.com/25432609/218480696-42ade94f-e0c3-4000-9dd8-39a0e75a210e.png) - **pcm-latency** : monitor L1 cache miss and DDR/PMM memory latency - **pcm-pcie** : monitor PCIe bandwidth per-socket - **pcm-iio** : monitor PCIe bandwidth per PCIe device ![pcm-iio output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-iio.png) - **pcm-numa** : monitor local and remote memory accesses - **pcm-power** : monitor sleep and energy states of processor, Intel(r) Quick Path Interconnect, DRAM memory, reasons of CPU frequency throttling and other energy-related metrics - **pcm-tsx**: monitor performance metrics for Intel(r) Transactional Synchronization Extensions - **pcm-core** and **pmu-query**: query and monitor arbitrary processor core events - **pcm-raw**: [program arbitrary **core** and **uncore** events by specifying raw register event ID encoding](doc/PCM_RAW_README.md) - **pcm-bw-histogram**: collect memory bandwidth utilization histogram Graphical front ends: - **pcm Grafana dashboard** : front-end for Grafana (in [scripts/grafana](scripts/grafana) directory). Full Grafana Readme is [here](scripts/grafana/README.md) ![pcm grafana output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-dashboard.png) - **pcm-sensor** : front-end for KDE KSysGuard - **pcm-service** : front-end for Windows perfmon There are also utilities for reading/writing model specific registers (**pcm-msr**), PCI configuration registers (**pcm-pcicfg**), memory mapped registers (**pcm-mmio**) and TPMI registers (**pcm-tpmi**) supported on Linux, Windows, Mac OS X and FreeBSD. And finally a daemon that stores core, memory and QPI counters in shared memory that can be be accessed by non-root users. -------------------------------------------------------------------------------- Building PCM Tools -------------------------------------------------------------------------------- Clone PCM repository with submodules: ``` git clone --recursive https://github.com/intel/pcm cd pcm ``` or clone the repository first, and then update submodules with: ``` git submodule update --init --recursive ``` Install cmake (and libasan on Linux) then: ``` mkdir build cd build cmake .. cmake --build . ``` You will get all the utilities (pcm, pcm-memory, etc) in `build/bin` directory. '--parallel' can be used for faster building: ``` cmake --build . --parallel ``` Debug is default on Windows. Specify config to build Release: ``` cmake --build . --config Release ``` On Windows and MacOs additional drivers and steps are required. Please find instructions here: [WINDOWS_HOWTO.md](doc/WINDOWS_HOWTO.md) and [MAC_HOWTO.txt](doc/MAC_HOWTO.txt). FreeBSD/DragonFlyBSD-specific details can be found in [FREEBSD_HOWTO.txt](doc/FREEBSD_HOWTO.txt) ![pcm-build-run-2](https://user-images.githubusercontent.com/25432609/205663554-c4fa1724-6286-495a-9dbd-0104de3f535f.gif) -------------------------------------------------------------------------------- Downloading Pre-Compiled PCM Tools -------------------------------------------------------------------------------- - Linux: * Ubuntu/Debian: `sudo apt install pcm` * openSUSE: `sudo zypper install pcm` * RHEL8.5 or later: `sudo dnf install pcm` * Fedora: `sudo yum install pcm` * RPMs and DEBs with the *latest* PCM version for RHEL/SLE/Ubuntu/Debian/openSUSE/etc distributions (binary and source) are available [here](https://software.opensuse.org/download/package?package=pcm&project=home%3Aopcm) - Windows: download PCM binaries as [appveyor build service](https://ci.appveyor.com/project/opcm/pcm/history) artifacts and required Visual C++ Redistributable from [www.microsoft.com](https://www.microsoft.com/en-us/download/details.aspx?id=48145). Additional steps and drivers are required, see [WINDOWS_HOWTO.md](doc/WINDOWS_HOWTO.md). - Docker: see [instructions on how to use pcm-sensor-server pre-compiled container from docker hub](doc/DOCKER_README.md). -------------------------------------------------------------------------------- Executing PCM tools under non-root user on Linux -------------------------------------------------------------------------------- Executing PCM tools under an unprivileged user on a Linux operating system is feasible. However, there are certain prerequisites that need to be met, such as having Linux perf_event support for your processor in the Linux kernel version you are currently running. To successfully run the PCM tools, you need to set the `/proc/sys/kernel/perf_event_paranoid` setting to -1 as root once: ``` echo -1 > /proc/sys/kernel/perf_event_paranoid ``` and configure two specific environment variables when running the tools under a non-root user: ``` export PCM_NO_MSR=1 export PCM_KEEP_NMI_WATCHDOG=1 ``` For instance, you can execute the following commands to set the environment variables and run pcm: ``` export PCM_NO_MSR=1 export PCM_KEEP_NMI_WATCHDOG=1 pcm ``` or (to run the pcm sensor server as non-root): ``` PCM_NO_MSR=1 PCM_KEEP_NMI_WATCHDOG=1 pcm-sensor-server ``` Please keep in mind that when executing PCM tools under an unprivileged user on Linux, certain PCM metrics may be unavailable. This limitation specifically affects metrics that rely solely on direct MSR (Model-Specific Register) register access. Due to the restricted privileges of the user, accessing these registers is not permitted, resulting in the absence of corresponding metrics. -------------------------------------------------------------------------------- Frequently Asked Questions (FAQ) -------------------------------------------------------------------------------- PCM's frequently asked questions (FAQ) are located [here](doc/FAQ.md). -------------------------------------------------------------------------------- PCM API documentation -------------------------------------------------------------------------------- PCM API documentation is embedded in the source code and can be generated into html format from source using Doxygen (www.doxygen.org). -------------------------------------------------------------------------------- PCM environment variables -------------------------------------------------------------------------------- The list of PCM environment variables is located [here](doc/ENVVAR_README.md) -------------------------------------------------------------------------------- Custom compilation options -------------------------------------------------------------------------------- The list of custom compilation options is located [here](doc/CUSTOM-COMPILE-OPTIONS.md) -------------------------------------------------------------------------------- Packaging -------------------------------------------------------------------------------- Packaging with CPack is supported on Debian and Redhat/SUSE system families. To create DEB of RPM package need to call cpack after building in build folder: ``` cd build cpack ``` This creates package: - "pcm-VERSION-Linux.deb" on Debian family systems; - "pcm-VERSION-Linux.rpm" on Redhat/SUSE-family systems. Packages contain pcm-\* binaries and required for usage opCode-\* files. pcm-202502/SECURITY.md000066400000000000000000000006251475730356400141770ustar00rootroot00000000000000# Security Policy Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. ## Reporting a Vulnerability Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html).pcm-202502/_service000066400000000000000000000011101475730356400141160ustar00rootroot00000000000000 github.com https /intel/pcm/archive/master.zip github.com https /intel/pcm/archive/master.tar.gz raw.githubusercontent.comhttps/intel/pcm/master/pcm.spec pcm-202502/appveyor.yml000066400000000000000000000011221475730356400147670ustar00rootroot00000000000000version: 1.0.{build} image: - Visual Studio 2019 - Visual Studio 2017 configuration: Release platform: x64 before_build: - cmd: >- cmake --version cmake -B build build_script: - cmake --build build --config Release --parallel after_build: - cmd: 7z a pcm-all.zip %APPVEYOR_BUILD_FOLDER%\build\bin\Release\*.exe %APPVEYOR_BUILD_FOLDER%\build\bin\Release\*.dll %APPVEYOR_BUILD_FOLDER%\build\src\Release\*.lib %APPVEYOR_BUILD_FOLDER%\build\src\Release\*.exp %APPVEYOR_BUILD_FOLDER%\src\windows\PCM-Service.exe.config artifacts: - path: pcm-all.zip name: pcm-all pcm-202502/cmake/000077500000000000000000000000001475730356400134635ustar00rootroot00000000000000pcm-202502/cmake/CPM.cmake000066400000000000000000000017011475730356400151030ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors set(CPM_DOWNLOAD_VERSION 0.40.2) set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") if(CPM_SOURCE_CACHE) set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") elseif(DEFINED ENV{CPM_SOURCE_CACHE}) set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") else() set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") endif() # Expand relative path. This is important if the provided path contains a tilde (~) get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) file(DOWNLOAD https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} ) include(${CPM_DOWNLOAD_LOCATION}) pcm-202502/doc/000077500000000000000000000000001475730356400131505ustar00rootroot00000000000000pcm-202502/doc/CUSTOM-COMPILE-OPTIONS.md000066400000000000000000000014041475730356400167220ustar00rootroot00000000000000cpucounters.h : `#define PCM_HA_REQUESTS_READS_ONLY` For the metric "LOCAL: number of local memory access in %“ (getLocalMemoryRequestRatio API) count only read accesses (local and all). cpucounters.h : `#define PCM_DEBUG_TOPOLOGY` print detailed CPU topology information cpucounters.h : `#define PCM_UNCORE_PMON_BOX_CHECK_STATUS` verify uncore PMU register state after programming types.h : `#define PCM_DEBUG` print some debug information pci.h : `#define PCM_USE_PCI_MM_LINUX` use /dev/mem (direct memory mapped I/O) for PCICFG register access on Linux. This might be required for accessing registers in extended configuration space (beyond 4K) in *pcm-pcicfg* utility. Recent Linux kernels also require to be booted with iomem=relaxed option to make this work. pcm-202502/doc/CXL_README.md000066400000000000000000000014311475730356400151340ustar00rootroot00000000000000PCM can collect CLX bandwidth using the methods below. -------------------------------------------------------------------------------- CXL.mem and CXL.cache traffic -------------------------------------------------------------------------------- Please use pcm-memory utility for monitoring CXL.mem and CLX.cache traffic. pcm-memory will detect available CXL ports and will show traffic per CXL port and protocol (mem and cache) and per direction (read and write). -------------------------------------------------------------------------------- CXL.io traffic -------------------------------------------------------------------------------- pcm-iio utility should be used to monitor CXL.io traffic. pcm-iio will show traffic per CXL device and direction (inbound/outbound, read/write) pcm-202502/doc/DOCKER_README.md000066400000000000000000000024151475730356400154600ustar00rootroot00000000000000-------------------------------------------------------------------------------- How To Run Intel(r) Performance Counter Monitor Server Container from GitHub Container Repository or Docker Hub -------------------------------------------------------------------------------- As root user: 1. ``modprobe msr`` 2. ``docker run -d --name pcm --privileged -p 9738:9738 ghcr.io/intel/pcm`` (GitHub Container repository) or ``docker run -d --name pcm --privileged -p 9738:9738 opcm/pcm`` (Dockerhub repository) - the container can also be run with limited capabilities without the privileged mode: ``docker run -d --name pcm --cap-add=SYS_ADMIN --cap-add=SYS_RAWIO --device=/dev/cpu --device=/dev/mem -v /sys/firmware/acpi/tables/MCFG:/pcm/sys/firmware/acpi/tables/MCFG:ro -v /proc/bus/pci/:/pcm/proc/bus/pci/ -v /proc/sys/kernel/nmi_watchdog:/pcm/proc/sys/kernel/nmi_watchdog -v /sys:/sys:rw -p 9738:9738 ghcr.io/intel/pcm`` (there is also a docker-compose file containing these options: https://raw.githubusercontent.com/intel/pcm/master/docker-compose.yml) This will start pcm-sensor-server container exposing CPU metrics from the whole system at port 9738 The URLs of the docker container repositories: - https://github.com/intel/pcm/pkgs/container/pcm - https://hub.docker.com/r/opcm/pcm pcm-202502/doc/ENVVAR_README.md000066400000000000000000000016731475730356400155170ustar00rootroot00000000000000`PCM_NO_PERF=1` : don't use Linux perf events API to program *core* PMUs (default is to use it) `PCM_USE_UNCORE_PERF=1` : use Linux perf events API to program *uncore* PMUs (default is *not* to use it) `PCM_NO_RDT=1` : don't use RDT metrics for a better interoperation with pqos utility (https://github.com/intel/intel-cmt-cat) `PCM_USE_RESCTRL=1` : use Linux resctrl driver for RDT metrics `PCM_PRINT_TOPOLOGY=1` : print detailed CPU topology `PCM_KEEP_NMI_WATCHDOG=1` : don't disable NMI watchdog (reducing the core metrics set) `PCM_NO_MAIN_EXCEPTION_HANDLER=1` : don't catch exceptions in the main function of pcm tools (a debugging option) `PCM_ENFORCE_MBM=1` : force-enable Memory Bandwidth Monitoring (MBM) metrics (LocalMemoryBW = LMB) and (RemoteMemoryBW = RMB) on processors with RDT/MBM errata `PCM_DEBUG_LEVEL=x` : x is an integer defining debug output level. level = 0 (default): minimal or no debug info, > 0 increases verbosity pcm-202502/doc/FAQ.md000066400000000000000000000112751475730356400141070ustar00rootroot00000000000000 ## Q1 pcm-iio Tool outputs "Server CPU is required for this tool! Program aborted". Is there a way I can monitor my PCIe link bandwidth using one of the PCM utilities on client CPU? Answer: The "IO" metric in *pcm* utility is the closest capability to monitor I/O PCIe traffic on client CPUs. ## Q2 PCM reports "ERROR: QPI LL monitoring device (...) is missing". How to fix it? Answer: It is likely a BIOS issue. See details [here](https://software.intel.com/content/www/us/en/develop/articles/bios-preventing-access-to-qpi-performance-counters.html) ## Q3 Does PCM work inside a virtual machine? Answer: PCM works inside virtual machines which support vPMU (with a limited set of metrics supported by vPMU). For example on AWS instances this is indicated by the presence of [arch_perfmon](https://instaguide.io/info.html?type=c5.18xlarge) flag: https://instaguide.io/ . Enabling vPMU in hypervisors is documented [in Hardware Event-Based Sampling section](https://software.intel.com/content/www/us/en/develop/documentation/vtune-help/top/set-up-analysis-target/on-virtual-machine.html). ## Q4 Does PCM work inside a docker container? Answer: yes, it does. An example of how to run PCM inside a docker container is located [here](DOCKER_README.md). The recipe works also for other PCM utilities besides pcm-sensor-server. ## Q5 pcm-power reports "Unsupported processor model". Can you add support for pcm-power for my CPU? Answer: most likely you have a client CPU which does not have required hardware performance monitoring units. PCM-power can not work without them. ## Q6 pcm-memory reports that the CPU is not supported. Can you add support for pcm-memory for my CPU? Answer: most likely you have a client CPU which does not have required hardware performance monitoring units. PCM-memory can not work without them. ## Q7 Can PCM be used for measuring energy, CPU cycles, etc for a particular process or does it measure for the system as a whole? Answer: PCM supports measurement for the whole system, per processor, per physical or per logical core. If you need monitoring per-process or user per-thread you can pin your process and/or thread to certain cores and read PCM data for these cores. But keep in mind that the OS can also schedule other processes or threads on this core and this may disturb your measurements. For a precise per-process or per-thread measurement the Intel VTune profiler or Linux perf profiler should be used. ## Q8 PCM reports ``` opening /dev/mem failed: errno is 1 (Operation not permitted) Can not read memory controller counter information from PCI configuration space. Access to memory bandwidth counters is not possible. You must be root to access these SandyBridge/IvyBridge/Haswell counters in PCM. Secure Boot detected. Using Linux perf for uncore PMU programming. ``` How to fix it? Answer: Linux disables access to /dev/mem because Secure Boot is enabled in the BIOS. Disable Secure Boot in the BIOS to enable memory controller statistics (e.g. memory read and write bandwidth). ## Q9 PCM reports ``` Linux Perf: Error on programming ... ``` How to fix it? **Answer:** It is an issue with the Linux kernel perf driver. As a workaround upgrade your Linux kernel to the latest available/possible or run PCM with its own programming logic: ``` export PCM_NO_PERF=1 pcm -r ``` ## Q10 If you are getting the error `Starting MSR service failed with error 3 The system cannot find the path specified.` try to uninstall the driver by running `pcm --uninstallDriver` and optionally reboot the system. ## Q11 Is PCM supported on AWS instances **Answer**: Not all AWS instances allow users to collect CPU telemetry by exposing PMU to the user. The following instances can be used: * Bare metal instances: allow collection of CPU metrics from both core (e.g. instructions per cycle, cache misses) and uncore (e.g. memory controller, UPI) * Full-socket (single socket, two socket, etc) virtualized instances: e.g. m5d.12xlarge, m5.24xlarge, m5.12xlarge. Only core CPU metrics are exposed, and certain CPU performance events are forbidden (e.g. offcore response events, events collecting “any_thread” information). “arch_perfmon” flag in /proc/cpuinfo indicates if the core CPU metrics are exposed (example: https://instaguide.io/info.html?type=m5.12xlarge ). The mechanism of PMU virtualization is commonly known as vPMU. ## Q12 pcm-pcie reports that the CPU is not supported: "Jaketown, Ivytown, Haswell, Broadwell-DE, Skylake, Icelake, Snowridge and Sapphirerapids Server CPU is required for this tool! Program aborted" Can you add support for pcm-pcie for my CPU? Answer: most likely you have a client CPU which does not have required hardware performance monitoring units. pcm-pcie can not work without them. pcm-202502/doc/FREEBSD_HOWTO.txt000066400000000000000000000003131475730356400157400ustar00rootroot00000000000000Building: $ cmake -B build $ cmake --build build Runtime requirements: * the cpuctl(4) driver needs to be loaded PCM is also available as a port maintained by The FreeBSD Project (sysutils/intel-pcm). pcm-202502/doc/KSysGuard HOWTO.docx000066400000000000000000012442331475730356400165750ustar00rootroot00000000000000PK!…6Nšc[Content_Types].xml ĸ( ´•KK1…÷‚˙aČV:Š.D¤S>–*XÁmšÜ™ķ"šUûīŊécŌvÄ:›Iî9įË ÜŒnžŦ)> &í]ÅÎË!+ĀI¯´k*ö:y\ą"ĄpJī b Hėf|z2š,¤‚Ô.Ul†Ž9OrV¤Ōp´SûhŌolxō]4Ā/†ÃK.ŊCp8ĀėÁÆŖ;¨ÅÜ`q˙EË+’āVÜŽęrTÅ´ÍúŧÎw*"˜´%!-Ō>˙pj‹k°f*IšŦI3ŌėIČ;ûÖē'jfÔ ŠgņQXĒâŸ>*Žŧœ[R–‡mvpúēÖZ}v ŅKH‰nɚ˛ŨąBģ ˙.9Očí›5\#ØįčC:?§5Í~QCÛÃŊŊps;…Hô˙ߌÖē"áÂ@ú‚•ow< ’ €ĩs'Â'L_zŖøaŪ R{ÎcˇŅZwB€S=1lœŅJS}ôamŨ 4Äaõ=~:,mERårŅŖ˙pėÍ ĪęAøÕjÉúčķA~¨Ų|ųDŽŋ˙˙PK!‘ˇīN _rels/.rels ĸ( Ŧ’ÁjÃ0 @īƒũƒŅŊQÚÁŖN/cĐÛŲ[ILÛØj×ūũ<ØØ]éaGËŌĶ“ĐzsœFuā”]đ–U ŠŊ Öų^Ã[ûŧx•…ŧĨ1xÖp⠛æöfũĘ#I)ʃ‹YŠĪ‘øˆ˜ÍĀå*Döå§ i")ĪÔc$ŗŖžqU×÷˜~3 ™1ÕÖjH[{Ē=Ež†ēÎ~ f?ą—3-ÂŪ˛]ÄTꓸ2j)õ,l0/%œ‘bŦ đŧŅęzŖŋ§Å‰…, Ą ‰/û|f\Zū኿?6ī!Y´_áoœ]Aķ˙˙PK!T>kŲ ^?word/document.xmlė[Ûrã6}ßĒũ”RɃ-ŪE)ąRē:S™ITcOĒö"!‹e’ādīĶ|Hv?nždģAR7Û2%Ų;™ŠąârĐh4ē›ÔO?ß$1™3!#žžÕĖSŖFXđ0J¯ÎjŸ.‡'~HEĶÆ™DĢ/¸ë–aē” 0)ažMįTÖ ¸āĻZ(č# SĻT(vŗÂ0÷qëÍēČ:Vh™wĄėŊĄŧ:JuČ9¤ēƒä†tĪâŧÐŦģHÃėģHūaHwĖ)škā…á=ƒhí_w­ßđá¯ąŪÅ čŗ}+ŸXę헐zFEXiĪiøŒ;~ē!|fZ’ ßkvũmŖ—}Ė÷>ėf7[´›-Ö ',üãŪô~YmÛļ<Ûö^—ŦĨ!ã!.§‘$đŸ9åB‘É@DŌw2å rËg$ ) #™Åôv§Ë,bŪ>†ō`4:zaË(S:û(%jĘȝũA봆Ļ3čÚŨũwü1fđB&ëMcØ÷ĒP›=°é$XĀ&ĩyI5ÁŽÍĻšßIgIŪ3ŠįqŲĪXļŊ Ë:ŗXķrĀžGäXgvŋyé˜yxx}ũąô ί%ŸŠnúĄû'9ÚPÉ÷āņˆƒ>~&Y˜ģA¸Xsô§?<ĩ†Ģ°ŅûŨ–įēnwØ5+¸­=īČ<Ûë oÆm­ Ių­SĪņˆæ‹ ŗMéšÕ%ʨKŨeé=Čh:ļQ\~\]Ę(Éb6âR÷Íŗysö ‹ŽĻ IË5áFÖr iĖĻQöy ;Æ<¸fxS E ŧAŊK{p†P„Æ1_ü>g"Ļ™ŽĀķBBM°šÍĻmtq㰁…‘ŪwĮ ›žeâe­Ĩ\äFĪr[j>ke\FhĘŋ,Å žœÕĪ’4}~ŸL$SmËđ,Į7@oëĩåe´ûĮ,Zkn[ČfÃtܝĀč`×ø,{˜nĶđn°Ķoē\äĢb“ Ô ī‹Šl.ŦÍöåŦÖt-7^Ša2]ōŦ“†]ŽOōڐ#ā?Čjj$Ĩ ĘQ¨™`D[)t ~›Ÿãzĸ`( mé5īase™> šg SŪ›‚ŗx×@-Éîųu ĒO%3q7-ö8T–k Đ ÔʖbAéh´tûkÆ PEą[ÆŊģUöÉGP ߜģĘ]U ÁSFCYę|E_nH1ŽŖlÅ1΀e"Z,ãņ†ķę#mI|„™ō˛LS,N`XQ__kĐsŦ`ņJ‚ŗ#ãÅÂ"éLqŊW7‘ā7ŸLļ:ÅsŗķĐÔWÃ3!Õ9ã Áˆ ix:/ ŲĘ.Xr”KO§dQŽ­–$Â;Š8€<Æ(¤BÍUĐeE#¤ ųqZŦWZáOˇ­™äúu~r΍=ûŌĨßKž5}ÔŒ*÷†)[EĮ-ōåķ˙@ėäģ+õãoPų‹‚E%Ī^ē¤ã/Ÿ˙Ŧ"És&=Æĸ¸|ˆŲXn§Ņs+ŨmIôÄ<‰iZ¸OHb-ĩ† ģ~ôīÍ_*f›ļi=eĖŖkvx 7“Yo8ėēo%fێã¯7hģÛaĀų´ßPĐÖģõ‚ﯸ´ŋR>ë ×ĒĢF ŧ?îƒwûŽ&ŲŠū Ī6nœaį¯=x ›P$āČ|°N( H$Ŗ)ÜŨķ<ŀ?Ē#na×.q=´zu˛E”ų”č—Q‹\ĖÁ_W’J’)ˇÃ,?¨-njŲEEūÅσ˙Ô6u-Kx2åZÆ\*ōĻ‚Â6€SPIąĶ¸Ķ ã§ŌqH¤ģˆßãVLc*v"(ƒÃMKė!ļgŸũ]H{Е68Ø)>„t’Ą6 ąl­č%EPŊVK°ôØöŊä"$DšvhÔ;šõqŧWšDPŪҜ†í‹Á?ÃŅKŪH‚fr~SL|›â6$Ã,_‘FaÅ@ôĻ‘\&ą/IЕuÅNņåđp™FģøÁ1˙˙˙PK!!2–ą{Ē word/footnotes.xml´–ÛrÚ0†ī;ĶwđčžČ ā2-I:šë$í(˛MŦÃHÃÛwelLcš1Δ Ų^y?ũģZ-žŨíEė˜ą\É9ŠnB0IUÆåzŽ~˙zLP`‘ɕdst`Ũ-ž~™éJ)'•c6†´iĄémœĶ)Ɩn˜ öFpj”U+wC•Ājĩâ”áB™ Įa–wÚ(ĘŦ…—DîˆEŽîģŅ2C pöĀ!ĻbÛ7ŒčjČOņ¤ Š{€ Â8jŖ’ĢQcėUĩ@Ã^ PÕ"ú‘.7îGŠÛ¤Û~¤¤Mšô#ĩĘI´ \i&arĨŒ Í bŪļz`Må9w`†ãC¸|ëĄŧN‘dWnąP˓Ŧύ9Ú™Vūƒ“ŋ—žũĢKíaēÄtšWt+˜te䨰rĄ¤Ũp}:áĸ/ &75d÷Q;‘×ī:ęx\ū՞l€]äWųųQųĮÄ(ė°#qōč"áī5k%ǰY¸WjΒul 5 nƔulø5cR10mN¨įđŽGŖæwÅsx“بc{/æ `ŲuˆQ­ÃÄYDzũš˛ũaÔV74ū9ÚSĶ„ ˙=pĢ*˙ķ#i?'æeC4ô&AͧĩT†ŧæ Š9€z Ęđ#lĢŋ”ˇløF€gß/A‘ēƒOË41Ä)ƒĀäĢh•/jpĻ~î ŒQœ$͇Į!*­đīāŧõļúyWø˜Ęžį( §Éčņ{r2ŨŗŲæŽ=ķĶ›ÆÉdy\Đøá¤/f¸´Á¨ËąV~1 Ǥãr[ļۗ÷…—}[†q8úß]öQpgvņ˙˙PK!Á Ŗ{¤ word/endnotes.xml´–[oÚ0Įß'í;D~§ÎĨP*­…ŠoĶē}×1`5žČ6žũŽs!Ŧa(¤Ž9öųųėã“<<îEė˜ą\ÉŠnB0IUÆåz†~˙ZŽĻ(°ŽČŒäJ˛:0‹į_ŋ<)“™TŽŲŌĻ…Ļ3´qN§[ēa‚ØÁŠQV­Ü UĢՊS† e2‡QXö´Q”Y ë=š#Õ8ēīGË )ĀŲo1ŨãØžeDWCÆøOģ x"ŒŖ.*š5Á^Ut;Ē:¤ņ0Ō™ā&ÃHq—t7Œ”tIĶa¤N:‰n‚+Í$ Ž”ÄÁ_ŗÆ‚˜÷­XĮßxÎŨ˜á¤Á.ß(¯#A$ŲՄ;,TÆō$k(j†ļFĻĩ˙ččīĨ§•ũhĨ˛Ÿ3†÷ÉxųÍOŦLĪlEļšëŽüđĻÉ"™>U ßÕāų.mĐ겭…Ÿ‹*é¸Ü–ĩöõc<á™pâxąĮOË˙ÎYaBkûvū˙˙PK !ķ7ŧšęięiword/media/image1.png‰PNG  IHDRũ-Lņ0sRGBŽÎé pHYsÄÄ•+iIDATx^íŊ=hcËļ.ĒĩīÚ Ãš ZÁy †sÁØÁVđ7œrpÁÎÚĄÜĀÎŦŦĒ3;ŗ‚ KÁ ŧ2;¸Đ ´‚ČÁ9ŗ˛V(Ŧ`ƒ,xī?UsĖ_Mše/Û=}ö™ĢēTŗfÕ¨QßøjÔß/———‹ûûŲ|žXÜĪfwƒá¸´X”Ęåg–J‹žû°Xl”ÂĪ^žį—@ņÅt °rˆZ”LˆUĻŧX,œÂφw;ĩZíŨģwÕj¯ürqq1›Íîīī˙¸ēÂŋOŽÚ­Ö'ûíō1ééa” ?KšÕÂî“RŠã„)…*‘™“äãŌŦZĮüé×ZĖĮUîGŪ:Üûxņõ›T÷¯ũ“’ô˙øŖwzŽîąŋ߄öãī—ĶĶS(}¯ß?=;Ûkîß/Jsü véØŋĒnĻؐ†Š”¤gĮÊ2|(•6܏ŲaųõaQ‚U …%&û)īO@VJŠŨ5OWŒwõhÕDž‹Ō‚p2N‘sšüķėęĩōŅÁ^¯{ē˜Í<Ž”ĩ Ä\ŒÃđû‡Gz‘„úōļd ęĩŖvģwų ]-—WWG'G­ÃCčũßéĄôģÍũŲũbū@&ƒDĨ6‡ %<]"úUܒM@›žø?ƒ8LņĨáô”DĶ$…Mūš›ÆĀtéwK¨aVxƒŨ¨đ“ĻÆH|Ú39}´ŽN Z UY__W%'0)§O+/Í)‚ 0š‹–ŸÄ˙–<+ą4ԛK ‰‡Mž‹sūĒ-ô9R>}.súMc8Ė]‘•Õũ™ôöŨ„°Đ}WŋBšIžs’œv9‡HŗE Ū;ë †CčüßĻĶ)Ä ¤Ė2#% čEÉĪö)ŧĮžæ†Üį”™Šš&qh—ĘKzÎ?VöÆņōë<õģúuBéŠ2i˜ÆĮģôRDŠ×2‰< Ė“°įĒÜHAU…Pz oũIÆ$%†IiŌĮ'†å×’šâĒ.J‹RAPŽ5ũ€‡†)ĊK1ŧgQŅSÂöŽ—ôú'a÷ĨGUæÔ æsĶû@ũæū>J8™L~ŲŪŪ>:j7öö éA4ũeŲxR•[qËw]pEYĢX¸ųÜRAŪz\Xԍ˙$‡õū­EiTíąœšr)UeĩÄŽ›ŨI~X5>$+“ėp Ô2Ôߕ~¯w?+-4W–°´EycąxĐ<ų?ˇwƒéLbšõĘÁVíōvnbj‡[īĶģN-Bá‡ÅÃFiãĄôPŨÜiĩ.ž&wŦŸŒũÃÁU˙üô—z}s|{;™‘éQuSzÃJõƒ¸BGäRPUtĄÆBĸC4Y(ŗŠE,ü8uˇ]%@Ų8ÍÖã2¯ @V \gļ]ze /¤$2āđ"ZöŌÛō?.ĖĀ'ß\!ė­]­Rní6zŨîÃtbĨ‰˛ú1˜ |Ëūĩ7——ŋKĖÁÁoģõwÃé]sxÜßŨ ęô Ō̞6ÚĄ„đllmtúÃҌŅ_`}ķ]ykkįoB'•AJUĘôgÜÅmI>ƒŪ)<]Ū˛OI“üÜ0ņ`å>åcÃîģŒ:LöĄ¸R ķ3-JOŖSr+ŸNĢQ,ŊÖNKe9Ŋ„Ũˆ…§1Ԕņ@(,혒†Ë¯éCaÎMōŠäKIôģŦĘ.FČĖ|N”ă˜TOčČ=%FžH*á]`üáą(/Ô}8›{Ĩ?<<Ū­^šwīå]úßÃũëwĖí~§_KŒ–)„ĀŽĐ‰¨Č8kŦã&5,99TNŠ‘ŗŠ +<†‰ŖŠŒĻÛ]X˛—v‘†ąáT3 MøėO)?đū°ąsÚíÎ'7B-œB€Ō†^¨žĶ9[ §ēKÃĄ3@é ÷.Ŋŧ뚋ūK]zK‰PŪÜnwÚũŅ­Åûíwå÷[;ŋÔ7ˇooĮ“)õä!=ÂŖáđp¯?ŋä–ņ7ŸĪû—ƒæŪ~rķûž”Ú ĻˇŲžˇ$fcĒ)ŦŽ‹ÆÃ‘:Žąã9™Ë÷ ÔÎ}@ܯŌĀápãH§DųÒōqOAz×+‚ĩw僭-pčŲõČõ>.—"gĐ!-ƒ“”4—÷‹álqŅ?•ŠļZíFJ¯\#čŌÜcüā˜ å ctƒ‡Ú‡FĢŨžßz~wë•ŌÎNÃáũd´kÔāęōķ |‡:îVŸ¨ŒĘK˜÷úúõëõõ5FÆŊūÅnķ đ0”ĻŖŖ­ßŽBĨyņũ|ˇ&ôŪũnpظ<õšī¨Đņ0ģ>zčōi^~?oT“¤é“:@ēę[kã;y´ܞm}ėœ|ģ?ÚZŲ"9„Žu€ņŲû–ČūīˇģĩĮ"ˇ›`NĄsA8:ÃĀĒ9éÕm’äīŅu68Ū:tGŗŖm¯žËēÍäŧēķĨ;ū~´éēJ0âÛ´V.īÖĢŊNg6@ų„ČSū„åk|ܲ•Jƒrm\Žu„Duļŗũf‹'Äw¨;†ŗŒúú„ęךÍvˇ{9År„€ß Ūŋ§ÜÔíđ{ΔT?U*ûD$”NPęõē2NîŧĘPKõFīūûũÃ÷o貧_gŗû‡ķĻ*Ŋq%ÛņûÎŨ(N š—ŗī3Ęjpđū8]ĩÛøNâl?Å{†æŌ¨Ėō,Múĩ‹[o|ŸŨíHŧĒ~rXßõųxcF}(ŲĮÎįo$“ÛÃĢ̉û¸ą ĸ R; {īkŊ‰¸Ũũ܂z(ĘyKL˜ĐW0Ø<Ų@ ŽĻ.˙œO.Üēœâũ˛đæņũ˙7cĨGAuöCōTíąœ4G|5j X—Lœ›˙A˙žGÃVéEŅnË5ÄĶ+ŪõÉšqū”æa.ŽO6â#â0hšS%ÕmQéŋÉkL„wjØ—†´z‡žœŠ(JicSr3)qeŸP&Š*ĩ0c<Bɰ s%ŽīI˛O#JĀŊ‰MäÖūÅ~i4qŧĮøUÃ"|4¤ŦŌn=šp~t‘vi\z GÕaŸMĨnc‡ŗ¯5Ī[› ü[?ފB) šQôdĻ™b„Ō¨c,ÂãžęFiĮS*M.،ŠŌ¸”¨EΰŧĨmęÂa‚'%9°ÉbíEÂî!ŌĻ>xÅHßíž gëÉIWā1ˆŋ*×}ëØ–bĄÜ… ú‰fj#¨†‹;ŧ—lüdu ‹hŧ(„vigTOŧ˛ˆÆMS´”‚Ģ'Ln>jŊ¯nŧ¯mÔĒíeËIn†GU‰o fnÆT”ÆKQ0ĪíZĢß;|_ĢöĮĨiŸßåúSūā´_ŨøŖīãĪĻŌåī‡Į’˛ļq<ŧ'ŦAyZg}|ŊļąUk|)•ž|Ŧžo HY;Ũs‹! ũŠ4‡7Sžík RrĢ:‹áÃbmüS:mgīp0w"Q,—Ú•n`j¸.=‘Ã{¤D¸Ņ.•:{$ĢyŠĘGrŖ_[üûäpx9T)ž,!ė…¯g=ęs^\îé Á§ĮÃ/û_/ēÚĖ%˜ ~÷—÷ÕŪ-§šíũōžßsņ,C”ĄĩŅNĩ¸čҰk/ōjUĐįđÄ:\r⍘)Į˙¤0ĮāÂHƒ0p*H‘îtÎ@ođD1ˆĮ¯”RŪĨ'>NOΓ\~Ä7đE~’úT*ŦÔ å–×x/ ¨F„+$ÚÎīmn.vvÆŦZĨ°žz}ļŊnļRáÉ\ÎØI?ķ2`PšTi\€đ€BŒ/÷¯ ¤ŒƒÎčqŖ‹ũÁaûëL8ĢX€?ôy˜Žûûu*Áĸtõe|8šŨˇjƒVŖMüaZ5úÜŲ9ÍÃ:ÅWúÖčsŠķąwS*ßôˇļ%åíeéšOƒ¯ß܏ĸ”ĨĪßîŋ÷›Š;~žíí뀧ŨÎîGĨÃöāî~øÛĮáīˇƒ”ĸŨŪ2¤ĸ~ŠöĄ÷ũ÷ōÁNuŖĩÂK;ûŋ7Û¤‡OF_š—T—ŅÅ-JōđũĸYŠ}%vQ‹ĶFŊāpgD4iöũĸtĐ@Ä@­ŅėákˇÔiüRûXBQŋu¯˙¸FņŲķύŒOžÛl5¯ú“bĻÃ^ģy°ųŽ+ mūzČŦũžčč×nûcKŠU*ĩÛ#pŗQˇÔ>K‚˙ÄKFßõ–‡~VcĀō¤­Ž0i+„ŗQŽR—Ä¯Ē¸Ä˜%LĪjuĢDЧ;)}iÖ,ΎJ÷s$1÷šŠˆ Ģ’e.>gnz°ēĶw pšPŌF^Ã=Ū{ŸŊC}Á{t5ĸ`‹‡éôa2!uĮģ;;ÜųKs|ãZíŋg’#;Ž‚Xāũ°ÅˆģCFVDƒaŪeˇYƒ@ëÍÎįŌÕŨ\ÜBôŽ ‚‡ī Ik/öę’įūīŨ&훋Áū×Ŋ-l\îú7(R|lR>Û GãŲŽāœņãš&ųëM|ÖSí›ūōœ\c$ÚŲcŧo`|=˜ŪWwŋm]m+[¤_`(Ė–Ä<{ú”mˇ—>npįŦí´ö(mi2l7[ øĪmëbĢā–ēŗˇBä0RAl h ?˜Î…f|>څ6?đ8І$åę&ę{O¸.2uá|ĩŨt‰Ģ›ûÉÅÕéIŗNī#Íl2Ā k/í|úJŨƒ,:ŨŒlŖôáC—žˆÉHC…åķķ{V,÷­VĘpjk˛Ũx"ŽúŽVPš'iib*Õ ŗ_+wĒŗnõĪ}2 •ũę‚bjC$éų-JOa|īV+œĨŠ0jŽīRÄDâü>@}éđŒč÷ĶéüūĢôįĶébss Jw‡Î@ŋ ũĸ”‚ôn (/Ë/ŒÜßä|k¯tųx6#$“p“°5 ´ žfĮÔüp‰•híH>Ԝ]ˆ€ ŌČ8A‰™æŖ_áŽHØIxOč~ßÚŌ” ´\ KyЁ˜ĐPjPZTwĪŋ•ŅŪˇ†wŽß—ˇŽ\I‰įQŨaŒH’ëģŲĸú­Vę‡ÍÁÅx<čuö÷7+ô+å\úå™á­Ãbĩh‰šîoÕqũHĀ5•ßŊËŋRę:ôˇ3:˙coĐŨ­Ë M)!)Ä×}Ûš2˜ö‚ל[Šë˛*Ŧ_ˆŽTA3 đ %DBčIá˛>™ĸT€ņB~8 .䈤pړķaJŖīJū[œ'H:6ĸ1 #DpėQßķ{5LTrî‚÷Õ*t”DT6›O&‹Éڎđ=úÍp8ÁÉ<ĨW}–´ÆHw{˜M'%40yuÆC0WūW{Ã)YƒųčôKŠģ /Jx1°v.{ŦYčœeĒCFŸ }3<4ā§ĸŒŋôFwôŨÉåáUŗĩ]Ųj€đėá‘īRŲ<įķaÁ{g¨Ę¤(_`&:nŅÉĻōb̞­ŊY ĻôƒnīĶķ§Ęd ˆÖQö“ëvi›čZŊyÔŊúí#œ‰m¸5EJĨ­Ŗīˇ„¸SÅŠõvųØÃŧ` YquâÆ[m ĒŖ÷ö0ŪSö;­‹ũö—Žĸ;Ģ{ųĄļy¸uØΚEσƒÁ~k§Fú ˛âZ;š‘ėaĀû˙˜”✭õÆx̎ņžwãŊp;Û&Ķieg§ZĢ3•Ņ ‰ßŅ9…+EeŠ'œäÖ0lBbXĐšÁJTčÉyō 9“=ŲĀkĩĘæʀ’°âP[ŗ€UĪÕŦyÔÁˇ€@Ŋū—=QD‰(ˆåWĐ{āĮíĩ+}ßb”ÕĄü§ß&īˇü ʍ cÔØ`ĪöūÅí a$Ī 3–č€˜„ÄSRnIŦĒnžëž˙øūŊěwk‹Å‚ÍEŋQÛŖČîč;ŧūĨÚáˇî—÷âCöĪNĒÆ> *{û Ú⺜=šG—Ÿ;ĢāH’˙íÁėˇęë<h°JČŧ¯yęLp€1['—CPÉåôrÖxĮų3?iū°MßÂ`š˜äüđŠŒ1ĀûŊZ›Ļ>ŽÆŊ ŦųöĐ €Ú=‰c8Î#–“ÄĮ_@ą_ZnÂJ8gâÆũQˇÚØŅy’Ķo÷ģhZņ r>Ū2(ŋWŒМU3ûņEāã§ŗŪņnãápgģ&Û)V€žŽ¯kĄU[z$UV˜*™ۃJSĪ%օõŖ›ŅÕÅÕ9-ÎQ¤ĀkąˆĖ×Lh8¸Ä´•Hté歚‡ŅųZGŦ\ų•¯Ë,Ŗēáb[=¤žvz+2Õås3’ĄŽââ†?g§ÍØŠÄ‹!c‘Æ0žyöTÍ`< Îøō–Œ$žB˛ÚÔŦĻ §7ĻVSė긡õąt‰Š#×ĘÜģ#úā_°ņNŲtėÄepáôé§æeåc2››ŽĖõjīãäXXtŅnMno vVw+ÜÆG—+8{%iė¯ū-‘•ä8Q¸ŦõJyskûā´ĩĮ4ŒöTcĄJi+˜¯ĩës8ģÁ`°]¯Č´”se˛ÉVæ„ą‚:›7váÜĐŪ_°mI§v/*bÃ9•/I5‘ãäĸր7įh'čÆNMy ŦãģÖ^–nR;ČD‚´Ųˇø›øbi—Ŧ6•ŠniņtÔÚúc˙ö|6S1Ë#w'E>CĨØdĮ×ųHķ{ęĘ,ąÛäéZÉi2 ÉŠ— -Ôq Gŧ@>’zÄøPĩކŅå •ˆ.%¯ĪáDŒ`‹Åpø•,WĒMÁĻģ@ŠB›BFJ€€ų´ææØÆg‡cˆ›ŽĮ4/_ūöp¸•€ĶQK„Qšy pXItō2é¸n$ŠžŲ{ā”~|ļĩׁ7iˇŠ"˜ˆFūl§ZÖĖŌđ+[ŒåĒĪ^¸%ąVÅĸ;ŠčĐWËī•Ûog‰¨ģ;S”°ē›ĢgįˆÂOOʓ&­g' A 6p‡ ķEĪ• Ūhk)‡Ž—ã֒YÂx`Ŋ >ÂYsLŠER¤\†î"4™„dg噉îvœŖ#„ö‡ÛÔcHORüņyô-ž&ā÷)x¯üŪĸÅãŪZŒlLãÄ!LŠ žÁéī—†Š!Ŋm?ĩ6‹úIĀ–ä‘ÖāQ(įčļ-Bv >Káî”CJŨđä÷”?#[kiŖR ˇWōč%ÎõŧO§ˇõ]U'Ų/ĮĒ‘Íī-Z<އé[!#Ļ<ĩ—§Ą‚kE)k|%6Z€íŽåģäõ͛ŠUãqë‘Ö ΘaÅ4ÚFÆB†dîËlĘO ŸŗîizīQ?qÜÅē™ŒņfD§2L#ĨŲC[ßUu’ßåv|~Ÿ€X1ÆŋkĀâö¨œfĮ ŗ§´Xážiü8Õ¤pk›Īēžî ČGwŖĶ!Ė–ø˜|\´Ļ“3Lž›†ôfD§Krs'+ũ[Ë œÁ104Îôįß ļ=žßãu93đžįJ˯ë~ĸ$OT&ã<7üá‰xĻXū“ô’fŲß<%Œî~B-Ÿ'ę˙"ņÚ œFŪÅ31 ~ĘĘgY™ķūîä@'āĨ„ĨÕD’T~ĢĖ×ŨšQ ‰é’vÜ2ā÷nß­Xđ}'<ģŦĮ㘞Yũũtz,Ũ!g#…žHô7˙ÆÛzÜv˜Lũ<éž÷Nģt߉ėvë1ũyi:ėÉ^lÖ*­ãv˙´“lzbF Z/§2Œ>÷ŸŽE>lãCaĩŌbßĘžĖë ËãyŽ+^*ģž24šYņĻÉüŠÛ^ÆĶédcG*SÛ-}uNĄ÷˛ū^Š'ëīÍú"9˙Ŋņč[īž ?<Ü鑝Üąö?O$"9{žâ ƒ$Å ÷Q"Ą ˜Ž‹7¸Hų}×5á*8%ã|Tá8įŋr,ŪŽÜŒ­âŒ{0´´q/‡ņ]°×YK’'œP~ë ‰­^Ôôš&ÚžVž"ՐlüM ÆĶŦØžĢč5ųƒ\ŪļõÅy+xOO§iŪÛž §#îvšˇxĖ…O””iiR-†Å{g‘¸2ÜQWG}­]¨^ĸdqŒ3ČåVbuô]ÎSķåiĘš?IŠWŠUˇČ…čĻ­S“Ú"œæŅēau-EŽ\+˜ƒ•8ucĢk<–aŧđ{‹ŦIaô9šÕĐ÷{ųŒÄŦNĩ&˙Ĩi,2Y4Š#S6’Ų_CáØęOH ×d´ļˆBßËCiō ÔB6Áāt<Ū–3­S<îydHōLCčíĩĒnØô-´ÎãŊ´l†E’Ž'4ß§ î¯U]Î۔ÕQnīĩGz}ãūō‡õĩ°Rd“ôZų5) Î˛4,‡ĐŽ"ûn€sŅ1GŖž^e-ĘĻ…íX"„Ę9P?4ˆ2l7p‹É|âõŌļö3ŦaYå‘aĸü­,iĮGéŊÄeļx/%Iļ6ĖīõSôė ¸ŋ–ķ ­ŋį˜Ôž„|ä‚Åē›ú˜|œKž™FQ„Ķذŧ´Âøa•ô^Œ4‡‡‡’']¨áōDʈo$(ƒE2kyÂå‘VĶö…ŗëbߍæ“GžiiÖĒV—ī‰ęd"Ŋo;z×!ŊÛļÜW.ÍL=3āĻiŧĐÅüž›Į˙­7lŅ"!l( iŌQ*ĒŲ49Ó î›â m ÂĨŌÅŅJ˜ gNôJEäīĶ‹Ōû– ĮKžÜ֚ŋ §•?9}yĻĨy:}xŋ÷“•,‹÷Šļh—Ÿž‚ß۞ę噸’Šîö­d–Y€„4žSōģĀ~¤Áš)րÕWŅíqØjílnļZ‡įˇ6ņīJ;Jŧ §åMŸ&ˇÜ­ÖŽ?ŋ:ŋ'ĸP}vx/č Đ2¤›ĀæÆ{o‚lnë ģų6B;›2˙܂ŧ•5Ŗrsīj'IBe˛¸Ĩ|2™áB`ƒÜŨnˇŲÜŨÜļNOéRE?ŪČ.OZlüjá4šeKŪhŅēÚ=šĪęüŪZşŖ-ĢüŪõBF^ĨņwJ1€ÂĖīÕ7ŧ&Ÿ $%÷ã  ›híåšŪŧ`Ļ"i‚Dá4Īr0epa/Q2aí>ėd÷ipđ.ŽĮ+—Æp8’Ø<Ĩ‚ē‡ÂieKĢKZ>VrĢÉųyt ĖīŖ:™ŽˇŦ-ŠũŒ÷Fz$dÆīfŅĢW'öüŪLHI#ú&{ōpÚ÷lĖt‚öx-Ÿy9œ&ø!_ŧŠsxîÂĢŠZĪã}WáGųģŸÍŸíÍM˜‚@vĄ<íˇŌž›'>-ŸxÛqģ'´ësˇĩk5ž7WįkŗôĶÉÜTÃhīŠøîĖrüä| Ë{’á÷^iøû´ņé¨oĐδ\:j‰By&!+’šŸ€Äx‰šģÉĶũÃÆû0nŲžš™Ėpą;ŨJzŋŨŗĘđõĩȝGļ΃ôÔũ”ßŲđ–0ÎD<ęŗ>“ ¤úôįįđÍ5ŽW§ {õ ŋ§&N@„4¤XcŧaáqNiŠ._*Ō´õ ĨˇÖ Í2h EõũŸŸëĀ .‰÷ivww///ą:r<ˇéjōtK•Ŗ ŠåˇåIޝ‹Ĩ¯¸/™°-ÖķļģĖŽĮü÷,ídŊEú˜ļū{šTP˙đû„šöMÆĻ<ļÆöt¤Rå´^==ŗd—zŊ^ ^›ÃCÁ*ųët:ŖŅ¨ąŊŨnˇŅüĮr}+E$ļ.ŠuL‘`*ŠÛ­a†Æw˙ Æmú~/27ōtüžw›ģ=)¯Šß[d ‡ ļĖŲßãØ`1•°ih“„‚ē |8ŒL%xŒbáÎGاA‹ĸ?Œ'üÔl6ũ‡WŸ˜2[e×°EņX}Mš°įL~X&˧OÃxŋŋJũ÷¨ŠŪ+$7Ô园¤ŧĖ|­ol}ž4ō•4ŧŗę'M‚úDĸōÔjy.ų˜rž|ŌĶ䊯•^žš=Sšd~oŧ^(és0w¸ Ha•Đŧ÷—9ŌPŒ|}ûŗ‰ß‹Ÿ›ŦõĒ`ø—ĻwŽÜÕ(­Žš‘0Īįō Ēåæú ŋ—ÃËnĮ8ŋĪĐOŽĄō{‰ cCx¯—ņJRīŋĖLšųŖŪŧ/uĖmHū‡Čiˆ”­3Ī`/Ž¸4iuY%uĻũ‘ōX ŧĖ6IEĘææku}NĻ~¯Ž"}*Ū r‰˙~YO Öc’zZäxKa‘ūŖ`d͓fÕōŧĨļ`x%~Īk–ę§÷ß Ōëåíŧ'u¯Žģ;éĨ'ë1}ŋŒŖČkIDÚų‘2naâīæI“QžDĢûÚå.ŋō{ÖŊĨúÉlAųŊ(}īÍÍķn­ŸÂ\ę'đû7†.ųė˜zåĶęž*BÛôą<ŗžåGYoˇĮīŊŌ‡ø=ÉYNTŧ§ÉĄY.+—  üž^ –Ŋũ$aĩMĢ;ŖŽáŠ9šcy.ų֛—2ŋOÖOo*<Ō‡đ^]†ŗⰌ|õ“Ã~ž6ĖīƒeoÔYė)²Æ3AŅørr“ņË!™ßgęĒaöĸĐÆŸÃh.gJ…Ŋ7Y=)XÉ¯ũ$čîõ=ĸžihũŗŖxnũ‰ņûL¤ķ{ŸŌÖõ˜Ü›˜Ūp×â°ôŒŦžäųŊē2’Yô*Â"ÄBk‘Bš¯uûk—˛ŋąZĩڜ‡LĒ/~zJš2ŋˇˆU„ÅúrXŋxoŸÃūÖãĖą(ŖxâúA#Az†ėĮķûÕւj~ PČ3*Ī•ųŊâŊđ—8Ū+ŌŗãA¸ūŌždö[i‡*NĮ9Ō¯éYĮÁīī­>ëīÁ×AsÁī‹ąĘ‹“<ŽßËxU ׋Îŋ˙~;ž›/0í‹õJ‹ZĨ|øÛqŋÛņĻÁsÅX“q{{‹•âr>­n.ßŗ{âú,žÕŲckŠ`!D ĀTbSN•owHШR į!cöÖ֖=M?Ô=ퟟΕä“úVJĨ­ŨwōũûíônAsZ¤úÄspīC¯Kû}˛˙0Ļž›Nˇ ¨ĩ Č  ôtZ†“_–mņûĪ,`čl6ƒęcä×(šSģs T2´ÍūÃ}'ũĶ.ÚšwåŌû­÷ׂæÂ{ÕûŒž$¨Ÿ÷PúwīŪÕjĩBī—5PņģJĐ ŊŋģģŖŗ†ÂÜA7ī—ęįI÷ô‚ņŪī-ŧ×ķ1AoŖŦū<ˆâ}Ø#†# ésōûHuŗŊ™K}‰„ oáNSŒ„đßŪ~úôéāā@ž Ĩ‡uooƒoüáđ=Ô!?á-9šØū]]]--ÃĘ XŧđX ŦÂī3t•˜‹õˆJë›ûNüíĘī—ö¤`Ī‹°.靡žŽŗįúĀæ?ūø.XüÁ‹TˆÁ¯‚ÜR2Ái(ą×E;pņÛņÂPôÉx,ŋâ˜ÕŖŖ#ôá‹ûŸ>ŸŸËOø'ÜūÄđŲ”ģÅhäąZēū÷"ē¤ĐœĖī3tÕ-ĩLå÷b täûx~bfaix Ɨ s8 Œ3’Šn‚wȍ"8ũũûw(.Ô×)k‚p# õũĐhHēŅpčą?ō&ŪÂąM_ž|ņņgggøV÷ë×ßGåč•ŪŖ~(Æųs°’īnŧw쁄å%]‰ß[¤×p ŪŖ{}ūü5ûP-Ö‡ƒ}>8 < $uEÅō øũÖ¸JßPĖ$ŠßÂÄÍģŨŨ!OŽÕ` ŧ”–Ž˙Ĩ]z4ŋįŌ,Ÿ-†úīũzR/u ­Ėī-Ōk8 ī“ĩīˆģV"™Zģ9.•@éÕ[˜ėŋ_ÂīĐäæ5üŪîˇ"Õ+zB~oĩˇ-ւåķHõüOņ>IT,ŽYMÃ{Äã,žá`ĀgOˇˇˇŠŲ†Ĩ ųL ųŊ´QĖŋ„ß{ŧwkņƒû­˜ŲËÎrķ{{-¯Ũ§Qo؊c§å÷^x^Ņ@<ü@sˇŲė÷ûHssss||,‰u2ĄAĨĀāÅKƒ?Œ¤}Vū­ƒÃC $Āõĩkx˙LZŊä3ĒôF—T¯īŨ}†9ų=ƒ<1Š÷nÍŊ#9žXvéO4ėækE)áÉá§ G*įŅԒ †‚b*#ŅĶĶSø×ßŋ¯A#ũ Šiü>M„čH_ŋ~…ŋRGF›!˛v7äOŽžũ}×ĩ ŧŠoŊ7V¯H͏€nQZ–~ŠĻk‹†ũ9ēSj2ŊĄ˙kũÖ>û|„ŧUŧ¯+–q!‚ĒĒŽY)ĸ"7™…V’< äÚ6ē$aqIƒĀcŦĮLŅO, ;9ë_œĶzL?lE>Xžŋ֝”FG‰ØEž)aŗt!Xî įûåJĩ-đˆč’(}ĀCrč'ë°ãƐūœÉl>›ĪųšĀ}ŦĀŧūÁŦ‚ˆ‡åŌfFzzúŪ騋Jķ¨ \H MĐ`Ŋō°^pûĢ<—é'ëpę<›Ķ˙$ 9đũV€wDŨ/îé9ts˜Øh¯JÛ3 %áŦ…‡ņ¨\Č!ĄCÅŗ@ĸŧļø×ĸzåV(×ČÔOÖaŌ7h<ūs?WđXŋŋũ~;ēšķ‹ļjå“vĮņ{ۃĨü)ŪŖkbøčUšĀ°BO*Ą:đûa’‘U?K?…ߟvæe•Îö;Īī™:éŪFjÃīŗP?Xē 4{=´ŠÅúMаĨÃr9¸õÂ!NęĢ?'XšĖ׊¯č9)=īēZß{f/cJaiÆBk”ƒēËu¯_öøSøŊ[sī°=˜¯ugæČ/ØČž ŋwÃA,3Ę.Š^…LÖ§â2aE[>ū~īWæđ<€™¯uH/¨ŧ§_3=9âíá=/ڟĒ^ …ņáB>ëĶâvŋUĻŽzŋPčüöß;~Oƒ8îWä÷Jl–Ŧ—x‡ōxŠ4ŲļŸC>â)”‰ÔĨ¨oøŊ cãüžņ~U~Y‡#ģ•Đa!ø…Ö%Ļ&¤ĘŠ÷ËYÉR~/ĢkVå÷~l .Į*Î"ōūBVĢ댠5é؊üžXIœß ŗ×ž´ŋwûÛ ūē>ūZŒ‘Ä+˜"ņ:.GzŖú•ÅÔɜoGö×*ŌëjĘUųŊ›ŪÍɡōp˛"M1JÕҎUųŊ[yã÷sâū$ømYT<ėÎĪ1+y¤ĪBO&aŦš™­ŸŠ÷2ÄOįŧ´xīFģrvû  ‹JËų9fLí–+ËrĀŠ°Ęŗ‰čÕČAUS)dë§ŦĮ´Û•Âū{7Fö#ū{îIŧ2'3ėÖHäMŸ'Ī"MļĖnų°†ę~ĢåúĖ× č+ęķų÷ˇãņ;ÎNŊrÔÆyČzĒ.ũá׊°ŠAL/dÂāø‚uį!÷NO's]ƒŧ¯WJ;z˛97T›ú„å÷yzU‘ϰx/SČī׏y~īņ~î7 îÔßĩī_zo~ÉHS”í%čāũtŽ­Æ{ˇDÍ6͇‰ôeöāY‹vɯnž–=0QŧNî(šíAÁī_8g-,I~Ká÷ĐđMËīU”âĪ)øũ2/Va _ĮÉáŊ¨¸.>vūÅ{ūéQüžŨØŸKRžöŌÅE¸ÉÚtŊņsRŪķūÚÛ[Ņ{Ė„•Ë›ĩ Ž#î~>"ß<īŦĨyŲô°Ė™á´škŽø+$đDĀfnœČ„Ėåŧ´<úŲÁūÚnw†ŗØĢBå’ũĩ˙ĨZû—Ŗ˙ņ?Ļ˙ī?õë¯Įķ˙øįō˙úĪÁ˙ŋ>RĒŋ#æáīį7’Âø<A˙ú¯˙Jk9įķ?KĨüƒ˛úķ˙(…ÖĢVĀĢa­3ĩËôķ?˙÷˙ūīÍ˙¸ģWĨÁaTũū˙­û­d̓,dX‰ßCéåφ9 Á“/p–#xŠp!‡õętL˜ųÜķŽÁ˜ßoŪ3Ŗ‘]WÁúBtáæ Qô_&6ē !=ŒŠĄĸ…Ųë‰VEØoę,dBŠĩ6Ũ€žá„@ęNųô3Ø_ ՏîˇrH¯+Ũ¨O°˙Ū­‚Č Īqg“ĶļîUô‰"\Čaí:Ā', u9õ3XŸ~ĪxnđŪ!=ĩd ø~Ģ`Ŋ›C}F„ã9šŋĶJöÂ8„+Âzfh!ļ~kŅYEÆüž(y’NēušĒĢŦé<¨΍īÎS OŽøs”æë|í˛^%œÉc|ô…•{Zpįą Ŋ'¯Î2VÂ;É™ß õû­˜ß—qœĨōû=éAü¨Ē­Éօˆo;Ú +xOpa¤—_yũ=¯š~/ōŅû­Üēdęđįäå÷Ž÷ØS‘ ~_ŒmžN„ß“Î3בUdY¨/į;ÉAšQŽŒs‰9I§(ø}”ân +O\›…?eļîéŪܡEWŽ>éˇ^‘Å~4ŋ—mBuBüž×xTđû(OÅŧ¸fĪoßžė˙öÛĶĄZÄbČU_…wHHŽâũJü^ D:ŋ'Į=m ~Ÿ†‚˜7i4vĮˇˇâš‚ĀeҏaĖĸĩpĩ Âb €Ü¸‚VWwy+_OŽŽÂ)K’ī"OÉYPŸŸô-Î?ōÖ÷v5›ģHĶÜŨ•ËߟΠũå–áüŪ÷Ęô>Äī՟CŖā÷KæîGŖQŖą#¨ ŨžÍf¸=a\zŽ=l0 ;;;{„†jÂJā‚:š!]R6vw‘ˇįzŸŽ-Å?aU#Æ"=ŪÂEÖáˇĘ¸¤ËŠŲévDīß°exŋ—ã˜Å{Žķäūû ¤ôü~w÷#Pųô”.|fŗK—ĒK×Ŧãvđ„ ztHÃJāÖDtAnüŠn€V” ŲũwŅm2ĘāŪ*›ˇJ˜/Gļ0°B¸ōmΟ<ŽßûEœa~Īž{ƉD~¯{[xÔ Ëų§đß{~?Ž ^˛.ŠđžT’JßßëÉøŠč%#KJĒÕ Xąø6aŗĻŗéRR<ū™ÖúŋæķûĮ+‡‡t*÷̎<‡Äī3ô“ÎKãŖŽ?‡yŽķß3Ū3ņqū}ËīifĀÍÔFŽ-)§Tß*{Xš°ø¤žĐlBžH§k(]ŧp}Üŧ^¯Ŗ‡hJ\U9™NН&77ÆgĘ yŌ[7üքßZ,`ˆ:6z#Ü>v[ņūĘ?ßgč§Üg(¤ž]™ üž‰ú÷˙ž{’xIé#aĻNÂq ĨĖŨ†?OØÖ}w—ŽY‡Pđņ2Á?Ąú\]Š7w›<$(ĄÛN€ņN†č!Ķą|ÅoŽĮȕŪ*Ņ[6ŋU.ˇŽ ô0!´@ėn aį ßãKôĶ“zá÷ęĨāŊøī ē˙>ģ'aļŒûpÜãi~š§­;\4€a¸Vá{9lÕë?~†'''"™Ŗ“ v‘[|>|ø—X¯×oĩŽČso$‰ˇ&î­m÷ÖîŪîiˇģŊŨ@V4ęxĶ’WëJ:L{¯R™H°–Œô9ÎīuŋÕāfę&ĩ6ļëåv§{Ęû­xW‹lēJÃāÂC‡.(Čaŋ_Ŧ&Ķa?uĢWy~¯>ÕP TĐqîB>?Šúq~ŋõyŧęIŽN]™õ˜‚ônҚŲo•Õ̜7TŊøkíŲRĒOŦ°F¯âķĩKPšėįkƒU™ŧūžā÷˙~-éqüžÜ÷ĸô2Œ ņ{í‚ßģYˆe_¤õ~ŋOÅ{î֟SđûÂãg`^֘äüŪ­/Žá=ŗsaö„÷2G˜íÉQŋ~Áī Ÿ§ ųX~}ö‡šēųÚŋ—ĩ"šũ÷d<‘Ÿ`ƒ…÷,EVÆf'1! ÜĪfØ4š‰ĩ,ūŧ ėĸ„âųąĸÕ"$ÃöėcÆ}%Žßgi)î;9;=ŊÁ?)öî}Ā&æ„õ÷›õ2öįķú{^y/}%9Œ=˙ŗčú{^ĨŠ….žĮ™Ėîīؕ[ÜīđĀėBVՏéO0Q–õ÷5Uå,ũ,Éúû)]’ĸ:Üāõ÷„÷ØøC÷ü°9@&ۛĩUđ~‚ÍuŪ¯dŲ ô8Ä;' ˜K’„ŪãRŲbA}‡÷C‘^6Á¯'ĶŗsāŊ~ĮãŊŽŋW~īÎĪ´ū{Yī&\*öū{Ëī¤÷…Ö=¸ī˙ÉĶ@\rÃĪüÍÆ.xė’ēá„ÔŨčOœßgč'ųīŋWP×Įr2¸˙žéúīÃōnāĪņĻ\×x:F[Ä 89ėrZ‚€3ŪúB’ãĒųĒoôĮé-\ǟr֋õ߇ü9ԇéÅĢŖës˛{ΏyĮŅ?>5û™‘uĮąŠP3č% á¸4RečôĶīˇJd"ÁŧųīEŸ•ß‹†‡×ß+ęËúˇÜjũá0Ŋõ߃ÚõŨM0q™ŧ=ŊĮYn8.zŧĩõūˇß~“!_âÎ}@<28~ 4Zøå ÅØ?eöqĻķßGt2ĸˇėŋWYÂúœ$~/Ž~aöÉa9,Á¯6_ĩŦ˜ÛŪÜäƒõčšËgËų^6ÍĪ~{z˙Įāā IGŖ1=ÅQk$›ĪīĐ= ô88΄gŗ9â“T?ØŊíWb*/rūû ũ¤å9üžé•į÷ŧTAÖc ē ËO §đ{vb˛k˙†Oö~û†3ņp|Šá¸š†ÍŲĪ~{z˙ûīŋãÄOÔ ÎŲΟ?ĮŦB0č$ÃE įÁ]ƒœ „ŗD5>,”DŨP~ĪJŗT?Cüž3­Į ø}°ū>ģ'ŋŸŒ)=2ÅĢčŲ¸]Ŧ AĒ#ŗæMō€ ō#âÆ$Åū~ö™\^^HĘV‹âëĩoOī}Đ.8Ō‡a%˛v؁ÃVËūŠyOčCÄ>„ųŊŅ+ˇĘËÍ×.a%Äė…߇įk-ŋןÜúœ,¤ @xâ÷ÉȍĶR¯¯¯qpļô` ͇e7ƙ˜ƒߎņëˆĪ–įí-™KüŠI4>įz‚Y…ëëáĘũūÅĢļoUīAZĐj}´k¯—XGØ `ä'ÄĐŠæ/Ėī^y~īũ9™Ŧ$Äīãx¯;­Būûâ÷@bĪīųˆ<œ!|.=ĐaŲ¨ã§OûŖīāÔČá`ˆ_‡ŖŅŪŪž Å@ŋonF° '€ĻZ•4kķ"ãßĒŪŖ™Āppž!<^G4+FzpÛÛ^0ÎĩE|ÄĄéĮŠÖĒ Yĸœ×ÉīŨĄąëå÷0d`~ēâĸTēįe?Đæ*¨Å~ŗ9ā `>>ÁUE č ĒxA‡ĒÎa @–Æãˇ1xĢz/üûœČsHŋK%āNũG‚ĢĢ+„ŅĻí6'jūÖËī%ãd~/^üĀīn‰đ^ŧiÃQ~ov]ŲŅ7*æīđ@ÍüŲđāî2ō¨Öj83'ũōĄ‡ =uĖh ĻZ­!=~mˇ;S Cããcéņ‚¯7üöôžKšmĖ4~_Ą  ōhwZ CHßA1Ÿ?wcJŸĐžŠ÷Ŧ^2ūŒčdDW=ŋ—Ėõ€äĀ/ūœˆ˙žuTz‰xu"á0ŋ×åhHÃĨōķ¸ô=8­PC°–JĀ~¨8"ņŌÃ&ôû=0Ļ= īíQ>Ĩ^äë 8C!+˜3~-ņ!`{}^\C~æՑģč˙ÎĪĪ‘ƒ:`YŊVC1‘”ÂīU—LûĒđu2ĸĢ–ß‹öJî|LAú0ŋĪîIȁ7đßSމõ!FeŠĮđž­vû=–LŽ}<–4Đr¸vpå…wwæķãé]ŒũŅm0!€Ķމßsz™đķųŋļ0Wėmũž~ũ ~A*ô8{!Úq{û: N3G8Qm*šļT?Ãū{I¯Zęü9Ŧô‚÷æūÚT¤—^åz¤ôËPīŧ™L­}üÖÖÖÅÅ%rŠāŽ<–4rXļŋĮ·‘3fÁ1p5ėî6$=lE$û­~[ ˙˜Ú Ás0~Ã3­‡¤ˇoāELc"ĄxRLԈdŧ‡Ō¯ƒß‡PßZ€"ė­Ķc4åÍŊƒáoŗšŸ¸A˜l#¨¯H˙„üŪ-į§^’ņ{ˇEéé^īÍM<19 HlŌ“BxËđ„Áú2–¯ŧ9 _šBÂa”)„õÁÅķĩú)SĢ~}N2ŪGų=—ÖīˇJ ‡ø=§—Ū âNˇávo~ÚpЃMzaģÚŗ‚°T Đņô)?sˆ Žž }@§pü^A9UWƒõ9yų=)`ŌKŧĖė†XūO€Ök´Nhc´=ąV‹ģA†` ×ĶtÉŦČÖOņŪĐú{^ǐŒ÷1~ŋŦ'1(ųž÷ķ õĪf^T}Å ÁĨĪf%ęŋw3bŠū{ōę0ŠįéIiü~ˆøSņūBn‰œŪęĀcųŊtŪt˙=š2å|Ėe=)ęŋ˙ xų‹B>é$?Õ¸Č×wu~Ī3Šųü÷ŋ˙šŧL¯ÆÚ<Žß3ÕI˜¯-øũΌ ¯ĨîŋOöīã,OČë÷ļ=ŋ'Vä¸~$œæŋ˙iyįkAÍ×^NËī3ô“ˇFÉų˜Ģķ{ΊØĒ I…˙ūÕpâ׏ô%w§T6ÆtR}ö6^ü÷ųųũ’ž¤kâtæLg‡xX–™×B땃;‡oŠ~˛ˇž÷׎äŋĪ@z?.–%DÂ)(ÂÁØŖÉĶč†j]ēG,€ŽĪYÁø;én ŋˇÅ„YéŨž§Õ֋j…<ŗäŦĪaÔO×Õ~īÎCžšķëīõ<äĪ'zž‚_ĨãĪw1ø$Vžaķ,¯.äQŽōH&TEXö%Ékp‚@qū=÷îĐčCøđ A,°tĩäø]UĶLrÖŖķī§ŋu3˙=žūü{AGaÂĐtė¯ņįßĮëPÄ$Jë ‹ķīĶtÞŸØ+褙Ņį•ˆ[%C?ņzįŦĮįß §ŋđų÷7wŌg úäüû|x5ÆØ`FxĪ[ŨY[ }:ŌCLÅų÷€(` íėlÅ­%4Œî;ÁÆÜœx˙ĨĮįßk÷ˆž¯;­dĢĄŽĪaÎÄTū 'ņ{ĩdx,áŧE˜% ãĢâüûRöų÷XŠœŽ?ÁŽîlũ$Ŋ]Ų/ë•Y×7RÂdhÂį&8¯ŊU„Å{—Cqū}ŽķīY÷d÷ßgëęjūûL¤ š¯-Н%–ĪĘ9ØĪ°÷oú§\įß§ÉĶx͘H(ū1ūûĖžÄ'ZEü÷Ō§ ”E/7ōÆtĘ Gčd×+÷ų÷iē°Œ4&Ä/°ßJ†ŋĖ\ŌÎĪY#ŋĮI!8ËáđiČĄQiˆˆ-čų’sXY_dzOķߖâã™&3÷i̜ŸÖv/˜ßËaß8˙ Gá8Ąģģr¤+ūyŋwGe¤I{÷Ænĩˇ¤ųht´uƉQč+œo-dØZz–‘=ū$Ô'ŧ_i})›?%ņ{š!‹ûÆųo|"Ææ{{ģ°€ŗ{9˙~û¨ÕÂé÷’žGČôŽÜĮ Ãāāoœ ļĶhQúŊ>’°ä ¯N’ÂÁâH#GĖ""Ɯ2Áš´hÉķā€Î镰ŗ0Ī~Ëü>īŅ%V8˙>­Vâ÷äĪÉ\Ÿ#“V~­āđ¤´°å÷’†O\Ŗ?čÜáá×Į#Ļ×ëķų÷ŖÆļ?š=ĩôŽ[í CM€%———8l°sr‚ŗĶ./ŋ"ė˜Ã[HđíÛ70+*(ßÅ, $ ”×8SŸķ—Ŗ9myž?œ}†žÚ+ d×n…ķī.EÛËxŗõ“¸”ßûųZņßë~DáŌ˛71Ö36yņ›ā(Â`ķ˜aˆĮ#“ĖŸö÷‘p˙’†‡‚ģė) ‡QSģ8G1 ņÆWôģ‹´z}Đlę ˜ā€ŌÃeÖØŪîÂÚ¸˛Ųrū%álüJÕŨ;Ŗvøi…ķīĶڋ1RĪĪÉÔOYá°âú{õ9°?%lNÎYā4{Ž9ã4¸ Ÿû#aĪJū §ß—Ë8˙^Ōp× 0>ĻōëiĖČļ„Ė)ĩ-Äa- Â|˛žä‰ķ7‘€Žųíˇņ'*kŲ"åų âߨ?GT?īņSūķīĶڅY†˙J–~’ŪŽÎ^,€ŦD“ 4Å9ˇƒĢĢD4Ĩķīé<ålq&WÎbĐ/ō?Ec ŦeXj%āPƒ• †­OŸp‚~Ä"ũ%Hī­ĶkõŒōg[ŗüįß§ĩWˆe¤0‘€ĄŦÎī—õ$îŲĻį…Đ#WÜŨđõë%läë`prr"=8~ū=_6ĄˆŽcŅ/W 3/_' ī ~_púW2߲ŋĮúŋüéøŊ:Heå´AŠ"ŦgS2ųaŨ0,#ßúŪC"‡LĨøī•ßcüûČõ÷z⎠› ‚au%2ųaŨ0Ģö„DW0Ū;N9á>CÃī1ū]™ßˆ^ úķčĀđ{]5†Üúœæ÷ĸˆū<:°2ŋ÷{EÜĻYäđ7ōáŦÉ˙<=ž@֟YÎ+ķûĀßĮ{ŲT^đû‚˙0˙~jÔŋ—õ?ūäd„×ļ>G6—9%p;Ŋīæs>uÎß|˛zuq´[ŲĒUk °_uũ}÷ôt2›ķ)¤ú8ëīÁy5ˇˇˇWŖi™BĻĨ îü{ė„’ĶŦeyRBũ'6cë¯ĨÃI8ÄqÉWÄWFá9@éÛ_õ*Îbg’ŠōÕŗÜō˜ņt1Ŋ_œTļ*Pƨ.•ËU¨ūh8ŦÕųüûLũ„\;gũn§;ĄSšôúŨÍōÖÖN*۟~ÆvØÔV~–?˙ūmī[/ʞ\ĐNŧVŗžU‘ŖÍŸķ›õędƚōW”m<_ŒfĨÅüė°ģā}äü{I–¸+°ķĨ'x/$¯ Ū‡÷[üūYøũdļhl× ôī*%ü¯^)Kā…„Ą ĪPžŌâîŧÛ^ĖĻ‘oíÔ+0ƒĖõ,ĻČ\ĐĒüž=údx|Ģkũ?'´>§đßgū<…į¨CMđR˙ža¤ļėü{!÷ÉŗūĢųīäšĢāI˙Ôü÷ÜØĢķ¨õ÷O=ŠKų‹bé旧ũĪĐ)ŗĪŋĮ´)ŠgŠgi5˙Ŋ?/đâ“o'đßs_°įc>bũŊ j×*ÂÂ9crŦ;ãemŠæėõÎä‚Ä?œÁŊnÛhā$9ŸáFŅ;øU"ŸÚ5ˇôü{‘OŠ.éΊüûk­ŋ߇×ËīŲ‡Ã=u{sĮ‡ÅˇŗŊŊÉ1Aš´pü]ÉsÕ|^NËIû.æąŧ<ųĶäŦŖz’×Í'pü<ØHCëĢĢ+œ‰‹¤‘ĸåx"GĘáŋ"ŦT`m1šQžķīU0N—ÂōŌ˛Âų˜Äėc¨ŋ&~ĸčÕҊpŽŋ:´Ŗŗ,]įT8=‰Ôwmúüų K>ũ8õ[Jŗ—•Į–-;œŗlODîųøųoihŨīŅ–p†āˆ+įB㉰Dâ×~ŋGxÖÕëëk˜ˆ÷ĩžč0Î&,€)8‰Q‹šmŊSÃŧõ°™!ežķīiY¤Ņ‡Hxu~ĪbŗKk}ü™†{§;á,@SĪØ€˛@"ãŗĩ( ļĐî7qØˇ‡Ö# ÚlāŒ!Ôī":t„­ŽPÁ!ú8áĮâ¸ÍH$ŊˇhãŨŨø.ž. ̀|$ €#K‘ÕQ똎YvV eĀAū8zÍŲŸÃ<—MN}“2°Iĩ$OĘīĶ:O÷‡U!0™ŒQÎÉxŒ*‹jrä”đ>œę‚ģ žĪfxzŊīv:8͇_ũiė_ž|Á‘ŊãÛ[H˜âūAáōœoø}‚ Wį÷T jƒ„×ĮīèG>ohŪatƒŗęĄ@zūũÆÆYˇÛj#ĸôīâĐúOŸZ7Ŗ1Ž<íâTûL >td û:‹›øŦũÃŗ^Ī[ Ņ‘'Ķyų‡fR;mlÜL&øų 7¤GIp; Ž.Ä] {d%đ-x@@!PfÄā[WĢĩxŲ$Ŋ”AĐ4^ąrOÄī‰ãęÆ1@ īFB@aĘ_Ė‘Á>Üž(ĮŠøđG[J<¸āœv:;Â0ëčäųā•ĶSM)NžķīCü>jŸ†ßKoxėúût÷ÜÚŖ8ō?9:BûCpŊÜ&Ŗëëļq<24ØŖ& ‚ß÷v›€+APkI†Ã”o}ØŪÆ^œĮĢ f$>nĩpõ Úéū~Ļ;Jš‡IH†.„0Z|׋ápƒŽQ‰x dŒũKË&õJDũ'â÷ĸaiügŗ{B‚€œD-gŠk‡q‘‹ŌČ@­?~üÎ#‰’SĢa]Ít’ :íĄÅˇōœŋv~/ZmŸQ~Ī‚[Ũã÷$›HOuüųoTi 3Î 0„Ŧ8Ķ…ŊîŨéŨāšyp€ŖÔ,rûą4Ę û}p}P|ĖĀo)rKy¸Ĩĩl–ßŖ$ @ø<Ęs8%¨Ē ē(Ɗ'ÂČgiŲ˛Æ \ë?Ķø=͞løC@8ž0q‘ČŋxŖ“Įc´…žã[*Ą™ %h°ŧ7މeÖBšē|I‰z~芞° <žú+/tÃnĀZAS_Mŧu;™W8æ™I´> ŨtJ%D`7ċíĮŅû+AßbČßmîJúIJyK•á­"Åz˙ŊhX­…(’Ļ~ú-‡ÄšMü¯ųéĶ'Dâ)ŖvüáWĄė‹ŅÜß?étĀi€÷ŪņißÜ\#͊…‰ō Üŋ뇅DnhЈÆû.=˙~ŨüžžĖš€ą†Ŗū{é +đ{™&*K\VFxâ{€‡ä4€¤Azb&äWš†7FŌÂÂķ [IÔ„ķ qEP‹FÁOfķ9âáU@nĶ['Gč-t?nģ ^)ƒ|Kō÷yÚ0°įč¨-ȇöƒÆƒįP™™ĶSJękZ/ĄõP›Œ˛ųô’gŧ<:p”éōˆ†ĻŠÉŠņJŌÜ[đ;ų  å¨ãh4\—?„‰?é ¤"á/ĸ“@ČPh.7ÆÂ|¸ėnV˛ö[õ>ŸHÉčUø +LõĪį:|Äč“:@ņ,$đ4€ĘAááŗÆŦÂjüŪ’š~oWē)Ū ęĪBO#ņĖË×ĖR¯'ÁTŦė¯Õĩœ?´>GĪŪaĮ9rxŪ[ÍS.dō:āh ˛–<÷×ú…ė˙$] Í×*ŋŧ˙žšŦöō",v¯Ãšå īgrûī…ųU:æŧ4LĶ¸Û´W:“ėqz94à m‹°ĮûB&ëĶ ’ĨYI/“l؍ΪߟCˇz†ö×âü]†J\hųL˜cWÎ8•ŧƒ¸—ķiaīt+dĸįe¯Ž~~5~īĨĨđ{‡÷ęZ֓\ĪãÅ,Õŧ˙G7Tņŧ帐ĪZô8 ’žéŲÅ) īŖüžĸéŪ¯˛>‡;˛Ņ Dā‰Wˇ*,/ŋ×ûBüŪÃŅ#ųŊ#3ĒqP„+Âbņ 9ŦC:§Äëŧo'ŋ÷ŖËīĨeÅīusšâŊúSû‹°ØĀB듃ø õ¨K7ÂĖ‹Š}ā"¤ųīŲ)L˙æã÷ēcMđLXW.äđ4: sD”÷ęüž_2ū{ęžßõs¯ŋ—ųZ]'mfÎÂņ\ZĪ´ƒ ōä {ŦsĖÔōHV.å÷Ā{šÔÍuŋĄ;ĻģLĪÎãO-Ō,Ÿe\jisø­ßǜIßhëzų=‚Ÿ×Ī=X=ũyüũEšŧ(•g'ŅΚ†´',õä8ųdņûĀų˛ŋÔŅiųüŠ’19õcĄ"œšUV+é›ņß'đ{ŗÆ`5~OŒ-wĪ+ĐŽÕsë@~/¨ŋŋ/Đ̰`/[tœčŋĀV7†nōâ¯ÆīsŦ ːĪßü܈øÖÛ%‘ßëūÚÁÍÔS;5ˑķ~+ˇ’‡¸˛Â;\0¸ŽLf\ģûV^ō'kpnv ™sH%įiYZ°#ĶlÎäĻÅ8ÄĪö-ŨBŠå\ļííäãäÚ™ôöŪŠŨˇųÅMËz­`îtq]rúpJŨŸíÖՄō×2äŪ_;—ąAh-°ŊMüÕbÛ[y÷×Ë×Ü´n=ᭆĄMÁ‘Ęģ-įaay‘ŠXļĨ‡RčšhĘHšâŸ?,RŨȉyF0 ¤gõ R&Ļ1ÛÉyÛjhU¯f%Ũ`0ûk5}Ššmö×Ft´đû[ĩv§gíŌŪæ˛ĖÛ;‹ôK÷42eĘ)=?g2÷ĢöƒũĩLėŋ§´’˙žā÷…GëEë@Ē˙Ĩö$‡FžV˜+üĐ+ûĄsĪ8˛}ŧl3ũ÷Ą9W>VŨž…ŋšđŽŧnČößŗ¯wđ˜wĨõ9<%žU W!‡Šēß*îŋ'~Ogb%=ąü‚ßŋn„{ëūøU-°ŽĮäҰß_+ūûŅ`4sîÅîVūœÜūûŧ#ëįņTāäBą?RÍ×.—+Ū͎ÔS!›å^]ŧ-dgH0“ÃĢ“u>Ļ÷ߋę¯āŋĪįCÍãgũÁ4ĸ ¸†Į&x5A3*ˇå,;ķ‘‹Ž0ũäÕTō‡ ’R>ŨŗįcĘ÷ÅOįį;~ĪĪ×ĮīIéé. (Ŋß8ŦFÍô’cPrŊd“ŊnÔ"I\įâ SQúWZĶ)š“˜xîũ!ē1K-$Ī+ģķsPå÷Ŧú bų+Ŧo^•o­=ũ|ą¨ÕčTh§ ķWƅ ¨Kš|æķ;šėÕd:÷ęëû¸öÂĨfRū1X"ŋŧ§Ūā=yu˛P' ūĒxÜl㑞M™į÷¯) %ĀŋtKOĸW„tŽ~wd!wįķ›Vk´ÛîėŒvwņŋÉq{án |u˛ ¤”Ķ{Ļū{[d­Sz6˛¯ËOęāÆynĀĮ^)\-<Ízũ+ŊÆBšZ>OĪ`Τ§E5š¸jĨzÍ..››ķyĩŅØlĩę5\ļ>âļøŠÜ,ũlõZߡX*Ĩ娯ū{Õ{OÃüžą˙uņ{wŽ ã|€”ö|•ŧáétvđÛŲtQŪoŅՖ˛”îķtö'O>Án˙DËéą*gžŗ‹K\ą u¯moãúĩëĶĶIŋ?ģšÁ?k{{“v›U˙™ë˜GKŌđ‚EÛÖ9ÎĮt+}øŊ ËĢã÷Ū#kø.[-ÆK~æ éÛgûŸš¸‚rĢŅhœgŧ;™Lqc!.Ŧá>øît:iūÆWŽâŌĖŦō¸2˂ęd$3ņÔzKë8:éTp}\iãözt0mžœ }§xnooTŪŨĪhųmšZĶMŊÉeCĐiqY%´5…pž|é,ũnž˛ũPgÕÅú-_%°„ß ›Œ|UësЃũ{ŗ ?“ īƒÛæūîäŽÔîöz|p|ļŗŊ]­”AuĐĖ—ŨöaZ>|înÕåQfų°…›ĀiĶã†įããv÷ôę’]SNBŖ4$ â™Ī-Ís1™āĒĀŲxŧÕ:.×jĀõÉáayĢŽ7îĻ“áhzsƒ ąīÆã4YáæqÔKnēEMŅĪĪĪqĨ6É\īŅĨÂuÜØŒšJyđOX6\‘ 38 ]Ŋp zvņŪîÁƒœq'1dˆđŌē„ęk0;Á*2¤‡âŗųŊ,Á”ŪķēųŊšAQp…”ÉŲ„/‡ãÍ:ĩze2AõŽŋÔ7ë•ęÆėŽÂŲāōëU¯ŊMsCŅw%ēŠ¸Ų´yâŽbDúôh`\ūüûīŋ“K~YyŒ]ĘĮīšiĶĘæã1x-U7îįŗÍOŸXé*ĩjųv珟MÆ7Ínwãá~z7}˜N#ōņåBĶŨËIßÂO¸pŅnávX—Ļ„š7ü„[Ëģ]ļ ‹EīôŅčæōōéå[xŊ˙D&¸įyi]Be`ŧ_ß;ŧpäR˙Ŋø-ī_•˙Ū36ĮŪDY…É%„›ĩņÍÍl:Ş@Îx2ŽÕßÕČ%Œ…ƒ¯ƒßĪŽ7á&OzWōŧŸÍĒ@P“ŋ\tėŋ †z0 ŌʐoŲgŋwxŸQGP°“Ōũüūîn|p@ú=Χ“ņd˛{zZà ãHr?§fO‘j„z%ĘĘÚä;ʛͨ¯ĻÁīí6,.Ļב@š|quu|܂ļÁ@Ũå[č0ƒÃ œŨ^QY1Ū¯ĀīŪĢWŽZrķ{Áû×ĖīQÃŨÎŅNŊÕĤčāõÍŨZe mŒž0øzõûYggs3í]‰Į ÉmQ˙]€=Úx8ŒĮãĨå Đ.É,ŋĪ“'­Â’ž˜NǏ7tq7ž/vOĪë\]UúĒtk˛ŦĒŽ3Gå Y˙@waĐ9hų<—ĸy"‡rY$´ ¤\ĀTã6Mŗiv{EŪUĖv,ŋā÷n´.x`ŧņđiˇŊY{7™’RnlTAī0¤¯ĪģPzЛĀs’J)–ģoī dŋ‹ŌœžŸétŦDOË_MņTx„c(_^G(4!:æuf˜Č 吔z6]ô{¸6KŽoc÷數-°ū‹‹ Ą.å,d mL¸ŪĀäÎĻxžąĨíĪß{ˇ~Ô˙vøŊnĪ•Šõ ÚÂņũķöfŽ›QĨRÏŖá ÚmėŊ’ĶÛø“““^¯?¸ŧ$:ąX\ôĄũŖŖ“ČģhōîŲÜ2֊—!’~)’šĸuI,sŖÛ—ßmÕīĨwåAŠÜčt0i5íõĻ{ĨŲŦüŽ<-•AôĶĘvÔ:Æ@e0¸Dņgŗ{t°yųL%bęŲõ:ÜߗŅ0 ,Ė | 0ä†ĩF“ņ¸Ķnį‘y8Úaėø˙åįßģyé7ËīûŗÃũ^§ZŽŽ†ũˋ^¯û™”>ßģā9ĐôËÁÆ–z0öûubüŅī‚ėīīŖŊ –/)[*’9TĻA^ŽrVĒU(úî’wåŌŧôĄŧ¸?īŪî6ĘũS įĘĨ›;pžĶ|˜CeĢonžŸŸ_\\a%ÜĮ¯¯¯1^•:ž|ū W x<*ˆƒ30āaÚė™wncb‚GŪĄ´;¸ :ŒäīĘÕ^ōnļUį)ū{ĀęH†ōŅõ÷—Ŗƒyuvw^Ųú{KøH(,ÅĒʒ0ÄÔŋloÖwDé—ĨŌ4LYË`Pē0“Ņ˝ÅGÍ0X¯¨#n´Ø¨¤ōß\\;úbūŽZĒ>”*åģ‡fĒÆ‹ōŪé)üˆųeõ¤uO+<žŧå ÆNč–V>iá´õ÷cx´’ÃYø@/įOõî0AõĨa„TĐ3GIZ‡M(}Îôyō|t”Ÿjánn’jxų“9gļĀÕĘ[Ghvk8|hî_—ë—‹r˙ž4ĒmŪīîĮø)>/A>Ž ÔÖÎŖŸã$‹<ū{ øs€œËGĘËxUîõãi (0ĩL:< ¨įÁO†?xœöDÛh¯^đp”ĩ@]¤üqųoTĢ7××ŌĪķ׹‚•ŊŪÉxÜÞ͎†C,œĀLmv9ŸĸŽų˜V6YukĨ”GXœēF×ķû_ȇķŌ†čCb)Kz­ķĨ{úu:ĘĶxTōÃ(đž ŌHAÄ×õGŽéíJ{Џ÷Äå,ģđ_Ę+­æ6 Zb¸AiÅU!ƒ ėœaą;™‘ņRgĪKŊj~Ī´`Í`‡ąx‹Õ‹ō*ÂpūŌ×ā\‚šuįx†÷ĸ市ŗģ)6C{WĻ÷žŋĻú>ŽTJu’’"=P)‰û’™ßŸŪLį’/în–wvÉxßũŌힴ^&ē'÷rŅŌâažm˞¨ø*Â(ą´Ĩ2‡uŊŸų*ęøã킆 ¤´ éEOŌö×ęyČ8Öģ3;õÎë<“ģ´ĻŋDJ–ešsœBĩ| ŋ´#-Ĩ yčÄ3ĻÉB÷8ęëų˜ØTĄūŽrč|LŨW(~ŖWĩ>Įzœ^ąŌ‡Ŋ7hŋ垴9Özú ÎŦåōą÷[NŪđúæüL™ØŸŖ ô2<6¯ÅŗT”3wåšĶûėŦyųÕûī#~î\H™M‹4ÄCs¯Rļ~ž[H°›ņÕũĩfē@ūųī ”}n}m, Ņ/xīxÍ+į÷ų9ß[@˛Â"åŗH‚›õ9Ąõ÷ŋ/ŦĮ[ŗŲü^vZŠđU­Ī)ģ°rY:°Œß‹?‡ŸŋJ˜cmw‘æå[Hå÷˛°5OĪīuõĢößČWXŋ˜Đ&ņäčZ×øūZÁûÂ_ øËGņÜmDę.H/Ē/aõß;~ĪŗYŋ~ëÂÛŖŪ]€ā=cŋ_Ÿ3ÅŋäTdŦCÆŽŸņÍ^|9K§xx~ ČōĐwÍĒ‚`Ũ‘_hāWŌģēÄd{§y°õ˜‚ôø“õ9´ķöööÂŦŋ¯m”kīĒŧ÷Á^CžJ˜ŠĖįč# ŋøÎ#Î,)gZøŅĩ°MøÔáŋ^ÚŽŨŠíiæúžĒKē‰$ŧü^Ÿ›Ũĸēũî':ü^×ßĶöŪŖvmgwFĮLSRAũ`QŌæG—†ĒfīJ …ąÎ'ŠÚØĩxxž%ėrŪXŦõ)’–<ķ‡ĶÚ'žOFiIæšy–ė͝ķ/(œVæ•ëTŨ3ėīUˆë‰@€Ä;7ē€ŽŸHO:Æ˙!õČmYģ§1žĮÛAzüa›ŲíõU˙üôløĮ–Ëápt‹­åē4-f1–ÄS7pËb쁔}Iī§P—pV".oHĨÛ¸đZ;Ė ŨĪ–AƜ‰mA’įŅ|WQD)[ZxUTvMäōĸüU‰*;ĩNŪ7VîˆîIžÁ IWÔåĒīŽ(—6kåŊ]ųęßp*ŒŲĒUuĪ,ʆíjMviøq^› ô|'ŽU =^ž¨TF:=ŦJĪi‚pē­@k{K’?,)ŗŸRN[*ŋ”œŸŒëūIj' &*øa× é[ËÂáŌڒ'‡ĨüŌĻá0J‚Â\uaĘ|ĨH°c؆Žų5?ĒčŦ3™JO¨oö ö§<93œ‘U-_ŽĀh°yåŋüĮüŸ˙Tū§Ū˙üŸÛ˙ößūũß˙íĪ?˙ūų?đÉ?–~…&˙ɟ—§%ģ4%¤DxãWB}zÚđįâīĨ_øYú“$ņįߥįß9Æ?K*ę#&)\ū; ĸi‚pę˙jēDū°¤Ė~Ä ¨oĘãkQâ:ĘčøwĻč?CņON+CRŧ-sž°”ŋô§ĢcüĄÅĨ_Ōü)O >ëIéWfŌøŽ¨ü*:ƒô¤Q˙Yū¯ø&Eũ‰đ¯ŋ"_(ŠüIØ>5~Qúį*Õ˙šü˙üįÕŅÉŅŋīüÛŋüËŋü‚3>ąU;ô†Ŗ1úÄÉQûđđĶcųN:?˛'•ÄÃn°pĨõ>ŸzVŪŌ[Ėâהso’yAļ _~ū™ŧ&ĀUzņä„eŪGŽ@ėô>ãŋ\õNOŅ‘ļˇ7Ap°O—ôžökbģ2k?võĮGÄkÖÄŧš˛îP|÷eJ ™liCCĪ8ę„TöĐœôF”Œč˙ĒÛTԊW¯oIENDŽB`‚PK !’j•¤—¤—word/media/image2.png‰PNG  IHDR¤ aUásRGBŽÎé pHYsÄÄ•+—IIDATx^íŊ=hcˡ/¨ķ}@†{A ZÁ tā ČÁ+h¨á Č0v0Đ }`^`ZĄ:°3;¸pL↰ƒ+¸Đ&P6Ė€ÅZ!RyŌ§ō1ēûūũɗ/ÂŌũu×ߟœ9:ū|t°Öų°ąQ­Vaõ~;ÜßO&ĮĮŸûũūf÷ĐŨė.Šq=éHžųtdŦĄ0´x'éuJŗú¤iÍ$ŪĖuSõcâ͜āķií×ĨģRi%Ÿž+ÍVJšGéYiĨ,)Ĩ;ĄŗôC<ÄJšĖßJ=‘Ž/ ?ü]Ļ%§ür0xÚ˙?ןGØAŋ ˛ĩ=+Fgr ŦŲfYV>ķ›ÔßėwĨōJiFrš•î"%Sã•WĘČ™Ģ!)ÆédöÕrye†žĄžiI‘īzÚi ë‹üđ¸Ęö+  ëĩ>šEUDo¯‹ųĒTGYāįlūHÃ-)+ZąāĢ^)ímžßß?œFäKâũÚi‚`OD„LŗĩQ,ŒôwŠĨ{7n+s¨ !^¯oīí~™ĖfU`ēŗŗŪîöÖÖŒŨß0zũ|6€Ĩ[ßܸÁŌ‰§Ė’I Ģ’Į{ļ8mÁJ {_Y_[oéč¯F 1‰XéøĘyÜŽ)RvÔŅ/‹Ē{JĶÎŌ‰MqVÆZ:ŅĪO.-FGųĩéҚ‡ßR‹Æu—ŋFĪ âh°”f)}Š ŨŨŨtzK‰ã[2Püŧßĸ BĪ&Ņ_§J™Ž§íZyŗģ#e{7ÅSū‰tüy(§ywÂīĸdú Ķø"S¤ĀōIŒĪÄ^ņOÔčˇzcíúú’Ļ&čßbŗé‰÷‡Ô Q:ˇHp{éŽRŽ6[-‰ÁɏÛĪø–t …RŦsq´´Ŗ8n]ļYtj́­Ũ˛´ˇÅlåeZÔÕ2˛īęåbĶq.Tí8basžŽd[~f¨>ŗ™‰Ķâu¤ŧŠbR:#)ķ’šOĪâ>¯8ŨĶą imgîQV$<¯B<¸H ÷Ŧ'T$nīũW’CbN–ôS’Ũ”vä Ũôv\æąúĒe‰æmÕˇyMš‹fiįĖÕ ĻĶūâĮŒąy[™įUWŗBØ\DãcvI6ũN-— 9ąb :ŪOŗō°5ĢĄŨŒĘ°ķiø ú`ŊVŲj5÷ûũŲ͕rŠ˙Qúœ(WĖÛéxr1žy'ëvwZ°ƒ´NÖ?­]lũŊ]]ʍĩŊ^īxx9ãoŠI×ęå?V›ŋÕëëo×W7Ķĩ27ƒAwŗ]AĖYzūīöööô|Ønˇ#íņ2‰ß‹6nÅD”ĶUČvˆüÎ!vY{õCWN‰y|δ AaMz‹ĨcũĮöĨ$-āƒ~bãŌ];ČöxĶȓšK[Xky žN`íe,Xaąƒ[üâz¨Ã$×xŒY:‡,b‰Obmb§%hŅiIO6F:,r'Ŧ&1¸HÚ{“-ã;hĖ+DPŠ×öŠwieų™Xž§ãî.Ī=ĒdÄŪÉx6˛wÎĢ)Æ;3ÃÜŠ}´üD´1!1Ÿ*2rZG]“jãVÄÜ5æß āfũmˇšŠulˇÃĄŒ–¸šėčÍ6ŋ¨v$ä<—.&ŗ“ã}ŠOwk¯]-wjn•­Á2æ“i‘°Q:j‹ˇ­ÖÎŪŪÉå5œŗiåF­ŧڄąSdGpÔ❋ÁéîVwggg"#m)—댔•HžŒŨ_ŋ~}÷îŨÁŅqgŗKíęŧ‡:‘˃Õõųöķáqģ–ébÅ8¸°@6­đ")üpDÍˇĢ´MLËåŅęzét´ŨĢZW›Ĩ/w[ĢķņŖČTĮžŽWŗ°×Íõ>㚍Ãáq‹+ĪÛˆ'Ø´Ĩâ8.…:ŗ|˛õÕiëy}ôĮûŊÎɡ}šĶ‡ņ`¯Õô.ގÖ|{Îī!ĨŌÕq­uÔŋŧÜj0Ī‚gų—ģˆ[=6`FŽųɐsJÆŪųųŲ||įl_´„2iãRøNĸ™?qŦŅķ°ž`›‚Œ}LŦËKįģ–D!M[Ä9ÉÅqsņč ëžõĮ¤Ģꥊa`#ėė@`§QĢl6jû{ŊņÅŠŒųH\hdú¤:¤éM7rPĒ]–kGG}ŠÍöv¯9wJc1fvL`PŪ ņÃŗđ~.žÖéėõû§Ŗņˆ^呸ŦôŽQ˛Ŗ˜÷IÕQ:q)]ōmF˙’'€qĪŅ_˜<ä¯×ëŦĮ+;4æûv°‰.ˇ{>ū6šģؙëōÔ q×w,+­IƒãúúXžŦJ‘ŽĘ1’j-|FŊ—8įžH_Ņfúȉã^ô­Āu:ŖY{9ÅæŅŽŽZŊĢߒA—đā‚2j_´ŊØr߄šņr;DŌđMá(°<Ĩä)˙ëã ōŦT…Ĩ;8 `$ߍ¤ĶËņüôU.įKéčĪɧ`@í/b߸ž„,R€EãĀø’E.ô/l)šæûø'āKõØŪŲuI”0ėwĪ>~šÛiRAuŒņÉzhrKĪNŅfî‰Tķ3vš"tķia[ōHtOiŽ’K×LšG°›Út3“ édãPŽæaiņڍ|Ũ 2IOčÉhPꡚîåÚÆū‡†[Ŗ¤øŽY´4 Eg-؉KÜMÆéū[IZÛN9~(FzÃkˇ6ĒtsÚ=Ŗ:D­™˛ë§­_'ĐŌTĻNoFŧ)ž0sņōuÍĐÕa†ë5Eáĸ9Gū ˇ=—Ģ5īFķ˜d)˜iâ)Ļp¨ ^ëÅĩc†4Ũc=ÉãqwģôQWޝÎ_šŖM+jÕQâw‚z|,ĪĖŌF áĮ?×cåsSPōEū–Ķ:ũ’KŅīĒ~ ĸ!ŌĮ9Exs:ėË´)ė u>š3‚ßF*g™āŖt.:Ō˙hÖXŋEũ˙§LT„fi”/*õërŊßī įģģ}ā(°wH”ëÄ?iĸčL…+ŠQ­¯ŒhĢ“[¯*FZ>ɏ™Cv€ žs?aå a˛ƒj#ųü‹ŲNe†˙ŠĮxtV掚~ Tė´ aåęJ­Š'a=Hk Ü´utˆôŌģ_ĮôÖÍņ @ígÉY;ē”ŠÔŅ`‡ōP [ƒ1û“ŌčHōŦÔ烝Į+ëZ¯Eå`üíņ °aCŽ^Fĩ/ū¨UQ JøãčŠę _lqJ) ›°ƒÍfuåø-JbÎĀwf>~VīüÕé­o Ļä€x^QۍēÆáI­z| ųãŖĢĩęęÖŲôû=°ž^[ŲCôĸ4nWå¯H9@‹ĩ­ããî*øŦ­œ€*twČžMZĐņļøWįāāb$ yyqĐ9=ü¨‘”R鿈J–˙ßpž›Ã•ÕããcM<¸!Ҍ‡Û+;į#H ÅhņwKwW'’íęjí`Ä(âĮ'T›;Z SßĖŖõ Š9Ų4ʋĄžšķļæ]Ũˇ ąŋœÜ'w—'Ĩ?›Gčōô­ŗŊ›­oß&ßN6ÎēgWęíŨ}AÎˏĨŊŖ!†PWŸ[›k”r÷íú´Üí]Œa.Ē(đÛøîÛIįŨÖŨŒúûÃo““wU‹Q˙Ūzueĩ^ƒ[­ˇ? ×° ëŊ_&k?öÚĢ•Ž›Ũæ9ĨLžm­UÚĮ—'ĨÎéå˜b|ŠŋØ;Äđ]̏Ro_˙U†u¨žŒH”忇ÃÎŪ1`ظĢOĶÍÚÅŪûá_—“ëo“ëãĘęÖõDgûįãģũvutÜüŗ1DĨŽĮßPN˙BôŲ§Ëîp<9ī—>Ŋ_iŽ—1uŪ?ûķėJđ¸ÅžõÖvgp2"Ã3÷:Ũĩē`ÉŌd¸Õ:č1Äžߝö÷ēÛ¨3¯néí•ŋā‹ÃĨŪ1ŌÄY`"°}ŨÄx“˜X[?é N¯nWˇž})¯w§<‚ã"7‰¯¸f…IŊqŽ_ē ߁‘Õ'ũˆxã¯ĒžÄPįĖN)ËĶŲ™ ;áiQ›Žĸ”§ģĄ•?‹ņã,Ē‘›ŋĻZHđM"wÜ=ĨfÅō4~įvÔ2â“"^ɘŽŧ…Ė]( ^žÚH*ų—¯¯ß9šÃÄēGŌå^Ã2ÔoÉM;:š7WmQ]ņøN[ĘutßĨ"Ká;ũ+åI9iONŠTĢÖ`ÃĒoë€ZåJ đ­ZŠ|UßbtX-ŋÅz9<ōˆĻ}ō„øĒĨ9…iy7‘ÎeĘģŒŠ´•*ōcËųÛ›Ķ2 eVŽ'ž~ŋP);f'¸L˜n*įzßáŋÃáøæö˙lš]ĐaŸôķŖ}Ōnŧ[ël CTŽ-Úl6:M­×:‚ũ@ˍ/ö{×ãŠŌHO-ĨĘģJ7ÛĪŽëtĨĖĖŽ­Ņ€=2r‚ãįFsĩĻ.’ÍĄ+ķ-Œō‰4y„•'Ũ3 ÷Ä)`ėöíKÖÃLĒā;Á,Dsw&[ãWiSŠNĻqNDĶŽoä¯ĀqĨNƒ‚.ĩöÖĮŗõ̓[Í_ī^_ļē™*&b÷ßļ:XGŒOB_ü]CÎÅõĸ¯ķÄ&ĢļÖéœíô43ģRž:]ŋčtëU°ĮՃbvËbŊhö“ą’‹ÜeŦ.ÎĀwŅî`•§HUsZZZD¤ížl $ÎÅ-ecgÄˍåÉĖ,+ĻÛOBƒ Á†˛P3ę EĘZČūģځ Åī˜[écō”.Â`Áá;7ãb5ČĻĶnŊ[smíf4ĒŦ6ĢĩúJ•Ņc7ÚT!2ĄmŠ"5“g~Jâ]pʈ¯ #WÃ×Á8áēp•'U ûËŊ“ŋÕ1‹@"qÍæúd2h"/D˧Dą|ôÎŖžŊąw7\íļšUŌ{Ãq­tˇõíäō÷Õ=ÎŲ9šŪÅhjʍŒ%ZĘôq Ļ[ÃũO­Öj_úĪ>­›Ûūö×đÍēŦæ;Ŋ8j7ē§UäĄÕmmā j2n;iA2‡ŠãXLĀVûŧ˙cUf`ÉÂZÆkš+˙ē>!ŧÖî}ėļPÚîéxĢé$Č8Ž´!Ŋūļš}:Ŧ6W™ĪŌūéˇ6†čPÚF{ŋôiīcģI*=ŊØkn^đw4lA7*°JĢŨÕ~ûđrk¸ûŠÕ]åz•ËXŞŒ& ĄpßĖĨƒyũ+ĩąBŗYãē+v̎‡˙haé׹:îTîfcėÔÂō@ŠqžX[ßÛũŗĩžĘœwk­õÖîä[ģĢįz 鏛”$š#ōįĻ&\ÃúįâkÔģ–Ŗíģ2â#ŦäÖ$3Meڑ,Æ&˜ÄĻ“Üx]ąÃqqĻĄŸą™{]b+à 7猿jĩÎųëō|äÎÍÕÆÎČĶé.õƒėD÷tļ46'žīĸ> ũTúƒ‹;ĨhÉãŋB_$,¯Ãôv…õyÆĶŲ‡ƒŖŊNënsŖšÖĒc$Ģ‘\šoŠŽqėØyŌ ÆĒĸA hW‚÷ÕōüĮ“Ųđęjpz˛?‚æV´W‡sņ20įįāėtwģ+=mūŸ;:>ÚQ*qgS=YŠ!`…; "2o "ķâIĶŽEyYØĩp´´äa‹c,T<ĮhÆSQGû­}Cū­ũDzËĸgä[ÔO7GÍõŌ f=œ?tV8ōĨ1ģėęĻüÄŪkī˛=-öQÛ.š"ÜHONŅÎĪéŖĻP¤ŠĘ$HSuŲö–ȏ7—I—f‹f阍“’MŖVŖ:™=OŽ1E=Āô3–åqķ™čú­ö^Aanԍv­ EÂÚÖ*<éĢUÂ]ÔL<ĮÜ=Ö*)xak’CģīZžĶˇØĻĮõĶØOœ!V.îmŨ\^Ļņg´Pe–Ä)„aĮĘ"‡Mo‹\ßw–tvW¯TÍĩÍũc,e1ęØkĨÔx[nŌvąØŪØČ/XŨh4Ú;,OšO;ë@Q;Y E-¤sIq­Ę6S^˜ķ­žö˜Ŋsļ/˛tÎÆIĄ1dkQŽĨõ\3—#¤ciĩwZqe ļ$5BŦíx¸ŗzļqí66(ȉL…ĒFB#UםŊ'ŊÅŖ‰Åz_Ôē-YĻĘĮ4š@O7;Ãfē'´¸šĮĄ#r@ÎÛ>×ĻÜvl‘ßeŲ>ą†:§léėŪîđ`ŋKÄōlvvÍ­.ö8Ë[:šcŨH";ƒ­qH"ę›I[Įwë$q_^z‚GL(\1Čéá)ą!WՏ 4ā:P4Ę|[Jí5üMĻ“Ģá×ņtŦøČZOŅWŅíö:i]?!ŸY–ä 2> tøĢ %ĘŗPqkŁōČŽĮč¤ũĘÅhI}ÍÖqÕ1öwÆx›æoQē:jnöJí“ķŋh~IŊœķ،=eL!8ÔĐqd=GãŠĄ„”†k/3éšį!˜Ģō˙˜‡éT6ƒIË2°Øƒąž)SÚ&†īdol&ž3c;ū˜ƒīŦ<“Ø'Į'Åf3ō¤/#Y7?c]ŌļÎķvq\æPU2MkRZ{2˙Ļŧģ‹Zx„aÆ7*TąÅŌĶcmkŲbœß4{c§0+ZIäČ n‹¨ŊMM˛í]dØR$Fš÷H!ž,Ō:ųŒá5Īrqå\Ģ­ŅKcRÖm"v%XføûHØģøxhl ŸAi™ĖYŒfyJŸũ{ :ĒöœhÄá l2äđ88.& ķđ]úö‹äâģ…XoY|g5ĀG“$žIŪÚ!;ˇĒ!‰ÕnÍŲˇk>kËō,H~zqĖ•‡ÅōŌcŖÃ´yĘ3Ú† ÃØŌjōԓâČĶô1ūhd÷#|g­ŒŸģôcūyViîQ\ôUK§WzIY(•ZTųyv*˛;(gÁ/nfiUK1‹ĸ‰Ī(xn E+lōîŦpMáęHbŠhw΄8]]WüJ O;;!ØGĮ¤ytÔúlûŧôŌ‘ŨŠP|}—‰Í=îķŗNĀŽî*OaŨöĸxĪqC^Rĩȍ&Ŗ4ҌGĖecą$žŗq=/Ā<{g}ų˜Fŧäbē˜ÍÍķÚ˛rį7"MȘYŪĒÚąpÚÂχU6…‘]ËqS‘§Gž‚b–Žâb~Å#j}DĶŅ.ŋ›3’ôø“‚.&ŖŖXØĖČ(Î díPĘ&Š:ŗ ēP~¤ĻøŊM2‡čfu>‘3ŅŪfwŋ—L"Ş´X@g`dFΊé),X¤ą›}J”]R\ĝiáMū*īΞĪYŸÜbÎö1'”“,Tšöw’‰Ĩs<+M<›{Ë Ōž?Įd¨ZĄm!sŖnŠ‚Žƒ#.ŗ]mwĪü2Ī+gËnYŋæ’u늞‚žXĢŖ}Ų†öķĄē/Hn‡ŊÛ{ĢëŨėĖ ×íH$,ūųŒ”|´¯E'#ųšŅh•_ÔkĸųŲøZ™ŨwRc›"°]0é”$sm2ĸĪh[1ÜÍ(G"¸5Ačŋyëƒ$}jV ­OŪ!ŦåīNåëņ'}ËĨ­OđĮ˙ˆōĪ`A¨}Ōh^iœŦ@ũŸSĀ>Û/yōŠĶúÛČ~Ų'žČ,";=…ϧ žŨsÆtꉲ9ķ íŪĨS$¨4=\‚$Î5būų„ }Æë5/]sŠLDi‘dLžÄ›mĒ)gąÚęˆÜj8Ņ(ÉcŸyééœRˌ-ûČL§'`¨ü*EŋŖöe}˙$míiŋöSG˛îÖ7÷Vú }Ų­Âsk•¸GPoŽD1]‰ęeęî´MäÉDĶ9váAÆDiš‘TšaŖxY4ŋaĶ•Vrū{ĐŽ­ĨÅšLj yŠoĮ*K•¤ā2õ´Šé8!ßåĨ?F+TĀB„īŲ ž[aŒė°Ĩ¸H™Ĩ-Ž:ÂwŦķf_-Īæģ}52Áá0—›O÷ŠĻ—{™ö[_jYé_2GŦũ+Âwv­Ÿ¯ˆfí}Év߅ë›ĸPÚ´éÕÎpŸUk vPƅ–öcÕHÜ[Åņ`>3#ˆ/MGĻHkevPPĪQ+c°›Ãqܯb˜NjÅoIēø(JRÜ$žGRÄ{j°´æw~ĀŲéRc[• ŖHųŧƒŒ”â<0§;DæōDčĖĻxZęÂĀŅŖ K{ąŒ†?0ÚZ‘'Ų Â8JŖ Åt­LY}ԈuÂelĮ<đs:‰žëËįo ŸÚ^ÎS#ą<:)gßFæ]Íc[6MÛ}WšČ"˙áĩĶ "˙¯‘šh?ôwÜD;+ėzI=ģ˜äąÕķOņ—’ĸ€-e×YŠ0ō’%9Q›úˆŗ‹Aû￯tˆOĩˆãĄŠ7YÚ|yЉŖ é•Á}>gžÖ™øûžĖt/ÎŌkÄ&ˆ6ĻiNQ-B§4ÜjģÅ"ŽfVDˆîQ6ëėŒmŽŅY'd§āI#¨Íæ9ÁGŖMųV/{´’^į,@BWęs=ŨĒ}ްâ;Ļ=äMŌōWU~KŗßŽ/"ΚæõpVkĻŗɋŒĶ$m—žM‹RD4f_hĄÉžjûíĶĐĸbŽbTxHĻÛ<ĪG‹įwę/tĘ Ģh#īôü^ë\뒆GtēŨįļiä 42¨˜ŅęĀâ"sU&éhŸē“žū´?o•ætÍåķāâ+ŊF%>Ģã ÚĐ[> déîÕŌ{K˜d;˜X¯ĨÛ<¯‹~J(cģp ‰‹ĖÚCd"Ĩ=íSt>­Ī~¤æ†…1^ĸõÛ0~-÷3Ķęšŗí¤ĩ™Åéeå!’w­ææÆŽ`ô#\™ÍØA[;{Įûē+SЄ Ē<ÚbŽXįÛŖéq œŽeĒãH×MhåÖĮäyĨŊŅŌnCĨ!Ŧq-´_E‘˛…{ėæ Ô9rÖž¯ũVVO艭Ŧėg,tŽÁh“)/O^ū"åįņķH"ˆt)ĻĢFū.Ũčŗé/ķ[yÉŋ}ķą0…ōšŖ•<Ũ^R>KZ(íŗ)*ÆOõ}“žŌęų6gŽ]ÚëãæŸūe;ŖBëėšū 7ˇÂæÅÅā4æ"}Xâ/Ų´ëÛŠļQmÔ0ĩÎÎĄ ‹5Lûžæ­{z”Ë#,Út›g>m<§ī,R~ ßoõ–L|—B…õD8Ë—‹ËHĪB 1?īĮČv/m|&'-7‡5RBũjNÉâĐĘgžŦ^ˆj*2ŧ_ē•Un9ļŊ2‘šŠī|ųë_säŖōŒīPĻŪžĶ/¤´t;fķā¤$Vo~ų3=œP"Úkļ˓Ũ^qĪn_+7›?-ŸØĒ{ŖWétĶCĶōT~°QĢâÖHēV`ąą9vPđ?ÕΈˇQņHtž‹c=ƒãø.ō q|aĀųéqĖų.ƒgcūMđõ‹| E€ |g^ĖĀ}ü×l,–JĪm‹ķך2ˇ¸Ī!hĪOˎ{šåā ŠhL&^nŽČĪ“…)"Nœŗq‡áÍcÕd- ^ķõJč’Į)iƒMl A+WE0Ņcåąrx@™ĪÆjĮ)^æøwYė6 fļ¯mwC‹˛(^ËËcuĪæwĄ;Q)¯wŠŠâß<ųĀt1™§ûiZ?m‹õÕΈĨ6ũ‹LŸX@ŗ76 Ũ¨_z |‹7åŲ{›ž‡īRx$ÃˇßëÅņHnLĘâžšPũš1id}¯‰×ÍÆ q÷CpŸąĻÚÂĘ9&ķ åc¯Ų¸ õ-‹ŋ2čœv/¤WdbËÉĶm—ų1žš8NJđa5ÄĻ•‰åˇéŲúiãn‹°yŪØČâžDÛåĩË|ŧÃ=Ž‹Đˇ+SOũĶũ<Ų™˜¯ĪN ģųØaI{œÆ#yø.–žŋˋ¤=jŽoO`ĢČˉkXoė„Ķ<ãXŖōsüļúķtŧÉúöŦhT^<(VŧŨˌ1åÅRč„3|{^üÔâĘ<:īŨ”(ĩ8ˇ*ŸÁnyĩÎAčÉ\ĮYf4Ä"2ĢZ6N‹ĩĨtŌÆŖķ"­~¤ĨZšƒãlą2´ōΈ­ĪĮtļ–´!ķõŲ u6ÖíøRdgbv2Ęe+čĐÄ#ĮėæĮ8ŒĮÎĀyXcY\`"bę]ųĀX\ƒ+Šq¨Č˛Ė]ĘoĮŧ´”c’2°žųV,Öŗë ‡K´ŨđN‘8ėSį‰aąÔÅâ8+CĨæDú™×F<&MˇŠ4x.v‹ŠˆÕŽ˜ĸH‰rŦNĻõ3ģ%csé:Îí_iÉįaˇŧ¸ü“Œ]ßrŖĨŦ˜[qßú™&ĩģ&%ļ.ė>éâ+ؑ¸+]yÄ1,íĨiÚä7Ū –_ũŋ‰…Å1]ļ—ˏÅügNėC"-ę%2ŠĄ¸8Љ§°öÚtW‚ĀeJž4íįpķ0EĒUæ1lIÉļEœf‹™į{ ¯ÁĖoSąČķÛ}nž¸ ПV÷2dbå“#̍ED2Žæâ¸¤†Øļöe$ôA͛=¨^™ŌōtráÜtąNŧ]¨´îŌ^ļĮúŦ•y˛íŌ}?_÷îc[œŽjt|fÅėĶŠįQ‹ĢÁbŊ‡Ōâ‘ØŦšČ Ž<ŠđˆĸKÅzQ~÷.zÕ+Û_ŲuLņøŽ×ÔģļœX™&ŋ‚^ã3ĨâČøXŸj*˙!ĸ ­ŨË~5ŽôĢÜFŽī“šĄ#ŲFžÚŦįrōĪm‹"Øđë.•CE ‡.[æ>“:fë+ãņW.íäŠ-mdîå/vJq„× ŗ‹#xßĀN3Xrōø)ŨŌ•ít/Ōį\ũŒ´]9ĖÖkĻZoÅúWēíœÜØ"֗ŸĀž¨ĨŠÛEžKÆėd_ .Č)GüC֋0E..ˆá8ö<9ķA‚UOeōįŌō–ņiąŊû42đ]:Žgš"GQåP˙á´kÄqg^ĘÆ€"UŠ5Ķō‡¤ŌtŽ™‹ķĘLˇéũ|øž-Ę(€ËŦLŧ„#˙• ˛2rļ8.)׀,mAúŌ:†ÎÃûšģ<ÚTZ†ãƌ3æīa(ĸˇIũ—¸‡ëAĻ„îŽō8Ü7Įižœ>~?}(¨WŽg9͗ŲXŲą]Tᑯ\OgĨädųŌ'Õˇmą€ķĖs Įšwsp_ęö[įĮōđŨ=ą^–OfũˇXĪĐ1ėf1 †ŪŗkKŠ%LÄū(•tÂc~QIjĶîãh‹ČWLēbFN7åʼn÷ųVŠĪ8˙QM:‹.†ÅX¸*áÍzh°›H=ŋ-LƒŲ–)Đîģ‹)/ĀtfŦcœëØ~\bq\-=+Ų%%Ūįá¸Ô#>æxJ;ŖČNm w;ö>ŧΎ]…ú˙'ŽŲ-°Í2j0¨M=@N\ ÷Yė–ōQą=šEđÍSÄg*ž2~X=ŊāDņúĻ}ģ ‚xN÷o$‹Īįi’n ‚Gyūë.đ#ų#Ūxpn}+íÉĨĖ´×ô”ΎéķéˇEøw|Fōá+Ģ ZōØVËk‹¨´ŽķąY/ÄXl×4’ũJ#Sá2­ūÄÆQÛą¸Ũˆ$†#¤Ÿ3Šãģ¨šū(–Nąˆ§ã}ĐbŊ­ũúI1på{_d͍ŅėyvN–‰SOœ-ˇvũ‡Ņwww777ÎXD>PAA?Ĩ`‚q{õ ΏN¯ĘIŲíáԓŠßĖ’ˇ‹ŨĘ œgˇģ×;čī9ŸÂVŅųĀH‹ĨĢÕj•Jå§l×PŠ  „ĻĶéx<{'Zh‹v{ûG‡ûŖ)bvģ(=Īîå ģËËËzŊcg#\A9‚‚~b ĀūĀØŨŽFfŗ -Rd;į†ū‹Īŗ“ČˆŽØ(-œTĢÕ`é~bĩU HKȆz}ĩZÜEs `J”4÷<;3ãį­k÷Ÿ18uŋ AAŋ”|ßį1)ĐW4ŗŸIûŲXŋüŪąįŲ ‚sOžy13t/ūĨ8T6HāuIāvzģŗŗĶjĩŪŋĪO<”Ūëí=°.:S\l|[ !ĶŨlįœgˇx}ųÚښąĩ”)\ėâw}„ æOË Û9B<>>†¸›ÍæįΟ%ÛÅÅÅææ&PņęüųįŸ~VÄÁÁ-M†ĖöųĀÖ ¯ üčF[Ũ­Éd‚ūõîŨģvģŊļÖZ__GīFŧ\<°î­+h["dG/đ–-ž;ĪŽyņkÜÜRõä¯nErļ}ÍËŗ(=yÚĘŧü ‘ŲĨš‰?ÁĀņ-i'''glívŅ0—××hĐōæ=.–ˆū@#,ŠÆķĪļVx=Hāg•ĀÕÕlŒŦÛöö6 ē°Âîîn§ĶAŒj^ŨHđKü531ËūdÛ ŗĶElņ˛ã¯™ņđv4ŽËt:ĖÄöÖVcm •-y.‡ÃNč¯Ũé\œŸã‹„ gô|ˇŲíÎˇß ĄČjXü Y˙'ĄaāúũŠā×ī÷ONø×_mllHāÇWWCy íqūå‹+Œ%øŗ*f¨WĀŖJxBFKčSčČxĸËFā C+|  ķ›°_Ŋ^oooĪÚģŖŖ#¤ =ąœļāøOFŦjÍü~ęԌė<Ž3Ģö}Ė.ÛvF{-Íú{×;::hļÖ`MšÍ¸—ü;¨Cŋ?Q.ä‡Oįˆ– “…šcŋĶōōXĖĘE蛛Ëĩĩwō ,)č%^GJŊžj-NDãũņĮx„!>Ēz„‚~ ĀHÁŽa`ģv{{ TL€Nĸl[[[@yģg‹đM Ö FB Î`Đ¯ņĸ]aÆĨĩEŅæ!?jdãGČÎĮŨdl[|lŒœõĐá2>‚ˇy< % ü$øøņ#z=ú2žø!ZL‡ū… ô2t1XĀųĩ=<ü tM„Ôņ}xx˜l2æ (Wz,hgcu‚-žÆėĶ‘u´åė¨,B–1pÚáÄęĘĘŨd‚waį y5ũ^\ŖÂ„k™ū\Šå'DāąXŲaöWōÃ{*=Ą úŊZ%YZN›ā+•JĻ%üIT6T#Hā~@GFčIFŦøŽ—]Ũ\I ÷I¸\ ßüÆX~€˜iéŠÚˇĘ„›î% ÆFQcv‘ŋŖtDōôgBĻŨi íN›įîđ„Š–ņ&D'ĐĮ›[ĩLP“<ķŋ›™ˇ@°§p xÆTJx„ Ąád€ËĐ*HD KĐ<u§ĨocvX˜"€¯ŖL1Íá$$füĐSdŌ“ čbûũũÛÛl:;âõčė‹d ĐĮ`(đĖËPÄ>h\Ëø#ņ1ĩ€%ŗLGĪĶąqTL—MûYGųˇˇw1˛ÅZx™—A ¨ v°>ˆ”‰nČ ņHX(ą_yßMBëV*îīcî%KdŲ0؊fØØčāŖ6ƒ" ¸šé7ØĘ:[ Ų!˙ņŅ…67AcéPĐō  L  wĀŽņŠ“1b>č€W៘ŲÃvdĨpƒčŪCä#Á‚%ČŠ'cœzĸ'ËŌŠ'ĢÍV„ėØŌ)ޞ1ģÅøn.+nįc:‰ ŦmČ$$đĖĀTđædĶŋZ:TDf! ÚĸäIÅ"ˆh[:Ø;B|‰ŲiąEc|´rĨŅĐÅō•Bcō`īžY…Ãį‚~´–?°'ĢĄc3š0fĮÖTmęãĶŖŅ VŦđʕŗ"FLsōģĪĪY øŖ[$|?H Hāņ%K‡^ĪŗEíOloŦØ.†vŗģšR ŨJšV)Ŋ˘øBĻ€#P™c‘ë3^íĸ­)ūŖš#W#UcĨ6s^ĀĶ~ÆÉŋ5?˙|´īڜyeæ•V„‡"yŠ”_¤¨—ę’GâåEæôõÜv…:üŒũŽŒ•?ϏHKKĖŽžšŸÄė~Ģ7Öž]_ŪÜâ/Ķ~†zĨÜŨŲ9ŪĮŪ´čžĒ@‹}98‹•YĻ4Pü[ÜOÜ:Äy´éWǐԕø™*'/OŦíĨŌWÍĶ”Yč~›?Îûֲߝ_ß<9Ė•[†lm~[æ#éÉcéÛ/VŽœT,ÆĶ€pdėV›qdĮ¨Čnk§wôōfc‹õ_-X@ĩ9´•I^ūŧ<yˇoEō<„‡"īáÁæųÕtėÕÕČîø/š•1ėŨÛ˛Õ5,ôˇøü)Ŗ ‡5-˜ëåČA°Ąü<í1`Ä­đl÷ Ûũ*úWiũØ>eĢ\ūÜ<úŽĘ'â§ČŪį ū•įˆ‡ŧrT?SŧåĨ/ā'ÅŋáMDmן§éĐ_~lĄũūSKGĶŽÜ?<˛“QdĢ´ÎnīŽŗĶ>ūILŊréŪRyŽyČģy /[æ˛ųSß}Ū[øáŧ€Cv:†!;ã˙ŊoÔU-ęĮ GF¨÷ķ é?BV‘Ĩģˇüë‹īKÍ}¨O4é¤-ŽÕ#ŦW`_ŗÃ;ŦcsÅé…eÎįĶ+dĸ.ļSÅe•í-î-O+Ÿ@˙û ÉÖéŌaRRą¨?uoŦŅ…Ž?X} ´ôŊŸY|ZVne7´äšß~jŅĢÂīzû›”š=ÕËęj^zJŸ­AüÉÛô§îË2=¤–Î7¤ŋƒ‚Ž%áHßĮyĩāߤüŌrp—Ī“CNž|<ũÅĘvŲüąvÉãŗ˙æÃŋt[˙rpČ.Ūu˛#+HĮ1š…kŽÎÁŋå∟Ú7>Vģ/‹•–Í˙X|†r~*=7‡Ø™!‰;Ī3§-ãđ†˜öģāįļ :đētĀí ``‡)W5än6vt;ŖxÍí•qNewgĪ­ŗŗsUķhŪÛ@Ą—WˇŸ!đüz÷ĸ„ļûéÛnEBl<ō4kæŅ2‹ÍtΏâuvēƒ‚7Pč$ÅÛJyķO5v2iáíz&ŋÂŌÉõBvč   J@öÆĸ~žĐ‰ŊŠãÞ=3„]ķßT˙ąúūÃöß˙Ž–…ž)Ŋ ūåŋkã,ßßŋ+§°Ŗš4Ž™ûˇ˙öß~įßß˙ūwŧžAAA"ø‡ĀIv8,ļč÷ßg|ˇĀ.ũËŋü˙Ã˙ī˙ūόlžå7ĨŖū_Ųag…ßķČČnį¸ß+bGqtŊLW™sîM{x=H HāW–Ž<yžķˇŲĖkZd'ģÂ Ų­6é ĩŲ=Ū×s–ÎŽąJŌ¸M÷ÁŌEĮjZėh45Č$Č$čĀ’:K'öŽ,U4ÖĖĨaÎ|@MƒsdŌÜlŦÛķHcŪØ^?ŗМĸqî ė}ŨˇßüMƒjMh/ä2 :t`ŽD§”ģõái[ä°ī‡qˇQ3é ¤[gį(r'Ņ;/°Ŗ2K°NĀĸ˙zIßĨ’ r r :Ō9¯ŒŦ ­_l—Ų™“ÍÔÆīe+ĮĻšížËlZNÔSģüsđĪA‚<иģVČŪąK~.„sÖScv‚áø`h.fg÷EĻhėüĸ“(xįv;ēE¸/ä 2 :t ¸ČØ‘€kÎĩKn">ā,—u6V–ŲÉđ3~]42ÍĄŽ¯¯q1k˜Š](¨!H Hā~,…KqY‘ķŲ1{‚uvæcxëėülŦŊēs~4Čķ9´EvÎNëÜ.(Đ"đ—"\ž‰ËČq÷nŗŲÜÛŪæĩšĪĘ.’ķz‚ Ņī­3|!]¸ŸNégŽ ^VûūÄüHĖŽCvl‘8r—KKģD#f/ėÚĄ\Ŧŗ;ę÷Š˜á€ėŠHé%äF°/ÛÛۛ››Đ› ū='o0K`ãŋà ūˆ†ĸ^ €ė`™ž‡oëõ吝›l%d[gᐰAÄ_įÚNļ¯ŲŊ üōûˇ‡āDØ5;üPgnnŽ#Ed5™Œˇļē0xb?Œ`Ļ““ãVĢŗ(ŽÖęää)H Â^ˆÂAëÉtŗYŋßo6ȉrä]h•`1)_ĘĀÜęú¯ĶÆ ¤#ĪņqôõųmŠ"–2›įßQ ŌQøÃ0ņž7ΰ­īCdŪy"9hĖNwÆ.ļK>f§9ąßūŪØņ´4šÎpEÅ+÷t°Ë3°zX6mfcÅ>úŗđMxQ2Ámí™mtppķ…Ë{ņ<88<Ђņxr~~ŽëÜûķĩMa›PâūūžÔ¯t:„T`ōŽŽ$Ârxxb8ŧ‚qÄÍĀHĻC™xâŸRž” › Ûéž.H“î€IŌ¯÷zn,[¯`ģaRĪĪÅøRĐív é(įōō ƒÃOŸ>I9`ϐÖY°íüō_T;ž4Ŋz~dŊŦ ÛšÅv ͉-a¸] fmĖOūšuvãÉt:™ņŗ4•õĮÆÆ!f÷ŠâD°čüŌęô˙Æû|øđ‰x^8ŧŗ°ŗŗƒWÖ××aõ<^ÛÛÛCN%"¸¯ŨjA#×ÖÖ.ŠĪÎÎ`ûP \¯'!šŨQũŋ§ņE|Éôõ 2X´ŽĒTÚÛs_ŸäÆ=WĀhGG„%åøč¸ÛŨ’r`—ÁÛÆFg8TŪđEØ>ämĩÚŸ&dōt8åéĖˑÉ=bvˇ“LŦí…e/¨ëėp˛Ķ]i&O^ĖËZŲQģÎ.øÆî˙É1ĒKâ#¤WĢt’a´%†ą•ßWÃĢ:Ŗˇ(†Â?ÁD0Ŗ­vF đ {ĨEl[ŠL‹›|™‚Ũ¤lÂÁwAĶ×'äŧ-îc¯NiļO{dēf)xĘ´…”#&žZ­qáúE l5 ŊeØūÂÛ.ŗîŋĪrē0ė´Ø.é š„­†ÔŲØ; Dąt| EˆŲŊŸ&˜čáüŧ{÷h+ŗą2°hÎę)ļōßå1y.°&'ōøô cCÁDH”2-˙ļO;+Ė_WUÎÆ€ķåĐín"ˇ–įˆ'ĸ^āĶ—ƒb‘r6@¯íęģH"ØD8O†Ø¯¨Ĩ^ŋĪ÷ˆŲ‘ūȕ:ŲiĖŽÎ(˛Ŗ§ ;’eˆŲ=žz9žC6LÜ!˛öūũû¯_ŋō´ųŠ˜Ėi ëqeh‹gi‹§ĐyŽŲÉę“ÅvÉ";ŗ‹¯ŗ#ÛĪQ= <mė¨Xē€éŽ :tāŠt€Ã!ŧĖną]˛ČNbvô˙Ņ:;ļq91ģ\|—ŗ ūķÕúΧđÉĄL†~qß~Ąh]Ļ[Œ5÷š˜–ÔEČ.ŠŲņ ģŗ{*ŋâwû¸¯Ü;f‡˛ËŠŲ‘ų 1ģ€G :đrtā‰bvä€BĖ.ĖĨ>úœZˆg…qÃũu`ų˜Yj‹ėėŪØŗ{9~,`ŠĐAŧÜ/fĮžĖ˜›‡öƘ‡ ë낝žŋ¯1Ŧûư~5™ß/f‡Ų ڕˆ9Šô:;wŪIˆŲ…õqaŊdЁ—ĨKĮėhŪV÷ÆĘz:Øˇh9īÄĮėøVZ>nŒÖ¸dĐÚōëėt='ã@ËÚà ‡ ‡ ցXĖn]"äĢ?ƒėâ1;ŲèĢídĨ˛ÜOS‘æä ŨGÁ)–šAAAŦgĶķí’ÛÆÖ s‚ėâ{cy/'VÛÉĶā¸\Zb€lG-žË¤Å&ęIŠîVÁ> ß}iyl]ünšW—HǞīũÕÉķĨĩo&?^{Ũ~Ą gÅkĪŖoŧ ˜wP¸ąf­gtĘŽXZWœØëvÅ T+ß-ĀtŒûØØqۛ˛ŒetˇŦāgã*• ŽÜÁMˇx‚–tü=‘ßžûŌhˆ܂yŠž q¯•¤ Ž{žņ5ņår$UĐļO>'??ëˇøŧåIĒÅq§ĩøķ´õĪ*Ûeę%ˇ2˛›7Öt;(Üéœŧúį덓ēčc5ˇÎŽŖuķė¨";wļDϝ#ūÄ.8̀’PNąø+CÃWā'MEĸņ;W¤^ĢÕÄŪ=ž“VKU}†—ĒķC¯;ŋ}@k:!Ķu"OÄ@Čļş­ŨŸC=/WŽŲ)˛“Ķ˜ōí’Äėxm:fGŨįgš-e‡ėŊcv L‡‹ŠĐ'ÉŧņĪkŒOÁ_ųōĒ—ŽīDī™˙ŠpëhĩŨb¸Ÿß ĻcfDŠS#UđÆføĢŋv˙)HüųđéKū–iņ˜œEæĐsßâß=y;.ŗ“ÉØĖ˜]„ėčŸG‹Ųņ-År$ˇũ‘Ũĩ?šäéáū'ƒ?‘…ŪÓ;›bųWZlßņ]AūŊTų›5Œ0€P‚—jĻLđ-ôUŊA€45ųĒ9Ēđ&Đ=ZĪÉDą'žČˇÍátęMJ7›`Rʔvœķ]äÜÜė ‚Žb:ĶJčÉ^<ŲžkGÄČyvEcv‘ŖÛcíyvrNŋ{>RĖŽt{;Ĩ™JFCۊįŅȉüö¯ĸĐĒŊŊ^ŋ %ģ8מWC!O\”åû3Âįįį¸GJ4ō^>$ésŧ™¸4ˇ.ē'ķģčūĐ*č"ūz?ūņõéô˛bŒ)|æķ3%ų#ŋäņōˤ­]ƒŽn}øŲBõ‰āŨƒƒ¨,Ņcûũ•-ߔxūå |÷ęæøoí;üōéĶ'hđđjˆįÁ§OÎŽé[”s!oÕŽÅˉˇx]`īJŠēRÅUŪ¸j–]ŠĘŸ§ãé—.ŲørČĸzĨÆ˙ŦVCŧ(Y08ʙ#OH^Ôū†[íīkÛYŪ đrĨ‚ ''§R&ūŠ[ß`ŗđO<Ž|~¨+÷Ļč€%Úũø*aÛqÎwņîÖÖ„€×Ŗū(:sÎz­ĪõęÕĸ(Šo†ŽŪ'fG7&FČίŗ#?Ė7´ãĶŗc'0N[zŽŋ2ķxÖŠšÖĘå‚ķl6[ûGGR~>=P¤/é¸Á^^‚ĩPĘũŧŗ&R•…uĄlü]E^qJāöaük<ŽHŊdNJFē ķCh<%Eƒ<;››x{}}}<Éģ°€rījģũęĢY*A‰ál‘ō† áKd yķÇårõǍ‹kĶ[Ā:Č å^ČÛŖ´cļS}“uTŦ“Ē?ĶÛ[ŧūļR+W`(* Ų…č_Ŋ†¤ųd„X+Î?V]]ŠVÃTĨyž/On…ÉūūdģN˛Íˆ^ šhn0æ= ̈́Šāéą<žˋœ(‚VÃĘ'\ŸŌvĖû.l(tFJ€J$t†ô„[ŸõäJrÂäåéÒ1;Z/LĢU<˛ķįŲQëĘZ9žu1;ģ^9‹vŅ+ę ŅMNÄ=™TžšL¤;MĢĄü3:÷ؤ#đ3ėDĶínxJ‰ú$B˙āËtZ•Ÿ÷Ũ…éU—Ébü†2WVĒh/ĨŊč-ƒLļEA]zp9ęąv+×Ūž­bcšäŸĩÚ[˙WŅvčIúģčŊl÷‹ęáÉņąLh@̇CŌęÄģÅä)r͖íh”0Á›×[qĘ:ģvIf 8X‡œ<úa åŊąŒéŲņúŲŅō=ˇž%“v7“SŲ6Gī:a"2Ēyž9åtųd93ēúcU˜9@bŦe,lÃØąĄŊ=JŒ| )ßaUæįūéÄÕtęîąĪâŸŋŽ;IP¯g‰īŠ”Â?J†6xÜäŋ+e&d‹Qß f0iŽ ÉV‹nĢA9°tÎĀc\ãy†™žēŧäņÔ ßbŖãSGÛvJã­1Īŗ“1­)Æąr°ôÚč!í›z—M—iņŲzkĢYĢŌOĐkk-‘rj‹ķŌՄÂ[ЄFŽĖm{Iˆ ā ŅęÄģKÉ3Sļŧ\†ZDų—īĘ2ŨLųŧ‚ũ%¯}:,]ŗšŠÁ/âķõ—¸'–jįõeąāl‘Ø%įxåÎ ĩû°€zģ˜x'ņ$l Í ąŖ"ŽÍjáßu#VMĶr?ąĸƒū,$š‡ņĸ:Tšļ„Ōč{XJŒčŊÎSTá ;h9đëĀڒg)yZi{Ū0×w||~điߎíņ]ü ˆ>Ũ3e5ŋ5ņ×ÛÛ<(ôpŸã€nė•ītõĸŠO&ĮG”3ŗ/KD&×ĨąÍīŲšū)ëSho,Ęõv4“æž§ˆ&a‘.[ö{ø#fxĨ˙ŗ÷3´ä¤hHY_oŖ×Á—BöÅ×Ŋ{÷n˙æōņ7‰ŲŨģ-x— GET§įÔE”’j›ÍO„}îĮ?äIU>’’§Č/ÕLŧŒ—eŽīũû÷2×V¯7D†ww1 ŅŨÜÄô ?Ŗ™•zģģXŊ߂™ķRͅžũãĮ€‡h;S‹ÃÉAiÅ÷Ŗ•áبÅ1cÆží§—Ã­#L„ŽČ˛€‰…vi÷āû\Fli`é`ĻÚõŌjŗÉČîúúâ†ÚR~@vˆž>JĖÎúZv7RTĐ-Ķ@ōpЋ1‰Æ ’qĄŽĐvō31ÔSûjfŽJ= ļDĒËa?ŋp‹ëŠ<ĻÅą EÖô9?ų ržGĖN‘mŗ1;Ķę,-9Fߙ?ĢöuQĖΎáņ F[ŋ§Q`ŊŽÕ=/6N—S€ āŌi:eņä՞8N—‚ ™‘ÍđđOüĮ=b"¯Ž-ž§Ž< ĄVގ¸úļįááIca¯ĨŨ—ŠŲÉΊŲÉ> ÆXnū“ūcÖŲ1n7Ģ–“4z SØ'…ũĒŊ­ĸšy¤´ųåüø<,{Š|ũøēXN^?/žíōõ3O¯ĸļv-ūÚũ'”snŋv‹Eüjß9v æCN3Ag€Õ#Î!j7+Əfœ1 Á ZÚŨĪr„ÉW:Č!č@ЁGҎöxŽ-˛6 ˆođ}ĻÉā1˛cÃį.Ātŧ›‚ßåûĢĀbËûķĪ/ã‡6zę6’›ÛA1oŦˇK2Ė!EÖÍŨkl‰Ųåc:ĩŖĮ t čĀ3čŲ-ÄwbØØĐÉ|„‰ŲYh÷¤1쀕BŦ'č@ЁĨuā^1;ŧ2Ēŗ1;ŽÕ˜ũŅųäŲ%袝DëÂ3H H Hāq$ŗ›g—$f'FMP›=]g7ĐŊą8_e%ZgĮgšéRâLz2ÖŅh…Ÿ´3•x€hí(öÆ.tÅ"ģ„8ä;(8ZėÕâŊąēƒ‹Š)•‘]ŗ^ŲŪÛķ;(dŊræ™Ķ;(Ėž1]ģR]XĶĶ›å^ų‘˙ ô"9@J+Õęģĩ5<ũú˛GoPōãJ@ÎCÂI×vÅģÔã7t°¤Ŧ™ĩęåæĒė øv=øŠķ‹äl ÚAáöÆōö°yv§ãdįVÛ=Āw‰§)ø„Ĩ/°~˜˛ø{!§ÄĘpZ×§.“Ŋ+,ķķJ€‘]įËĘŊŦķÆšl¯v?1˛Mev‚Ũë‘ė]Ģą˛ģÁivolđ“O-œu#Šáđ^ėžēģ ē*ۊą§ûŠ[*”˙(¨đ.ČÄŪØ…Čn4–i ÚĐ×n”ą7Ö­ŗ“Ķ/ftéfcí:;Ųō/k[´_ggįVÄ{†įĶIŌÆŲŲ|Чėͤõ“NČjˆ#°dŗ•û ^T´ôĩH k]qģDgađˆ–wPpYÜ[ø:Yˇ76š@ĖΞ™įh+˛Ukhņ*O!‹[ŽĐ`~ŋJ 5:ãd‚^]]b¸Ęו’aŗ˛’‚Ž>‘~>ÎëÚ]%’e‹h^!f—ô¤bAvĸŲĻd§{,ÍÉ =‚ė’´îžˆ­ ×Ŋ™Œk-÷‘å@ Įûūx~‚f“ÃŦ„CÆåyø5.8ÎäŅĨõ}?r=Eģ˙ÂepĖŗE6]Fœ‘urn[bš‰ž|ÖŲÉēg/ŗh„ÎgÆ0&‰q¤Ŋ9‡ū üÃORĻ›cŌx”gßcȆŖ’ųĐ@M‡´oGãÄҎ|vŧĮAųījd÷5bFœYO"¨Vųĸ[OB'拇W]U đŦú0ĀÕã[[čXĒĩŊģK72öŸÜMŽ­ÎãOt%õí/ÃØģwîŨ_ĨHĖAwPŗKlÎŧžķBFv>ZGȎO=á˜ŨFyæ{sŗsÃČ‚ß•ūõĘķgÅėŲĨ$˛‹Åėd6V-bņąąxË^3~ ö †L6Z@ęŸ>€ū2âųéĶ'}—Š\Ė, Si§tëč3ųŪ—âĮ՗$–ŗŦË]‹÷ˆåa5ßąpŸw_HÜPŧ&nø† ņņÂ3\׊D¤°UōņMwFÖ8ãāĶ'øfĀ(8ZX¨´n#ąœu5äÛĘ—Æ†0Á¸6ŖŲ˜žĨĘÉßāB[ÜuōZtõ‘øŧOĖÎÛ%īLĖŽŒ?¯ &?p˙˜úĄ3hž­Ö{;ž“|)Ô˜>JšĪOW2Fķ¯Ũ=!˙,%)?ÂV|OÛXn¤\6ֆe8|w—ũÖSæ6Ĩ۝`Pŧ|Đ2ŪĻcv™x+õ)ØW]ŽôĶā#(s§Ķ°ĀØÁPŗÃmŲčąüĮeŒŽÃĀt¯öĪ€×Šö‡‡(Ņš{ÎĻL&bv%Bv<žå˜~~Ž#“–A"f'Đ… :Ŗ˜ŨpˆĻå[u¨Lt*ĄņÄ5M>ŋ\ž)īf–)uVčųĢĶÔO-žŋ‚M÷Šģ‘āUƌė^íÃ+đÍzÔõîĄĢˆŲaLssŖ1ģŧūb1ŖÔ@rww—§˜eö¯Ÿą/Pā†kÍwã|—I›šę ķŽę:;‡é2bvlMu]‚NXPëĶ~Éû"ņ°txŠjr˙Œb)в_Ęwų¸¤•á<ÚÄėĐ%§2ë:Ŗ.Ą‘Šbëī`´1­ņÚ1â8\nÍ?ļ„īâ!S5HĖŽŲ%ä krvvũÄŖ{{ģi=„Ō"ēÅūūŅ=O´)ėvwĸ+aŠ3ÅėÖDķÉd&ô_Ōíø`C×ũũũÕÕÕ_˛_dÄėŲ%qŌ“=•°8f7ĮŽräĪ㈘ŸLø%ë‹>~ܘ‡ÆāųņãGöNÜēŅÜĶÁöö6ā:x6öWķcÅëK‚(ƒÁĸˆ/ C¯.wŖuyĶ)\Ëׯ_Š„¯˜Z:Ū÷ĸđŒT‹…°BS.ĨŌjŊŽē°8ĩÎNt/`•°rā}Ģ…ã0ŪŊkK̇ģģÛųŠ×Û­­î|œĩžž~rrŒˇ6××a‚ašlūtą˜qw{#qXa˙e07úƒXĖŽ ]Ԙ Įėüyvp€+¸ø7 ŽÕõŪXųŒœz’Icē Ŋşgįr…˙>­ĐÅĸyDhī/œÃ xG+i*č§\ĐnōĐØV"_ųÁ€ÂÁg?¤đîB  AąÁo0¸•¤ íŸzŌŋ)Cū6ŽxjĩtĖÃŌ r˛Î.˛ty´ŒĸŖĮkŠÁb~R9ŧōøÚSâÁŲ”}°L}ÂŌeÄ7 ŸŊŨ]XR H1˜Å¸2čų“÷w‚eo-b—lĖΙ!ŗãv7Í9´]äŪxŅ7Ė`ô:8;Ķépb- Š/š—]O$P/ÄėD¯íGÛėfīdĸÉÍßē€L’ á÷*$đX1;ēƒī‹Ģ[Ū;A+Š×lÚ?ęī.:“…›ą“Šĩīqė<Đ2‡äätā:Pĩ+—éŠZmĨdķÎgwwPđŽB#YœTėî ¸ž>ģaVfcąUŧ×ëīܞ3ĢķŗŗØíb¯ÂK&ƒ‚^—Ų™;(ōׇˆ]ęņWŖŠD*đÅ;ˆąÆÖŲÁŌ-ŗ‹ÖëËØ8<ƒ‚‚U(ėQbvæ<;BÚfo,ŗĢ;Ņrhá 6÷Ę9u~*ĐAAA–Ą(ˇ_[ˇéÉÎũ|ģdÎ.ôŸ÷ŗą2†ÕSObëė×¯mIĐąuvjËB ˆ$ZgˇČ.Š}ԁ,ÎũIÅ­“Õv˛§l>Ļ›ęwŸ4$$$đ„0ëėæ`:?—Āk9em–XĒä:;ļwfŲN6jyOˇK‰ėođBAAAO!ÁU˛*sžEŌõD~ũŠ´āÖŲؘÎÆęyvluTœCģũ†”“ã}>Zhö'A&0 úôáū:Ā{Ry‰Åė’Û˙Og͉čaņÜlŦ[wBũSöÆ2Vsv4›”čVĸS^g×Íū„ådätāa:āė !ģbvÉÍičړŒ˜Fî8fĮqž[ŸŊŋũ8ČÍbt Žîū"vɝŗŠ8ŒGÁdüĸ˛ĸĪwí ø¸ËëPd5JöĨā4œĮƒÃtļBVЇįKāz<9LGtyVø ŧz 4ĒĨ­VšY§Ķ2Ŧī ĀI3´ƒ‚F˛ ėí čõnčCŨM;(šM=Ī.žƒĸ‚Ŧũ[n&C^߯"eĮ#\| 9šQ¯ÄúüŪÃ@‹.>šŽĮŗŊĶiŖZ^ÅĄvOlUCųAĪ áh6ž”úëPi\jžė/ĐrŪA!įŲeÛ"›ž÷éhߝg'šå<ģ‡";\ƒŨČîy-Îö î{Žt[Õfũ­Ķ eHäAârx-:p=žčķƒÍZÚļÆöÆDvũŪîÅ íâ{c‡#>€bę~o,÷—yv”‘]â¤âGÃ/æáÁÎáxĢUŲlÕßV^ũø%T HMgƒËéņÅx°ƒ¸’6ÄėĨ‹DÚ%ŪĢ'‹ũŠī]A´Ž,]jŨŧšŦuvaū‘ÚâasOdXöh?t– W/Dâ`hČĨûa.woÎüõ!ŠåŨ*9sˆĘ¤˙$vPāútžš {c‹­gq{Öš˛g^cß~AĪ-LEūųįŸīßã>ĸ?đTŗš÷į†ÕYtÚ=Ŗųč{¯ŗ#4&<Ņëėߙuv’ĶĮ>tzäی+”pI¤‰ā"8 x%Ũæųá´ŋÂãō6GV~ļč~ęÕ\]•CéŽĒš_™x ^R,šūĪn÷?ūX]ũˇŌd=˙pN:ʗ'ūj˙”ūŽMA¸ß ¯$Ūē7ĢžŸ– ¯Ī‘įŖ”˙B ÁśŨî&˜Á ׌Ģ×ĐĩßŊ{G×}4š8Ŋų!|’å1ĒÚūH…ŖoĐŽ<[dĶu—¯C֞ Îí L[g'Ɩž‚ōŌ4ũrØc\Ž9†vâfõ/įįøŽ€ÃŨq˛’Îįy 4á[ĮķKā§ Áu8Lŧå.ZNkgž\¨Į¸•/ĸÆŊÔˇč íõöˇoßpÛ4îßō>†bā´t<ņWô\˜)%§ŋëS¸Ā÷(× âĮžķ ÂÔä>CJš/Ī…r{-p =ÚĢRy ë†Û&ĨĄáˇä:´Šwiéķ/‘~|täaaČū˜cLŦ­ ‹âÎūĖŗE6ŨÛí‘]|ol*f'#hŒĨ™Ålšm­_šĮ‘&=ízåččzŧšŲÅÍ&ø_wsķāā@æp 8ū„‹ŸđÍå¯ažÁS´ÛtĀ˙ :¤ĐįįR&ōãRí6zQĢuqņõäøØĶō]äėv˙„UílnbIŽ| ˇKŅ[íu”6üúUž…ŽļõįŸ|aĘņĪåomũiųétŠÜjŽ^ Úߘ@!T 7ĀJ"üSa‰ŧĒœžŠ‰Ļâļf܏…ŸwËhS¨`"‘ršDĘ@¨m5Vėņą ¯D˙áŅX1°ąąņ×_É×6‡[Š5Ėŗčųô)čNÛÛģ>|āåVe‡‡ŽĀŲlssķZ OoÁ­/Ä͐Cĸ^H?‚4‰ˇŲ î˙ÄĶ…ĸŽH)"OäÁØ@īķgíÛRdc=ŸÃĖöĘã*ŨF_/.0†Âį0Ŧä;ŧYā9ōK˜—˛á-/¤ā‰‘iÂ÷@=_n‡÷Âö-+÷>Ŗ‘Wøŧ$›āĶA÷ú}īÅΨ­q6„R”&Lįôaą]’Á)Ū.ŗƒ•åK ō1ØQ)KåĮGPbˆ€ę–ÂMŸČô ‡x‚–<(g4â>ā^o÷æōW7ĄĪėčÎ žų===ŨÅ%Ûģģ7ŖŅģģč ܋îĐč(×kSĸ|Ë˙ä­Ũ]Wžupt´ņá×/_°"Zđ,’š˙ŧ@ė™)Cėįûøąˇãĸ$Đ]˜ČڃšŠJĄ‹ÂÁ Oo• ĘÂ%ˆnÆĄüPD¯vō¸Ä) ĘsÍ9!X„lāä/¯¯ą?ɀWn§S)Ö[‡„ÆK9ö‡œŌs¤Ĩų}Šô´tiHã(áÛxŒ§gÆ~ÄĶézQ\ĨŦHEĄ:°G2"ĨŽp?üey~úô W[{.../o| ĐIˆĻ!B¸†E/“ĖöBĨ2šJˇŅÖÎNŸ›ûãĮžw]yō‘ŽäE ;‚ˇDsđ„ ŗžQ,Ic:E-Ä,JsŒtIĖüĄ##$,z[ )H×ü2t™§Įzį¯\/RČ.ąá$ûČ×+iĖnÁ:;a%ī|vüÉ­ŗģMč*ô>īō`č% w č !JÃŲÁôČį,ŊÖhČE÷ž@8ą•P,ĄĶĘ{KrâX:ya&˙ŨŧĻziéXz˛ŨŽĩW+­ĩ:]Š˙¨` (Մxļ¯|>üĶ#;4üÉŋhāËô´4–äˤmąļ„ox×ĪāCūč˙0+Ō¯Ōoų”91ÁŽH™­f&8Ã…õōEĄ„fÅRQrŲļ š…ō ƒUK@T”"BŲ` ķ8Ė,?ÎU•MČĘ× …ķXjS.Ÿ“_ž|äžõDëĀ Ā:<<´Â'<ūøëæí0~Pģ˙ō_ūKĸL˙OÄg1ļļˇ1€…4°>×čvZ:Ŋŧ=ų:>ÛĘĶŌžƒ‚ØāuvōËŗK˛ƒâjĖčÎŦŗ{Ŧ˜„Åã@C­]Ÿ‰ĨãķrĄg"ņšđîˆd>•Āc4ĒßŨ: $EˇC9ZŌĄR@…đ6‚/„‡ô-IĮO'°Õ´ßâ?úw_2fō÷؇&ūüęnƒúđŸreũúû÷~l 7ãQR g9Ŧa–§3ąi’SģĶ“ë‘ ŽŦä…oßÆė0āõĻa˛Ŗë_ŨGĸ/1:ãZ.cd ĩõōôÂzAVŽSCuÕ˛ã-oĘ>&=÷ã/ü ƒ åĐTÃË0ŗ|å "ˆ:T ûz4AĐ BŊûĪ“OÚԁ#ē“%„ ‡DXūōvČí B‹ČO@ßX'ĨÁŽu6:˜™Ÿxv66ŦIMĮėâöä^1;ūŽĸEü'ē7ö‘bvz¨qiĮai6,f•b˜°‚8ĩ뜰_QžLZՈËɤájš­&bWWCŽĄÉ§Ą7jŌp—˛ųn‚OËķKŖ)Î`â\04‹ēKëâ5…Žnī Đx&Ņ= Æ=mģkd‰ŒúûōŊ&!Zdg<ĐßėÉöûnĸGÎÃdûDt*?QN1G3æ˛/R܍í z]ŊŽ ā~õÄĘĶ'4"_ąŌX(OĀŽ´ÉLG˛šNkLĨe˜Ų^HTވŠlŽ<‡ž=Ø,īxĘĮ ÃpJ'š€1%Šä,.Š×Zl hEŠŋ?7ņŽũgŋŋ-ÅūÖöģw>P lĖ.̝‘ īŗŖÔv¤pČNæauŨũcvŪīlmÜBFwÜN5ŦŨEđ n41ÃßõīfŌŖ×ĒÎ1ė†fX[{‰ôûđႎ5gœ–€=2€Cû-K‡žL|'‘ßĩāWāPw„x<–ÁĀA:Œä„'đŗŸ^DÛ<žČF@ÚĨqĶņįĪžwĄ(Vt‰‘ŖQ@uęW,DŒw D‚°Ø;X:Đ>…ŪÛûô å€7üBÂ_åíN§)=ŠŽô”Dt<äANd›_/˜~oa1Ļ–ÉA đ"EYi,”gw“,^A™ļI<ω2 PÆ8t,ĻË1Ž\­3ۈƉōōĩΔOf#ā]ô -ôÖô"80$BÁ`RŅ.€xĐ4„iąQŪT‚iCoaōxŖ×íŊŊ$fķī|@Ē>$f'ļ‚Ųëė(Ē7Vz‡APaģŽ¯Žbîėė3-X_‡!.y`õđOĖኘ™qĨîËĄtrņŠB§¨"XYA÷žzåģč g` ąļæŋģĩ#2‰C_ĻĶē0¯^cāZ1xA_BīÅ"6ôAA!øv<Ņ‘ˆSÜßęVŒÎFvŠ$p“ôm[,äg†ŸLŖ“øKÎÚ>bŊZZoÁRĀĀ!Ez LŧæÉ‰ÎË<‰ü `ī?(˛fAöÂC žņĖzAEaĻe\Ū{uõ_įÅĄj•ė[ å‰Ā>ėÂd(°Ųd/ÎmáČĖu’ÃsčXL”/¯ƒ+x,åĘŲĘĖē õ?}ęIs‹ȓ—ÉææēdC#Ö ĻÃtÚr”Iİ(ĄRĢÔĶ5ôņģŌ¨ô^ ˇ?™ˇAd˙WpåÖŲĨú`´#bÁúČ.‰‰ķxæj6s§ždėåSOxi Æ e%O=Ą‹ĩŨ‰P–]~-‡ÎátĢ]Ûlž {cĶŨéĨĨØųœ—ÆÛ=øÁF9sNĪ/¸GąØ{q9=’ŊąŠū „õ}đ°Ž‚˛æÛ%ŋ7–Ŋ ũ߂Ŋąb#—]g÷2qĐkÁkÅų”˜]øŊ| Á;/ŋžC cīhŽ/õK„á–ĒÔSÆėd J8/~žbg+ĨFŊÚĮš´Ũ­9˜N°!ģŅÁi Ĩ`ÎមEb˛ĨtC“'yl|ô˜ëyĘÜL!žNũm5ÚÉNv’ŪõŌä€Ūt3ŌšŨ—ÆÛËáG<}ŽīĻ̓.Ä$ĮC0•ˇrž]]MCÎXS,Gė<;É:ĪNčëė–˛ë!ķRĀ1ÅŊÁŦ^)ˇ›Āüˆ–b:&<ƒ^ąžŽ&—ŖŲŅfĨ^3ûc]¯€ĩz”uvQĖNBķæ<ģŗ{šņGØģĪÃŲĨ.āˆĒū-‚rxMrhÔJ;-X:FVOŗËŨAą˙‘Öæa:Ÿîī X8ņŧx ™ƒ‚‚D@vøÉ~›o—Ü ōy3•;(’ëėčī´ãõž{c_Ë:ĩĀįË\?Ú%´K†<Ņ:;ąŖK¯ŗÃ´B´ÎNFÎūZüPCCЁûéĀ#­ŗ‹öÆrœƒ÷QđáOE΍ĸœîĻnõH´WGŨø:Č!č@Ё‡ë¯öågîؚ™įŲąMŖåÅÉķėdvÂßAąô:;Yw"ž+ĐAA‚<–ˆĨĶŠ‹×˙ęyv7JFÅ!fæ Ã\|Ё'ԁĮėxÂ!;Áwdīp– Ŗŧŗ ņ¯Yk4‹Įnø8õĮ™[ęģo/=žļz oK}k)y>ĸü—úî=äŸ)à ū+fĮG8ëąJ<•éŗ kŖõ’ä Įbü‰Đ_wšÔwžÆ“9/ÚîämŠo-ĩvõåŋÔwī!˙Lfđ˙Č1;Av˛#Bâwcvî>žķ.'d„č'‘îđ÷‹ƒNËYŽ=Å9ā''g‚æ´ÅpøU.cÃķüœ.å@~œĸžąąšļÖØØ{s¨MaøŽqeOĮŦĪo_‰á&ō‡ĶEũˇ’Č-ékx‚–ī‚ķ­­IäûnčŪt|Oŧ›Đ+°‡ŌŸKː‰ Ī÷Ŧŋŋ¸Ā‘îLˇŪã-á-ÍĘįø–Č ÷Ûloīđ,÷C%xȔ ×BĒļ#w‰á­´œ߲˛Jˍ×MŧÁš#=Ũ^ū“ÉĖ Ķ<€7\OaëēĶYG­åÆgÜIĪZq.ågę•mOã•îi \[!rËâ˙=swÅDåCĶúãáSĶ‹Ų%9ČĪ(Äbvü')‹O‚_&f'sūnC4o ŸHGĐ!¨#,îH‰œēKrn[āšv\¯)—§|Ŋ {x‘įÍ"×:īíėŅ|z_%:?nM:—Ŗˇį×.ķģ°¸ãčđīâ–K:j$ĨiâÚ]|sw‡u ŖxâŋÄ‘ŽŌŽš´L™€ôĪ/į¸s}ˇ×û¤wŽãr†ŖŠQš‡Ģ!]ųŠoÉIąGGĮm\“2nĘE<)I‚‡´Lp#j 7&ãøĩ5ē‚ßJË9ņ-[ģ´øîШžũũî—/hb'éļųãbÜY„û46;4¸Ėˇd1rõ w€ Lp'Ž× $Jų™z…6n4u4nĖø°ĩĨ‚=Üß÷úãÛ üÃāâfÜčāųGųrÃũÍ ]W–ļ`=ēGvĻĀZžE‹æīÉ11ģh–å+ŗŖÁ÷ä|í4‡kh,čG—î F„†ÉM•éō‘(÷´ãv7jĶšm{O ^¸ĘĨá2yi/¨ÎŨÆU­ö;Üz#íˆDč.ŽSč´Æķë•ų]¨]ĘÃüāŖ°ČĐgĘd\Üíâ-<ų-ĒoϜíˇlķäāë;üúwë@Đ"4ęNõ5”|:á&BŠ9>hÅåđJxËÔ+§ Tž§á&qK1îWƒč|Ą?–H eŌ ŸN¯¨|i#ŧ“Ō[ŠVąKąŊąąuv<;GvKÄėĀ c‹ŸQ_hņ™(č| e°M¸ <]~FQ0Œp"™ÃCũí[ĩÁā#áŽŊˇģ‹xĘÎÎnrū]l%IK[ ?77t‹Ä4Ō:ā0å¯Ö¨"Īx:ŸIžKf:eƧĸ։īÚ2-?™2ą¤tr|Zžė¤Ö+ËOĻL,™t&@7×Ŗ‘ã§sƒēLæķāų30T‹Sē[ę›)gû-ێ™r°õE4ƒečž6Ũˇ…ÔˇVĢīâðVÂD™<´Û밆P\—…†:â-CĐŦô.čLŊ<'“ņ7ÎúöŰ7ĖâĘDE‰“”ūHd–ø?9ņüsųtÃ7*•ÖBvd—V%fW–Ų âl™uvøļŸī žO*¸M( 4Qä5h'Ë“^ōy@wĄO0„üšE0ÉäĒŨnõ÷÷QŨ)Ũ§č ō#ļrx¸3Ŧ‘‚Ž"%ˆßNĶ™-îŋk˙*>\R0ˁ‘Åwņȇ¤ommŖaÔxusZr"˜áŽû˙–ŸL™X2éLxrfOä‰bģ„$qŅd’–dĻLđÖÕÕ ŪÂsk[k‘)gû-ۂ™rˆÕwoo08ÅŨķ¸ÛĻ{iûúb^FŌËäéø4ZųÚ%—Ļ+Æų‰;Č%=S¯Đv(|ŗÛmŋ{įÛWīĒīvųŽúØž„ū@DŸ?˙PQĪ?ĪÎÎ(bô6Ē_ŲD:g3/f‡;(hÔĶáˆ|;ŸŲŲ^­īõö ŪAĮģÖhLÉXōp„ã‘<Ėô”Ãx<•yÛĐ?ĢN¨Á–q4ãū}íúzûˆ]PO~^Ņ8“K• Ė#AiUiÚŨA!æ‘ōëŧ–æ(ø`˛œíYplŒ˛ü\‰ņä\šúŠ@‹Ī|9bî‹Č žĢõúŗ}7´õķ´/äüŠ×ģ8Įˆovr|Œ¨˙Cž;ß|ú„õ) õäĮę•DëØÎx` #'•ĩK2äMė•ÛÅ\ĖŽ‘ŨRccŲŨæŅŠėÄ¯č ‡ AChe˛ŦũxHĖΝYÂfPí‹(´žEvP(˛3{֜-gtŌŲÅ9ˆOrrXZ?ą!)ēū—ŗĻÎŗĶ3KdęÂÅė0’-˛7–ō”c8ސŨ-b›Cžâ~,Č*ČęįÖ†a‚ėŠØ%‡SįŲ):“î’1;öQ:Š.2–yŠėK y‚žxˆäžg—ŗ“ÕÅÅ}… Ââųƒ˛ :tāét oŲņíb4[ŪĀÛŠË)´–EŸš…™Ņ¸ L(ã_;{Z7ļ •-iņg˛LSš[NČ#r˙lŪ¤`)?ś&'øŒÎv‘CûRO“āĪ>ņ{‡Ŗs¸č4-™áYĀgŪ) !=ŨĻ/S&ņVæv×ŸĒÕwdŽvétŗû@ĮV‘&Åē‹Õíx?JëžĶEa.Ļ—YeĘdgŦoÆĘŒõk-ÍöИŠČ*GÛ×Éjîv’I¤“wžŨoõzãŌz"įŲ­­Vû8žuw+išr5ÉÛÉč{î\€ôYžqë`΅Š•’”ĩĩ˜JĮĨœx;ú§3e:mŦ¤+'V¯9ådđ`KË}3ü!Hā^í=Oë­OY\ļÁ Šrĸn”SNdEįņ“Ä.j+Ŗ2H7(!yÖ¯ŗâŪJĸŸš^mīĶŅ~ŋ5Jžz!;˛t|ę‰ ģŖģsÎĩv4ĐáLž A^Žd#ģÕfŗ#tífc î ą’+ :tāĨé@^ĖNfc5~$Ņ9õäás"a^)Č0č@Ё ŧÎÎÍÃŌ*`=ĪŽũEĘ%^N{ĐÂ:;ŗŸ7øí—æˇ?A'įëĀâuvÎŪ˛ ëėÜ|TĄŊx?Āw…õ÷Oˇū>Čö•Ëvņ:ģŲQäŽĐšQÁĮt čĀḰųëė†ƒáØd—ē7Ö¯mĄųáŲ4š!ļŗÅ¯’öwqDëi^Î|S˜û‹Ö4DƒŲŖ_Šđĸuo9ûšú—YM2ˇ|Č­¸Î[gĮëqZKŽŗeÃc|÷ĪOōƒbF#^å$á͜UĶ!ũŽėåVĄ‹åuØõĘto6ŽÆ‹uė§ė_ś ÕŨÜPŗŲËØšęƒyëėĸŊą„ė¸W/ŗ#K7k4°tņũd_oJš\i4ÖĻSŲÂR{`_’Đ.Ķ)žXēרiåJûIûWņöBû6ÖÖĐÖÅûāĸ˜›DänļLĖLÔjuŖmÉzÍKŌŨüpw@ÖÛŋŊ?ZŲ<Î|]1bĪ-ĮIę ķČ'ĀítJ_ q™——Aģ uDIžAøCŦoEtlŠūõJå0_ļFJˇČYD­ŗ“S@y^ĸLwČĩŖ“ÉÄc:€Fõ1EFˇãÛv{}x3ēŽOēׇ›üėZzwĢ5¸æÕ~ŅīGŅ%­Zą;Âœlqßû`Y‘K…M~”ž<äģ tlŠūåûŨl<žŲÚēhĩ†ÍÖĪvûjkĢ„aʏīS÷”÷D˛Š…tlÁ:;AvZ^ŅķėČčęÉđŠÂ˜›\ē<ÃĐ­ˇšëŨíÍūpķārŊ7\˙4ÄĶĶ'įŖ ŠyåhHú‰ķČW mũIŸō<F–vąŖĄ{ëĖx<>:>†zŊ˛|Î׹eû—”6:9™›Loëív}k ˙ĢļZwˇˇƒFc|rōüuŧwģ°eŽė€´xũÉ#Ūr1;sž{ņ˜ ›ģx÷2ũ8hčĩĩĩjĩvWz[ZŠV*õNĢąŋŨęoĩwۇÛíwkõrcâzM"‘ņw‹”˙¸y„ņ%î4 qŊįŠoęY5Š˙sõ­ˆ>  ­˙y0šU;›{fDō 2‹|7:g-_Į–í_ČKwĶÛĢw7k­wŖáđęččæāūęÚŋîėLNO3ûfqžTß䓓 ­{ī`kBæÄŸgįŨR1;˛ ÉsŖgËŖÕNß¸Ņ ûę—ŖÉūų¨wr3šÎēŠž´'oe”ƒ9ßD:§ˆ‡/”?ˇŦtq$r#_ö3ūárāŅQÍ}[ØÖã1Yēv{­ŲîtˇûstéęęĒÛŨB puuĩÛíŪ܌|ųõZŨ͘ÔÂ_ķt2ž>OĮ–í_Ŋ^õz•vg6ž k››­ķķæÉÉŨÕÍäæ{Ûé|ŨۛMĮsxCŊĀ?ęˆßÎÎ`ī™ĢcŽM°r+ŌŽh÷"ēW,fĮwĐŲ‰­ė¨œH#ˆ,‹VOŒ€MšüáŠjšŠĪágÃņÛJŠŨ¤:<•[N^ųŽČŽ ?ypJđl!ßžeQ!=99ŦŽīôNŧ~N&ŗÍƒÎFģRĢN&Ķ ī§ĖÔ[BL7‚muüēē‡N§ÛŨ$C g5 :›Áڌa)ˇēŨų}Áéę<[ļ]özåęJueåúâbs8llmĄ{vˇWŪ­AaîÆcD*õÚpˇ—ĮÛdBüŖv¨#~īŪŊûôéSžLžîåļ@V‚E‹â1;XäøŪØûÆė|ŧ‘ŊAdy4tĪV9ģcŌČŽ[W*udûn'|1ãJrRå‹G­7ŨÍMĐ ~āggĢĢÍõõõ1ÔÜ#Áy<Ģ×-čOŠøœįqpq4ž ^°P÷ŽOĮg—6:WŖi¯Oq+čĀæŸŊfk ]h @ôõæėtĐßŲĖĶáƒŖ#ģ.Ė%U*›››[[Ûî¤9BšÃáÎÎÎááaŗŲ,Ô+nĘÔ1I—ßÂ:BˇXV{{s9lîė”ĒÕŲdrŲŲŦŦ6JˇĶģéøëÅ` |‡Ęsh+Ž˙Ÿ> š¨戚ĸCíīīK]ƒ‹6‚€FĢÕē¸ ?WŸ6[­Á` mĸ{Ŋ=¤5WW5JX&7€bŊt<Ž­ÜË*’@Ąq•Aô,[•ŗ‹ŲÉA'‚îŗ‹N$V)_Rī§X¯Dļ~Õã‹1&ˆ šˇ•jˇ]9ē˜Ž§¸÷ģ &˜ŋÔģsËī÷ûđH777)Z(ņ]|exy gĩąŅŗ*Ržpš„? ¸Ŧ¸ī}˜ŦüxB-EBĮRzrr†iÉ\`Ŗ^Įb€ūū1,]c­Ką„et;ž¸¸89Ø^[kÄtՔÚŗauRāË_:==ŲÛÛ;99A˙Oę|žŪÎ×1§{Nõ/ÆnĨŌtRû°QšM.77kúėz„S*¯†Wû‡w°ņˇã ¯–Īė_Z3ų‡-CŅŋđėB:™ÃŠĄ_íîîĸJ™č}ÕrEŸŸ#ŋ|ëčč¨ĶiS÷ėvŧÜ ÉĘõĂc,‘[üibv2ĨĢŗą…×Ųąu73bŅŲëūlÕČ#ąp8ÎrG|°EŖ!+ž›­ĘÅÍėâjB)”´ÂRKŊËåC_ĄOõ=A{}…įAÁėy>]ĘÁƒ ¸ĢNgÍ0§|˙]ãQ ų“ĮÁ,Ü}ņpŽ|ĖŽĮ¤ŲzbĶ[kuëĮôõÉjŗqqu ũÁ$ {2š8ŋ8ü¸…~Bg,úĀ‹Xj˄úaЧyJĨĢĢ˜ ˜ŧ"üÔą{ô¯ņåÕŨd (wŲY_AlnpQē] ¯:‡‡ÕvģŠúŽoĄ^yú?Q¯L9îK˙ÂÎįˆƒyĮ•Œ!ԇ­-ôĩj­Ã'ß ÎÖÛŧŨn ŧÜæČ<’ŋCvĮX bvöĀų{Įė¸PœYŨ> ĐīŨ8ZG-0y’@ éyåĪ׹eû× ŦXŠ"7]ÅÁãČ"úãų÷Q6fņ˜ā;؝‚įŲ9īęb ŧžÅXúlš| âte†ve %ʡäKđoZyBé:ĩåGŨļëIbåcĻėkˆfŗ›ËKL™ o‘+•ĩĘ2_ĖįŌūäaq(|.`ÂØĐÄėrõ!­‡Gũ=āŠŅÍ-å,ĪŽ.ûŊ]X1֓ú°ŊŊ{r‚hû):~Đ4,.ų¸ģ›xˆĻ˙ņcoocRÕąeûW§ßŋ-•ĢoËŗŅlå-YēV¯Åããã›õ÷+{VĘĶōÛVŋŸĮâq˜u{7›MP_¤Hooąũ‰†ąΞlĩ×§‘"EŸ?Ŗ¯ûŽņē|ĢŗąAŨĄķ!&{EdîķˆVH_.ŌG|ߗˇ¤0ĄwP${?2†ķí(YPkŋ5;GrhI÷ø¨¤tSís˛cĘg3Ëá¨_VųˆšÂį`ĪđöÎNģũÎåqųË%ˁŖ!{ģģųåGß&¤ŽEüIČSDg%ŲE¤€îá­ãũ^­R §Gû{ģíVsŽŽZF„øøčøb0ö-Â\(’GøƒĩÎF×ķ=\Į–í_˜mīī€éŪ–īĻŗvĨ<ŲīÚ­Ų~¯2Ö*ŨLaūzLÔæô/Ė–žœü…Ēĩ¨šī¯Žž~ü¸+õúøņãf§ƒØæĀn&ŲV°4 Üx<ÅëČüîÅđØēgŖŋB†išåõkÆQ¯/ĸ?y1;š]lx:¤C‚ėÚÍzņ{cH—ĨVaŊSÁÜĨi€ü¯WĀąĶzŗĶú°‹L•jiûmÚ;ĸá…ŧ†hÃÍ~—âkš 2§Ė‚éؘüŧd9„7ĶAĶ†ģÖ{žģSpņSåĮą?ã1€†āŦ‚:āukîĐĢÛ-ŒæĒW u~o tlŠū%ßē9==ßÛ[-ĪŪâā5ē/p6])îfŖRĨŨī¯mn>˜įGčËņĀc@ôDXYEķõ*qž4¤ŗVÁz Av֜Đ:¸%bv˜1Ā램^ī]ķhÅJ°3Lá?Ķ ÅnÆDccžH§ĩ/âyŠ”Y,x’ĨĘDfTMúRō<ļåņ"ŗbR—Õ“îf§Eqēûŧģ뎿៝cąZĻą4››;——wíĢZãdR:+U.Ę5üskx?=.˙Ī#C‘’‡Đøg‘;("ÔīÖŲéŊą§C”Ĩ`ŦÕŦc:šČŊąø*Fō™ÃĮŌąØĀoÖī¤i ڏŽ?ą¨ŪŽwļUIQŠ7–ŌZĩYoƒÖĐx›CBË &ĄO™ OäOŧģ0ÍΆ )ˆōĐéü‰tŧ§Â2Rz(_ÍtNWmËĸ0n­ųē7G7ž‹ÁWĻ։.§W–ĪE:ļl˙zHØŋĘđŪŧIODƒŗˇZĘąpŒ•woėo˜ žŧž>Ŋ¸ņŖČVŊÖûÔß˙¸åŸ^r;éPĶŅø/,-פIe:_ėįøĄ#]^^Ō‚>XXģŦē‘Oȓ֙G‘ ķK? O^ŠâŅąŸĩˇ^Jî,t?ÍĩKŊOGũũū×›ŠŸŠhÕiųíCcvĒÀb OkVčˆqüõ*iˆđ–Ī•CĶ^{¸RTîöv„ËŨ_ŖžŌąŸ´o/œōö-VĮÆn}¸_Ė.Ųõ?õûģ[E}2ŽéDßģ; L0‡˙7¯—Æ˛ ŠL—Âō‚Ëãƒ{— |_‹̝QߊęØĪØŋЎÎ5§ĩh„9c:É#wP|e#ģËûÅėŦ}eĪ_”›×0* 1˛×‚gYë^Ĩî-Ąc?]˙*l+°'„VÂ-!Ģŧ˜]bOÅŪkįD^§ļ͑xX[÷Zæ _ŠĨ“É`z™Į˙éú—†ˇ×ĢØšß˜-â%E~6VÅ\Ö<ķādņŊą×ģSĖKĻĖƒ ‡ AžZؚųŲ ™9€U}Đ:ģâ~Јī y‚<ƒx¸đt•{Ũš!BZz…CvŒī ī x-`Õ A^šđš>5v™{cŨĐûÆėnƒO 2 :tāá:ˇ7VÎĀbØ'cŨeî ņ¸‹ :tāeéÛ1:CɞzâcvrFąZÄÂ{cnƒƒ 2 :tāņu@&\Ŗ9ŲTĖNņ]ˆŲ…ų˛§ž/ å{2ėržHnĖNņŨŊÖŲ=žm.ļū(|7ā‚ A’ëė`éüũ$˛œŅ¯ŗ 1ģ0§öŌæÔ?A'īŠ<Á˜Žö|ĘdD´Î.Äė‚o ø(čĀOĸ¯ŗ;(x%J؟g7BKķYQ3ņ„sÜ.¯pâžAAĪ,Ųíë}fSö öj…_”Öĩu8Í‰ŽæKtE’íFššÚ¤SOޝ¯OĖyv8ƒįôîĶŖ ä“eŸt4Ŋ%‘BĖđōŧĮ‹ĩž˛GO8ĖŖīÍŋÕŨį¤Œ´]ģSM…žū<ę[Ō âé‰Üœ~štEt†—3dŌņō3¤ą°¨ĨķûIĨ*ēCū%øŲy°u—Ž*îńŅĮˇ°—ÕÂØ5[ŋáō‹ííŊZŗ;å¤Áw-{/ÜDŽ?`Â;ÉĪ'¤Ÿbîbļ˛ŧ˛2s‡@]ēÃũ‰û”Ž"eŪÎëjé2œSUīî¤^Bŋ†ƒŽ,ŸEčųxéE<Ũ0:fĪ,‰ôM7‰?L=MĢäÖŲĨã^pYĪį*Ņ&b}‡î‹HôévԇĞŌ‘ēvL{¸‚™_õ?sNöį™Ã׌žö÷ûŋmllāžÕSÜî{;wb-‘ļk‹S¨ąĘ”ĩĘÎ&ė]žˇė 1âlĸŖŅčĖ ĨgĶO`7 YakģŖÃübķšĪøsŽVŒ­5ŧizYä%u‘6MĶŽÃgõa‘ī\Ŧcj$šâĩTs°Õ˛Vi‰ür6­Žr˜C§Æ4ŅíÚãÔꭔq)zMŦŋx{įVÉúŋljĶzëM ‚Šãˇ5XCÖŦ•qîĪü;Fë/Ģ5級Ŋ5 Ĩí™(–vy¸iø]ĩn,ė}ŠŽ÷ä'ĶdY„\§ÕŌqz6Gˆčâ3§%įü§Įw1> ˙ŒæäđR}Žp]Äî<íL}kæv~Šđ/y¯ũ‘õŊO Š8Û'zböí=O߲ņ]´LÍ­ĨĄWRڟ~aņ‘ĐŠé–ĸm4Mp–+'Žĸo~Mŋđė°…Įw,ĪČŌ1¤sČέķįyøFPŊíú5žâlĢlS))ōU ?IëpEđųāŒO˙Ŧü›˙ī˙Û(˙ÃŅ?˙ķÚŋû¯˙›˙æß}˙ūûß§G~Ÿ}/ŊAæīܸōsŅ.O 9A¯ŧaLēô]ņŨ›ō÷Ōė÷Ō›ģīôÄ<Ļû~÷Ŋô{‰ž+ô,˙Î8ŽS"ēô]ņŌ]ūŒhz6ílÜcûŠĶ’sūSlœ+3âGkôģ?Ŧ™jWâ:ʓĒHĪī’åwJ~Zēô=úŽå!M[>‹ŌĐ,_~œææE1°qÔú‘>†”ŋ‹žŽ1=Oßæ6¸đ AG“’Îۈž9ZR`Ũčéh˛SËŌTBŠSŧā{Rt”_žåys}JûŽé)Ō_ ZŌđīßßŧųԛ 0ĩ†ß}€¤Hāá§ČÃļ‚Ŧžĸn’’ø‘BĄs–JĢ˙ôæâ˙ÜëũĮfãßūĶ?ũĶo¸Ëˇ–āPõ‹á%ĸģŊ×í~Xf8+s‹ZM‡ū\ČU:Oũ´ĄĄĮĸ‹ōœW÷_'ũū!8ĢcLs?Ņ2@k”'Ŋa.-}!ŋīÄûK,žäzv?öžûK~Qŋ.\˛p-Ų”ĩ^ĒÛ/2ļÔé Oœ•åÅvģũáÇŲŲY ž˙ûųķįÃÃĀôŅņ ¤°ĩŗĶũ˛fûš‡§);˛‡aŽt¸+ ƒ#ēĄ5†‘*ØP|^œÄ§˙dŧМ2ĒĻ’ŋōyÅ4We'ßĘ%č0Āf†'vũoĀ{Ų9&šc9Mo˛ŗ*GũT!!*áđn–ÁOĪĨ%@*ÔīkåmåVõ‡Č§Øwō-›YO¤XļŦ˜H]2Ŗq\:)ˇT‹ęōaLfRcD­Û ŠkļlŊ2KŧáãU­„d*—ž[D˙V˙'[Îb[pŸ2Øˇ*\…Œ%žŊ§˜ÅÎ:lūĒ’C™žąÄĒEŧ*F‰.1ĀS ûH\ņõë×˙ũ7*ž÷÷áááÆÆÆķŌČŧ}´ŊŲÛ;˜`‚ĪIĒûųĶņĪ˙ŽŽ~ėîAsžŦ­R[˙īøøxzīđpwwˇûåK˙!°.ŖteėÖ­1•§™%’Ūīí…ūYĨUUˆšŧ0c_Ũą•G¨ėÄ[2Ė”pÆLŖQ¸?>6fft§3&îų * —gō,ĢW.đ=R—į]Ą ‚d™Å!åá14—sd%sĪU>"%ˆ‰¯Ę0‹YãL6Ėm—Ú덅sB+m‹!M­}–cTA‚Ē×c^§#iBūÚŌ?é]‰“š¸b‘%ČEzl6f¨Ŧ‘7Š%áGŦ™üĢôĖ:áĒAB úTŖP¯{•Lŗû¤tEųˆŦJņM6ũX_]Īë5z0ĢžF^"/“Ļ4Rl};>„fĄôŧøå"ÛÂ2†T#iK”Ä‘ŧ$wŅ ) ß(ŅUJ_xĪĢÂĻ ĒĢ(ŅĀܸ'å.e+åž3ËUŽ#eNũ.Ļ,••Ōj8ĖGZw]…Fļ>ƒ€UcSˇ–~îÍ]Ā™5sZJ]âģËGî•YMįˇË*—6J}3ƒŅãxž˜éw3žgcQ¸ũņõ>ęƒ"š—Š}éË} ē‘ë…ÜgMœL úÃ_íM$ŽŌÕ{l_Äę´››‡„VËæÜ1į_ąnh4ŽŽz=d4ÆÜ]5Ë7­fãhķkoëÛÃ͍ˆ3ĮDˆdõšKE Ųq•G]cFāL$]ÉÄĄ0I ĘŅîlloũüīĩėėôtkkģ×ûHũķĶĮģßv×מÜ>5Ž*ÁĨ‘2 šGmÎōŧY Iårá¨ášH-ŊdŋeŋŖ‘ØMĖø–ž<4Û¨’mæâ4õ’pá.Ķ}”˜Õi}Đs.fF2§zÉČ÷Gt͑wA =°–Ÿđ8ÂmĮ”8â‡įfԘéIŌ ‘Lš'ĸ¨ô‘üš„ōMĪ ķœ)Û°2dĘI˜+֍ҧ˜\™Ĩ\Å ĸÍ<ąŋJ˜īg˛a“ģ¤ī÷÷ŌžéɈpYüĒ’×LYūRĄčÍJ8öŲČOĮīWŌ—#ļ6=š[0ĨŸũBBĘZ!‰†1Ÿ ôDäôMÆséq,,A™’T*•sä đčųŧB–F?ŖŠpK3-t.ąHœŽ>įnDš‹bĒ šl\ÂĐ 39<`ޚą87<=åˇŌģ$Ēuč/!Øĩī‡>›dĨ $Ud™(ÖOsP’”%ŽZy¨@ou˜3šf¨gA´9\ÁߝDdÁ/˜ŅR“Ã^Š3#2 ="ÍËTÎüsiųye8æDŦėĩ¤&īJŽ–Ę$íÕ|Y“õ{^Wf6×>Ëz˜ ‹~†1*ę*ĩOĐÛ֞¨úœŌ Ģ,ķ$é°Öb_][k5›777˙[XXØØŪ^YũÂJ¤‚ō`ø]šE'zĶüš`öˈ|Iw퍡ĶQĪ4ŋÙbÉËŲ2dĘ\œīMŊäíbíķĒ82V (‡§Ŗ@2/AIäõyļ —ØuвŌÕą#ZŒIO ŖžŒ:Öh?י#ŽDšrŠN§üųįâI3M՗íÚįĩ‰Šé˜œĒu‡ē…–!)šJ0Ô°*Šƒ_ĄËü­3„Î 2&Æē‡đ…6ĘZĢô‹đ°j†ūĪm&ĩŽm'sačĸy•įį…†¤nQļĪ8ĩfQyã¤d œr‘qJËicäõ!Č9ķŧ8YHuãčÉm!e +„ ŸJOĻ0mĶ0æĻ–ÅĨļ Ĩ#ĨÅ ÷)J3€jũRËÆueŨc­Kļ"ôTÖ´jgZDĢAi2Ži6fiŪ_ųQj;­o˜]Â<­’‰ŗN(OčËi~Ĩ6 ¨îeb6‚<ĩÅ u0*jĖĖΊžØ{„Ô *2Ķ?I.$FÁÆFgčŦbĘČ"h ,Ö2?1/‘XĨ|čׇV`nwæ&č¯K!*oJ­0wØ$Ī s˙ øą‰žÄu›jÚ%Ú3äÛÂZô­5ė̓Œ0Ëļr,v(CjĶ0iH™Ĩęœĩ: N…”ĢĘŠ9’ŒŦ–rŨûRęúU'¸¤ūĒ;’SߔE mk–ŧ°ÚŦŸ:HąM[YĐ[ŗ”YĢŲ’ĩŪEĶ-ę~M1Eoežá°Æé ˇ‚Lę$I„EžĻÛŽŸũŦR•Íļ)hP{íŊĒĨ§nĶ”—E—į>4ģŨ¯GG˙bŦHV(Ö"%‘xEķŌō'ØŠ[ꭍ øž Æ<>>Ŋŧ¸}Ÿk­ã€†WV–W××r´D}ŧŲ[ߨ~„á}ąBw^`Š„„2Ōfų]^_Ü>ȘšÜiõįõ įËO:",i#i5–ÅœäˆûėÜbocãøââöN‡īüėĮáŪŪ˙æ...AQËZ-é¯Ē„Ėjv^/NÂôDP¯\ŰpĖĸŖĻ)éI[Į˜ōŧäJE,y—ĘJŽY0_YĸŲˇbiM^I4Ų÷ãÜV˜(Âāc NŌbUZG†Ngܝŗ“æŅŸĨM夊};‹¤%bÂgĻ-čÅÖaę7Œ‹nŸ”ŖBŠļ‹ĸđ WļĄĘ›…ĩEg&ŽÁšÍŗ¸6STl]˛ŅšAq÷á2Ŧ™fAƒrRČ"ÚTâ€ÃŒrĒŖq¸Ų2ņå%ģfmšēZhŖ´60Ēgî ¨Nš¤õCė 2ŪĮ˖­b˜EášÜķz[*ÛHaĶ™ø-5ōë…QZđthÖIzqÔĘ XŠÚgf ĨĐCjƖZŠņ„ŅyV@šû•üJ߅2ŖbÄaŪ}ŖˇKĘŅsčĐUX33>—ÃA‚Vŧ{ö|<„ĄBÆy. ß1ķĘh&)‰@‹yŠüi´yˆĶuˆ|Ŗt5˛Œ)ũŅeŽ65zŠŧ‚|bų“”du‘ũ:Aø7éjž.…ŽĒ)‰ūäq­ÄÉ`n˛÷%)rëPž1ũáöĶETmjwíøĩŨCúšÂįWų2ëŠ"–9anƨa=Ö˛e–p4ĄŠüBĖKg%‚…ēŨ§°#ˆúM°3ÎąlŦ]ÔAIí2ŗ˜ĖYq…Lb~ Ēž%` č'áiŽ— ĖÎĖ Œąr ķW$Ė#bÎŦšá…–ÉSĀĸjƒ­ãˆĩHđˇäGĄÄPXé ╔KŨ˛,ušsíüÂÁ–šļŲejžŦŊ֒œj<‡t°=H6ÆAėlmÛe¸ŪŨ<<ÜĶ’„ŧ0R÷Öģ;ģ÷įVf áJëŽ9\™c$&Œ˛´aI“,lCú3ų­-@jl>ԙtĐč|h,..ū¯3ˇđûúōęŽúLœLkŽƒšnY€Hĸ%ŠúÅÉZŋzhœĀņ$ą˜áV4(ËmįV?Aĩs@_kÚļŒō:¯D=Žã—–9_5ķw ¤Ÿųũ°ę ëÚX Y7Û+§7Ų…ŅÄęqčų<ETY+‡ĮRmĘAƗė,,K;ÜÍŪ))YD“œjā0ėz:Žč-ġ˜)ƒÔĨEríĸō Âfį×8j;WÃÍg^H’e¨)EĢĒ–’„:’ÆVādō:ŖKÉŦFˇȔŊ'W3.C ŽÜéIڋ“öå(2Ëãâz‰2¤tŊTÕĻ\wĢąeĮ×ĖģÃ# &Ž6IԐ“”MāŒEđiļāŽŒCyo@–qĪč!KFf̀e^Œŧ‘ Éy6gûÁ¸JŽ´ÎDøžöđ%Ļؠ̍*Yd/-¨ųÆ&įūbGQ%ÁŠøÅ ę„›¸Ĩd<‰ģ:Ė1%ĸb kˇ”zá?..sฟ.âeąņDyym¨„í8ÃĪ^įŨœA÷ffņĢE*ąÖAęŦԘöŧŧFĘ'h‘I;z–ÅlŠ‘Ÿ4m\iØõFV—´5ej]˙K*FEųTÍiVL)ėŠä›GÕĄÛ•ė,] 5’îÁ7jÉxŃ{KF?ŠW¸R|Ö([*sāVĩČ4.Ņ›Rj™ņ^a%`ėRÂD=0K%@yĄßegēÂ|†ÅĐŠ+¸.áĶOą5MŗwH䟒Ž$ͅâō¤Ņˇ†š2‚m.ē¸˙ŠíDĒÎõf4ŖŌĘīŠ€#ĒĖhL0āB €ÂŽĶ6ŋ+ÅÔ'RGJ]úoĀ…Ųĸ˙ ,õáAûpŠ ęIOāPîāđp{kĢԙR i`gkįd(¤ęŨŲŨĩ%A!šã^ˇģŊsy§eî”4ŪB™š>ŒĖ" ã›ūÅũĀbč,Ânv;m‰ŠH€Ã֒6;,5fzۛ'—בĨF°2Y\\ūßÜÜÂåõ%mLԗtĨ%>??W›ų8sh ŗŽØAsļĩŧŧ2Û ;XcŖäqvŽfå-ŸžõyhzQ›üߕŽ-y•ĖëšKvž°Ą˜ŖĒæn„ ĄĐܤ¯sŲReŒũ†m!žÔB]˛ûvš$š…dÄ@iĻÔˇ ’–™›QNüē$’ŠHšĩ´k~b5Œ{ĩƒķ˜0\@ĖTZķUŽ]BĶúX|Ą†Ēđ ›ˆ¸ÚNĪ ”ãUķ=Ũp”vœIÖ-qMX€ÁÚߨę4úsĀ*XV2ˇ™ą/$Ąx´DY˜5Ą:Nn]§xHã›zq/ ÅÉĪ[áWĻD餹;âÃȞAÕą/( /gsąWŌØ­_cmkęˇr]Ŋ„BpO(kĩwä?rĀ$-ü‡ÁņkģI3ÉGrLČÃļˆ<æŧ˛ßņEKĩ‹ˆÆ˛˛&$aøi‰–ēĻđ^ÚFZGíúZ´ˆH0,"0™įš-RAck1ĢÅqœģŦÁ¸Å1+ÂiÄ4;q˜Ĩ–r†t¤$,ÜÁķhŲôyÉĀĒČ*Ü Ž†VŖwązaÁ3âzŗčsč`ŸÉņ%ōĘāāX~ԅ×Q@Ŧ>yKĄ€j…Q>hPú×ļvB'Ŋ˛č“dÎ_âŒĻÚĮX![´îĸ˙¨ŠöõQā€ŠÅ÷Š4/éįĒڔߒūQ~ĀŦ™qÆđʈ,kE-ŗe–Œėļ‚\^ükŪ6 ŽÚYoĶZ1vČČ7“ä“퇴,÷SA´IWåÕ ķÔ˜éiä§iSû¸hĩ`ĶpiģŒĶøUÂö›3•!Ú<OGĸFk“YlžZāø:nqMŗ?ƒ§Y‡[-J^Ū)øũĐr¸'Ā‚=xüØÛ##1ŽždR4é°Ī•.!Š#T>::Üč‘áGü¤.@mwgW- 3Õ׌ ō,5ĐI‡Œ4ęėlë¸÷,u˙ü\0´ŧ’D78VŅ‘<9žŊŋč'T-r`Æxz6ͤ"k“l蝄qæļˇwŽ/9‡§s­Æü<ąÔsŋ¯¯‰ĨŽ}˜øėėt}ešÕBĖwŋœÜŨ=üŧ¸XYY-4AX7¤ Ąl*ĢFØWW=Ü,ß ė’R˖C]‰ §—ĩL&"”‘hwÎbƒH8å¤ũqŦäŽ&…‹ƒû˜â~zpaNž\†Đ°œąāfž Ž'íĄū¯=;|\á.FĪŲ'˜[ĮķÕ)Ž/ŌqØÎ,@&ĘJą8ĩũ™“ŗęnPˇŦ)ĨĮA‘­âøÜs•¤ÁĮqĩRÖLƒ§C[hš™Ö7=JdČsĄļ,ŖžÕ1§š-ĮÍX 5Ž4R×ÉÔ]Ā|Û-‘•™Ĩ˜ŸČ¯^Ė“Ė˜ŽēÎ8,†Fĩ1+&–af ™ÁŦŲ|ËÆĐĖū׈›YĨbĮSíŌÕ]Ämi*Ė!9z3§?aešĻ+7Õöí(MĘų&} ž!ė`[91‰¤Če¨ZKž™pļÜkÄĻH:@¸¸°ižÖ2Xl­’Iü=ëCL'ŲŅRēY67õ÷¨ąĄīHŊ’&gÊ\E&,⠟ĐßīŠ~ UŽ”dąZZu0Ęj‰L‚<3í¨p^wĄ!1•Ul/ÁŖ9tnJŽCZš 2÷%ōâžd’‘Uc-T„Üę2L HšÚ?čëa„y6œ—?ŌLl.cJŗYL‡T|9en;Š–´‚Ēa5?=ÔęI–:~āzEŨŽšæŦ1t ž¯mXɗYhË<’G7y ŋÎSVŪ2sMü#āJŅböCG!冕Š2į–ÕYOD4›u}˙`.CoüŊBß }Áú­‹îdhĶyMu&âoÚRîŠã•ûŗeÖBEƒ0w'u4øXÃō$Úg+Ôĩk}o¨-ĩ`îĀå×CbŠÂčō1“‡’7"˙0žT…Ûŗ­ƒŊũíí­[°ÔąĐœá.Ā(Cį^¸t'ūZú^iœj ‡$j†[37”j_°¯Ęr—‡ķŸwVŽŽVډ3ëŸo.ޟī^\öæâlT[„šūĢ•ķß SRëX”ĖŪgč‰ŅÅ /ƒĒ%a=1HViřû¤Ŋ%˛ƒáŖ@āøBq2kÄ4ņ¸ 3Ÿ”M152[\™ŲB¸™ôŨDÊ8…´CōMˆëéâ ëPuu´ŠˆJxŅĸ„Ņ9ĻM'öĸR™ĮÜSY0•RĻá(tX›Œ%kj/.4Ú0%¸Füš´ŅøFM;i2HWąl,ŸNŦY ‘ņĐĩbüĒ@­cž„CÛĨØ=`YVƄėŲŽ3íŽzĢVŒĄķĮ•^ęSŦβ"U}ļ_tl8ko Ębu&t J‡W839Š>Įe‘Õä¨WÔÖ HęØRĢ ŠßR"RךSMeŋD…šœqæĻÅĪá‘O’’¨‚Ī Z˛,5khča8ŦÂmužkyPž{ąjx`<†ETÚ#t4āUH\…V,Te<ŠiKQ~ŲHnŠ3ÅŧŠĢ]ȡū‚§+;-ŧzũW0 ģHÖŽķ ¨ŖRĻ1›„ĩøQąaĐÅ sråĒĖY›ŠĸĨ2÷‘gØR”_2VöŨØëJ –YÚ˛ŠĨsĸą7ę2~-úlļ8!|OŽÉé•ēúÕī'2žč*‚$=ƒ>¨v˙ÚĮųĩí͡yîĨ¨ZįD^ŊS™ôÕ7 jú5‹§sjÙ̝Î*™ž–ķ\uHΖ$ōåü÷}}Ų„Ã3ąĨŪūļs{‡ Ín;?9ŊĮÛÕŊ¸Ķé,¯ŦäLŨÛ­æņŅí›Û~Î Å čëCĐŪÆNˇ×‹ŌČ<ßÜČmÎë´Û'Ÿįz{GWG‡<ą[ļčq&°ūÂXEŊ3ėÕãYŗsҘ=âŦ%÷åÆũęāÖÆA[Em§0#4((īVL"h¯ŦnīîūŧŊ‡\ĸh‰Ĩ^\ü'DKcŽB)a:ŅĪĨ]ŗwr8 2ĖÍÍIßČãéûŗŪōæîÅ˙ŨöûŋģĮ?ŽbŗĻŽDĒûķ—@á3û×a†ļ1%,+Hs—5Ĩ`GéĨFôÉY÷Į•zŖ¤_o/Îxäb Æ$ƒībü×T߲aú 2ˇvŨŋîÍá×ģķîĮîé]Œ˙ø@05zs c„–AĘ#Š1K„rōäÆE|hM?ë×-bÍųīˆxO5Ô;ōaN‘ČøĨæ¤ņž‘ žü>Rí4>žKžQbŠb&–(žCmØ “p¨Fņž~ĐHø19‹YŒ9å ų‡æįœ DŒ •w}Âņ9Íđ„ū īׂiÂ=•_ëBy0Ē“Š)ŲÉqNuOđyœžÜséNę@OiÕΆõÎ^ŽŪšA'&wū ķ[ô+R#™ÜSŨ%L)“úāNMĘaÕú5zi5JmÄŗ)įĨh@úiū.ûƂļŗRe™”&ķ+ã‰…ÕŊeYąl“„šõÕ&’ZŠâ@@\%ž]•‚’$wH›õG•IĶąm¤#ø? Đča4VĮ(*‰Žņ$,ĢkžŅ¯?!ō\/J3Dåį23Ō)ėģů 3đũĖYáÎ+dūūHA^¯{å´v / ˆdë"šĨ:ōč§õ­–JČZ=ö*aņ$Áîķ3ŋ˛ …{S ȃtWᆹ€eh<ō DĪ$y9“B\HcĘ6Ŧ9N*/Õ‘¤$()X„‹4ğĻÁĩF&ōŽĻŖÚĨŗŠ(§¯Ō aÕĀČąŠDŊâ—(QŽHqčNaÖ.ŖĻÉj"^1PÍĄęXm^*­î“…ærr)\\4Ē+ą”Ga {ƒÎ°ÔúEŽeeŌ4eļ>ū ú—lYõ¸ŋh^Héūl¯ž_MXŋ°˜š˛–ō÷^Ąeė Ūßåëú– VÅĄî”ž´ņ—ŧĖ wNĘ@iqɋņ4ŗNŲsX‚j°æ(žæÉū6A)m‡4Õž9'~jœđV ›ULTN"žäBŠ? ĩĸfHëkžô@žŸsû¤[6ˁ4U’"ĪĖx¤2ŦŸĢˇôVMQ9Šü S…ŪĮŨGzŽƒ’>><ÄOâsL–ąFš˛Ö‘ņ ĪĪ vØv s’•™(ĸ>k´/3‡ûģœ ]㠞—Æ×tRĘŦâ]ÚWG#áæ?ÚfŦ¯aܗ kyh'9)WÄÖōi Ō kŖĐ)(RŖqwsÖ8XZāp{õx ĄĀFpÂaJ$Ŋ kJQÔtÖާúĒĨ’˛EũS]Ųš¤ļŧ<˙ D͗ė­0I Žš-E2Qō mŒ¤gÆY<~_./ŋĘ<F =%9Ũ[ĘŖũ0y¤gŽ=„ņ <Ũĩ.qĖŋęˆŪ•¯`4߇˛Ѕp\ģĢāD|?Œŧ–ø„Œ$ĸ¤å-sšžK%$œęÎ&ÎFúœĩ_!x”PŲĀōâ\.ŦŖ ÃĻ6"ÚTE9ĒŊZÂIFO…cš:đSAė\›wePâ*0.j!,ÔšĒÚ::ČkĶõŪCˇ ‹ŠÛ÷ī&Ĩ+Ŋī¯hU Wė>ŠT2 Úc›˙ČY†(&ßËĸ] PX¨¤hŊ#5’|ck H3Œ&wî#A;ĸvĘR›4“ĩ~„ZŅęš;†Œ$đlÅYˆč-†EĶ´Ũ)ē ŌX••č[xˇÄęƒÅ#eåZÄqOdn•)•”dļøH‘™oėŋĄ÷Éd¸ 9 #ˆZTx S]šT#aĻŋģģƒiŋEghƒ˜XDqA(‰…îņÚæ§î9ˆq‘8čŲ™}@\N˙æpæG7ÁÕ~{vĻ=ķŋvīŧđōvŖąũivĻ{v×lܝu˙‡Ÿfņk÷ėËũyofũĮa¯|lĪÁŗáõ!éen å¤Ĩxģ˙¯}ß?ģãįÛ_ßW˙ũ&?ayyü/ÎÎĶũđ–ąėíŲ‡Į” ž‚án4úgŊŲí_ũ۟ŨåîiãlũĶĮŲÃKˆķōˆâ´ņ:\Ūđ×^˛sÁšk•@q¨–Ч9L†ŒHh5l֔q5IRĐđ™ĀX<ŅsÎ2¸ž+ÚødĐvhÁޤĶ**XåŲ(ĸM.%D|Ëåđš8•MV­Yæ)>uˇøîƒVFãË[¸ËsŪU­Еté*™ųNŠEļ jĀŽ`^uLrWíåq+Əė ^˜KÚš%rÃ˙“×Č\PŊ3V‹á€­)ĩiĮ˜Qz”ĻäĢ)—ķëȃsĖÜud åĩPdŸÁôžƒŽŅĒāŋîÎŦĖwÃX#ļœž@\Tō`eÎÅUšY<-‚ Qy2 -K:fFpéišVf~ĨŦžÚF2šTĮC&=ŅF¤AäLÄØ8Ü×D†ÚËôįKÍzĢɈŽ*ĒŽš,ŖŸ$ŊŌĨ§ŧVQŠLVe“žĶΌ0xíž§z‰Llī3Ė-’JNa“;ÃŅB,Ą$6~%Ŗųԏ4ŦŨ"=J[Q& *ÕK$žˆĖ‹iV=ŧĖČ JÆŽ¨ŌÔ"7ÛEéãȖjĄk㨁QUstn–9W49Ē,?RŨã0õ#Õ[›;ŋĢÜy0ˇŽü´aŖÕG¨…tTÉ;ô  Aˇ…žģ´kŖļ>ÅTũ‰§F…ĮI§zi ”„ŲrŦé[6]Ū’ŧ¸„đhš9×1Lų ē%ĮŲËi N-ĀC ËsåŨ”rĶ•š.@č-yĸX1YÚ]­1bK1å;ƒė‡áĘ.;BĒ-¸aŠU#ų s"1LM-ŧRä8˛Ö­@€P×ĒģöáË´wá_ÆÄå‰Ö%Åáū…Y/–GŌQT_ã8OÍɚ#ĢģUĀņ`›áĐsēËZ…ÂŧŌĀlôŪî–$žąĩŗØ¸ßØØ‘?ņŋƘŦ˜aŨÂijĘŧđä8ôÕBę%)Īá—zéún°Ŗ,´&0 éuW×××mažđ´ZWWmŠo§?îîžãđEk~t [ę/ୃdÎēŗŊŗæÎų#L‘A\ŸwÛĮŨūŅęBŌ{s×{ųãõËŖ•ŽhvŗA¨ēņßũÖbŗqyøŋOëūėž­Û'ŨūáÜåÆüįĶՓËãÕūáĖį]ŧĩķŗŋŅ8˜ũUĩÍT2Ąfæ“ô䘆Ļ/féąaāäDŌEʝd&ļÔĒĸʏīô$ãQ„ß ų†!ÁôF-rœŧB”ø’b}­=×"í•2álsĪĢŠTķ|y$ 15š°ú¤ZFo;ĄÖIb"!ž˙X›8…Ā gißøPĮå Ãš¯öˇ„>e–Ō¯Ãa0W„Ēķkf–ÕøFH\rb-[ęÄRÛîāË.4{Øã¯œwTĢ0ëHžaÚĪ$ ˜Cŋ?$MË´]Ú,˜RüЄÃô$r=4’=T|ĩ1¨ĒtxN‚Û+ę°ĻLmĩĨ–’ÛKdž0ĸ(ilĶ5Ƒņ­ĢîkLÛˇ2ßåÃ˛FRæēs.ļüwFĩĩ*<<áAZąHs!,åŦÂĀOĮš†RIæį‚ÔÛ5í/ąîQoM¯aŠÚž~ļ'YÅ!DĶ‘ÔLšTųvĘ.Fgûæ8áÄņ0û$3 ’6Ã*…3ãmĐŽW‰Zz9čË_Z$œw‘íĘ˙ĘÚR'F¯ž-õjîģ¨ÚRo°-uvÎēüuqs}“F•BŸęĖĪ-Â8;b–s§={ōyĄwpxs|¤s´ŅJņNm D´ņ:k´.éž/Īܯ6Č$8!\žŦ6PHŊ¤÷ŠĒ~]ĨGķŊŊ˛‚•Ņ–ZRļÔYŦmęI)ĶqådÛÚŋŊ}Äy7ssũ‡‡ģÅEPÆ`øhá‚퉜?OÔõÆé´V˙ŽKË˙ëÜh|˜ëŽ_#Åë‹íÕîbģŊzxŲ=ž?ŧ6(D\ũ"ÂzžYę6âĶ›ždōmkĩîĨ]ÁĶĮÁėüZƒškƒ“o§_ĪnũĢĶŗŨŪR›¤ŒĄčáæĮéĘŋ넧!¯ÎĘņęŲ ŠEíņíŋŪ"r™[ŪmœážHd{ ʉ'ũĢŗŗĩƒ•ŽŦY>/ŸßÁ$cû‘ĪȖõJĐp´Í@HkAšĻŒKú&›æ?iAî26ÉĖ-܃JĪtŨ(3+WZãĢūPä]nAA-”D˜Ĩp˖°Ô2âéĢRsģ …ĸK2IanŪ° T,Ō9lņķ &ē,ą—Ú’jŌaÍ`×c†umä MĮvŪúĸ|'“BTŖPĶT;Å.‚̤´fÖ ŋ&‰ɘŧ¤OI[ÄöÍĨcĶŦ SƙvyN^R‹¨': …6 k*ƒtĶWtļ’dJ¸ę¨'AC’HŌĘMdå$z›´‘*]´ŸvŲf VÔ4_De ŖŠÎÚÆ ķŲ:ęxÂヌBËrļ:˛ÉX°5E.Yøf‹§ųc_ŗČ͌ęņË[¨äá6ˇ‹t9™%ņ…eT”ÂøF?k›Æ’‡‘P4§ŠĨŊ,š3?­— Št§{e}mŊˇŅŨܨē/¯dđ´âZYíąžX| /.­hjŊ˜f¯kˆŦVŒœaDå'+4îø@aēCjŗ-ęQ|‡e‰NíņZĢÍÁnû~gļŋמGŋŽ6īå îįâĶģ”Œ’§Ģ<ÁĖÄaØáĒļčLIJ…-ĩĖ…-ū+4+‡IĖ\*˜§7h¯l7nËˀ×Ôv<"4“?Uƒb:  vĪÖO.aSŊŧązzxss~¸ŊÖ[ø@ųvV.ŸÛŗ0í¸7ØQ´j÷ō˙ú˙w˙H÷>ŦąŠé׿Ûēãō†/2ĸ­z(= QÚöŲæ™7q@)Ë‚ĒšäŪ2a+bß ZûžČ<(V^&qĨÛ;5ŧ,CiPÁ/&&ĸĘ߉+2Á.ŊGĖȈ Ķ˜l)Ü9Nš´ŠašXtŸ Ī;:RÂqĒŖ9‚}}ŧT K:’>…Q/™6YsĶ=plT׀­šîPcʐRKHÔÆIåiqųåÎīGEöÜĮ2ņŽ´pŖ=){—â§ŲŽĮ&•§č@Nļú„õ†›PíęÔĸÔpĪ\Y‰d=‹UVļ]¤tũ#̚ø$>ø[R3wAƂÎuMXÕĘvŋĖÅŨHuˆm9šēܝĸLBáÂëHš=á FõeÍmIkŒˆŧŠ=Č˙—ļlhAißLÉm/ 9€ĶäN¨%!–7–?õęבĩĸ7č0‚Œ€•’Ķō,ĩ| ˜É2‚ûȀŸ|ļ<˛ē,WNQÁ‹tĨ_uĮ-ÅWK}z—ŌÄ˙č 9ɧJÂe–’{j˜GTE6TKaŗÂj +~lÄãJŦ#ũ õ ’ Ŗ.ŊĀ’ĻĖéGše% "IŋĘģtéÔ ĮdĞX^R‘O6em)ũ5S )Ųv é˜öJ5ĩ)?P^$ķŧdĒę˜Ķųî”ã¤Kxëāaƒ*Ģą…š} ˜Ņ–Z5ß mĘūDTV3ķ=‚ã ĻĸÆĨŽÜšĩĨÎ0â’K)x](ļ÷Aō¤;pČķ’nn_ZÄS ÷ÁDbÑøeâøęBztĄ_§~!ŊÃȐ[OžČ8Ŗ ͅ uŸ=gŖ)‹ß‰¤ŊE}Ëõ“ãCöŨØ A—RŲ´.1_GÚ ĸˇÂåKīŨ°wáûų Î'–ƒĩÆ,°cnžŗ˜¤8f•w)Géëev-{kxž’Ũg[ô­Svš2HĖqēāį˜qh‡(å*w†0ÃÌ=ô]›f@üČĨ5Ûnˇ:ķˇ÷ƒEØCë@b¤šlŠIh&‹ #~Ўˇá—ēΘ@ÛEÖL¯šÍŖlØÜ=:>ƒ_jnē$öíáĖō6Å ö.7æä‡ĢũŲÅm8×ÛX"4ڛ_?åšpįü˙ØõķŨEˇŊ~ÚXûy¸r{4ŗ¸ÃZÆē×:Ûøx´~ ×ÍĮËũŸ0ü€Õu˙ŦģxŌ#“é8zߐMöOØdÃLã|}ņX~…}öĖįÆåīŪ\Ŗys4ģ¸/_o{'l˛-“ôëÆđ ,š9ÜúÕûxŧūû`fĶŋŽf—ŋƒ;ŋøŋ/_0ļF˜Ëļ ëk,[PÜĪ×ÛK:ŅT/ķ[<'ß:ŧĀVqĄįmļR'õ.E×FHm"Iŗiã™.Ī ŽĪšĻúĩÚ>īrûĻ8Ԃōĩ:~b aĶ?c[sŗWÅĪŗ;W2§Ã ŧŧŲ‚"ā0äëöØ×˛eßģ-S’ą-Nį>NE‰ōĩ˛ E0ĸUyęĒ/Ķ¤íBßI­lpF˛ÕŖļ 6ĘÆ—RŒŌÛĻ™ō(zŽ}ŗÄ&OÆUŖ?f꓊ZČÃŗę<˙5'×îYãĨ™d=@#fŧĮđ„ˍ4âÉJy&Öpy—į G\.3ĨôŸ{S˜{Dĩ0gsIÎōĨ<ē’‘’é•ĀBØ=Ļ}Fô6J•r ēš_IåvWüÖ9Aæuž§Žn@ŗ)žšdĩæuŽ­Me–Ęžžä™fâÔÖŌž„¨-Tâœ~…˙IĖj{íđKäĨR=Ąō?( äķ}˜ąÚÂb ­#īÛÂÖ.Œą4ė‡žĀĒ"žreW=Ô *E_Ég´bD͆ŲRNüŽõR ׯã1„r§‰[ĶdĨË ÆˆJĩEÎ"´xB–2Æ4m˙ ō,ņô3Ŗ|VFŲxNOÔ|„d|TË`æĒ){ąũā:ęR|]‡9)ú “•šie3ošąZ+ĻOÂA)J´ęæá,čxÎDÚ:Zh[ķQ&–Ŋ–!‚K2/UH—Øc˜ļÕ]Ÿũ‘~ -b“AÖΐSyeA¯–›Ÿ›[]]mw`NĢ#˜Ũw4nøčđhgkŖEÉŠÅs0j<9^]ėŸŨß^ƒ„đ˜Æ–zˆ?7ŧÞPt V&qĨiF†/Ä1DĪß2RԊg˜Į:äë›#It<-[W¤)B+ĀiZåĢ–ā„ÔÖŲŒÕ7ŒŅy-Ɲy4_ŋųWc:™ō¤vˇ0?S’ļČęm,ršÅe\7´áã:ÍÖ+ģKEˇØK'ØLë}ÍâiÕÔ`Ųwxœƒ ÕŠŽ…— %[4ŅJm?C芚5ąY”W q˛­ -aĐ;&Ũud1¤ąÕôßđCEy"ĄˇĨŋ ÷Ä06‘ŗ×łÂP‚ôk˜ŅU>ŲĶUnE`ÔĨ}K}ĒęzUŸ—ėÕÕÎÖ/‘W™Ŗ4¨JēŪĐūĸK‚OŦr×ĸÛbXT3§ĶÚ,”ĮÖQÃŲž!zĐaYë”Ö7~“ÍüZ,-§öŠĩTæeš§FB1JÚ}Č,\H#iWš[ĘR›ōˆîĩp¯˛ĨÖÂ?âQQžAĢŪ"H‘.‹ 3spâíԘC픰ĩ¤IåÁķ­îĻ 'ĢMˆĢō 8‰j'rcäu;”ŠŧeS%g’GlįՄüãC§2öQM4(P{E:esu~äéŧĸūXųp įž$p“Qé nææ*ÖÂčyŌOåīĄ84ËpģbæŦâ@ø•Po0—nqK˜)ųĐЙĢÔׇLHĄFÁzAšLÃãboƒT*s…2?p ą<ĸž2Ģ›OWTúTfJŽĘL˙Ķvk€°Íûúā\4/îY¤ą‚D |Vœ‹‹ROøX9~*[[IúņȊB÷=3wËûy¤ZĄ-´^7§Ī´ $æ[yēˆē$}Eü¯I¤ /hSŲįΛ@sžŒâcÁpĘČĻFŪÚ°Ô˛í_ŋŗåû—i~; CD€|Üâvjzgá,•”ēZNáŸō§& 4tN#‰Ĩž¯fŠqtŨůŗ‡ū}fąTÖ8P͕ĪĢčT9$š+t“øéÍÆÚ—Įp–‘/D–äËÃĻø T~+ h8ŖīUņÅZŖX¸\]L9Í´ę[ gõŖVƒ*ĘVĸa -ū4d4R“Ė:O1wä`Ė|ĪsŒŽV#V\U8ŠÚZØ0‹S$ŽxŠs‡ąÃ"uŗ†aS; įŗ"×#’ËČĄ¤œVeĒÕ§V™KÖ`eúĶĘéd1Ąxšg܌eķƒmö<âa–ZˇJ–Z #į`U¸3ŧ*ø€RŅī!ĖĨqžŠ•Y÷ä&˙”rN$iË>ō>`L‘Ēiôĸ™Ä´_TpĸW†ãˆDĮ–ŠÜPäXrĶ]´ ´6sFzĘ=iIœŽĻ–ėIxް!ŠĀŖ‚› â‰áHûL8¯j¤.5Ō5Oø.Á°VGš8&ä×?1MiAąōĸÄTcš—…oč„ä¤ŨšŽēÂIöQ 2ĩ\˛™œĒ¸ęh`Ņ<‡ŅD= =îÄāzŲ˜}KN [z7Lk™ŌJ"Å2—đĶyÄ/ژN'ȘN„ĖäÛr\ŋč—úøˇônY$û1ÁĶĒĨa–!™kû˛E„îMŌąûŠkʑ­AķfŲĨ’™)¨ČUky+ˆ:ÁĮû¨*΂u20)alOĢÃXĮÖÔq†Y†ÄR˛“¤ĸr\ĩ–CR–2yņŠ'Ņē5A=Š`(=­5J¨Ęâ2$>aŊĶæ1Ė•šô“pԙō Ŋq4ÃĢÚ^QņkbŠ“_jÛŲÍĸTK)ĨÉ>ŠÂĶ™Ûw‡ĸęĒzęiöáĮ‰Ÿk*č*f:Ų=[č<üčYJÎũÁÎFЈŠ÷Čȉ}ƒļ‹Ļ 5Ÿ”,†ãXfĩjԗ‡ Ô9c¤^ k"Û\Q>œpŖ‹ôÆÆëĨhxx:9õ‰)”ĒUqļø›ŸˆžU~áMŊ ķÅ6 6Ģ:įQßąHËpŠUĶĮãĐ÷i`ô¤š|AÃŧ‡’Õ8YM“y‹:ĸō¸: đģ:ÂG6sé9´f¯’ōžUĄ„ĒJeüšžīK]ķĢ‚2ŦY…AķĪķ,lÖf†KN2N˜>ąÔÜ4Ą<ŲQ@ĩ ąˆåyúžaU:u気ÍĢT>Œt öî‚>ū—Ë?´TX ”ąøržŠ˛ÔĘæ†I‚Ą8Ģ` )Kėņ•¯ŦÃY‹d!O+úÁĮâ\Z”ė7ˆ˙֞’ÕRæ¸îR@-Ŋ;žMh|< §Žŋ&Ũ&ū[ôGŪPAĢ÷&ĸ¯#ÎU–ģŲoČá+ąĘ6Ŧ[b‹$‹j멚אęKN['âi–¯m‚ūĮö*ņͯöĸzއÕ\Žhá ,ĩ‰ŠŖ#uŗ‰J8ė Cõ ˜;ķ<‹ĩXĒŌ‚Ĩæq2Īr[ęŦÉIŪ'­˜ģėXFčĒX:~OĮf˛%SųĢõ‘p/āé!%¨‡oŽŠ]Ĩ”YŋÔ,éۚJ0.ĸØÕáø–,´Ō]?æhšl’ßõ9‡­ĪûÜž›O?0ĮōnéEí4b +bÆōH:’¯–Ę<‰R’ZđP”jGŧ<Ú"ū*ĩ–osNƒ2úĮ :ˆK†XYĸ;ä˜t3‹SĄ†qĸĐ4ļF%­ÜB5¤äú<†Ķˇ…ø›Ô1ö­|XÖßēB ¸™õ€h‡áéL™‡Úĸ–¤–ąÖMGU,"O-ĨĩæF3ö‹5×9uZpxœ§iÅķ­˜­mją<ĘfEí= WlßÔŒ$ã8#ōĪ´”"c}.í"I§öŌūÅzíTy[ãŊbųڝÁ,¤‰•QÍ×$™éĄŌ'$9•o4ާ0ŊŦ†–ŧ/ŋŅkēG“ãË[4( ĸŽ Ŧ{ܸßqŠR]žV)ɒBZ„2—!‰Ë1"? _įĩ/ȝ™Y@/2¨…>"IķģŸOŗc¯ąÁĮHI:qG†âĖō¤ŧ¸-˛e.Ë+Ŗ?Z#‘ŽŽŌÔĒoōXåVŲŦK’€Žë‚ÂYúŧë#ؓ>°’i<~MäĖ SžKŽjå¯-’sĖ|yë°UK‹ÃŲG[j•’Ö+uÅШlŦ!\h.+ĩôģd ’Î[eŸ÷Ē]\ŅÕē2<-’áS^ÁŪ#kQ)” (ÂT‘ZŠWqRLāunëŪ¤cē^ĨŅ@ä/ĩãq ôJ‘Rô͟ą6Ϝ{ôŖĐ“)”ÁĶRh‹§<Ō]Ë”ûl-[8“OrŅ‘$HLØwÃk#iSŠvqĨĖr’n WZú… ņÕ­ģ3aÉÅŪ9f||‰dã°lYVÂú@RČh•āŒOÉüĒyÉøôJ5?‡ž¤[HŨĩņ5,ņUÅj„ķéØ4Yƒ-õCĘO‡— ÆR*MכLaSŅ ɲ$ĶM¨*L]eŽzžá˜M:™O UĪŖ MŲĸ€,ĢcFÖAvÅd˛=KGX€8÷\:šø%\5ĮÆu)Ŧr(ã'rˆ'r‡Ū:›ąÔŊėn9ŗžX$0Ë#Īä„.G}΅ķōōˇ¤+W ˇ­ķh]nģ4#šm6!Ō„*QŠí5Šį5Ûũ‰ē0ķM*BŋČ´KVJšÂęĐ|q ā+ĸjeÔH“ôëŧŽ ĩŗŅķ~e„”Ŧ,DgĸūˆŅSƎíŨ%ˇ7Q FØm†EÁYLû~āÎ)9ōŠÖ([âÎmoĘsՕ=´ĒįjŪīÁö#|ų‰;I>\ļŧm7Į´#ž–„g/ļRUšeU<~  ĮœD>ęQ43ĢÉę"ÔbÂy™”I¤õÎgҍx7­S!ķė˜#eæv$)˜-=8ĨĖq¯Ē?ęDŧÅĮĩŊčđČÕģŽķĻŨ+D° 5’fĄ~ĄWÖ# õYĮ‚5+ī/%šŌæ¸j•ÕΘWd…"PČLüeyŽëUm¤˜šŊƒĸˆds%Ö5ŌYAģDßôtF„UwŪDŦ;4MĨ›YÂöLĮčD4$Ô.mŽŖįú!äKeˆ¨:ŒEqÆ3,ux—ĩČøĒŠãžœ.V~74Žˆ6ėe”0Ų‡¨"•;n/Ë+ŋD8ÃR cČ—ÖŦ{ßÖÄ~iÔ–z~1øĨΎž$¯48§°õâx!Ŗ†Ž^dŦã˛Ú5VXSĢf—)561…cļ w,'ŕ2?hÉĶS3qhüūĩxGjö˙1=Œy…u!•ÃéŧŠæžĮ=d‰ŊŽņ'ŌO|C,‰ÖQÆtŊs ž,đLÂ%˜ģšŌĀ2įicË,Ĩ˯ÎŽéå-y'Ĩ Šdįš˜žYuū 1uÖIs˜ō–i6ĩāĒH}’BøyŽŊ2kË^ŋ@¸ę;ošöåâXî\å“Õäá-kĩ(×ĘŌ 4Ú¤;•¨‹eÔX"74Y§ČÄđY]˛\¸ļˇØS˛lEįČ8‹§§voŲjÁi°Wx—^VLÆsv:-‚:y`ļ2׃L/4ųÔģâƒhNŗsÉãĘŦĶ"@~¸Ž9üš-ŖTúK×$ÆÎ8į{NGۏڅŅuh8Ö=I’X[žXx‚M)[“’.–™sÔōgʍBë3Ą¨ˇĐ:ÃZ$¤¯úÆ˛­ĘY‚§UoĨaøÆŋrŊŠËĨÆšo˜ö{füUÆyAķ*HƒÚ_¤_JŌ™ˇĸXĨ<ôV5žæ4ųT]}”ŗš„äNL4žŗEĨMcŨ)(ōWC.ŗ’üæŨ„‚áâŨ pÖų(@eŅõ0םė%d5ėXVÅīĻV~÷9ôa éŪ’t4,)‡s ôŒ-]cˆ.éyĀ”žÚÉpƒØs‚ĨõÃiÁZÚ4ŠÖ§įɋˆŽņœ4r*%—XęØĘdxë’į”!fs×'Äy‡_ƒ_`æ o­qøIŊ°@7_Ãß§_#;žžđhV­ŖōcA*‰5 p‘ĢæH•ņ%\œä—ú!ËI„\%ī'ÜĩĮr§–„Ä‹U|FxŦrĻâIH^–ā(fHJĢƒ­]EE)ŅĖú[čtēäĒđ¤ŋúYĘ.pųj†ŋcE)tā*Åų ĸ’Ų kŧ Ī$Z+f•s™ĖĨ "ááa-m} =$fNV–%ĒãËĄÕ÷žrNbĄ3íU%ÕBÔˇHƖZ†ĖŌˇŋ„ˇ¨+¤=aŠu ÚÅŖ9âŸ‘{ũž˜g†° ŋØÍ0pč ÚĮŊāí‹Rļ^É›˛ }°ŽūWÅŅiš,¸@uʘ†Î76ãėˆđBņ-͜Ž°Úą8â€4DPĮÕ0%ö:“žŲáyŠøBÁuž–žjNjwEĸÚlIO¤aâ”P2NĻ1ĶV,Ę_gÜ8ØYi‹ÖĨUAō=/ZÚŎķ#ÂĄķÄU¯ŖT‡—R.öSÕdŌ3ŖZ….I-˛ƒų2Įą˜câGdœļOC‡%Gā ÃŦÛ=yaÕ$Ŧķ¸ĘÍŦÁ&ÆĐŽ9ŦTŖ˛A†,ķ,ėRü@Īšuų…8ô*\ļ/—‹^Đũ‚˛†Ņ}#œBXššÔKq?ŗÔØ#÷Ü8ĻŗNęlXü÷ɓ‚/ŋO n"ėö5uŖ‘ESŲøAuTĪEArø3čßpXú´_ķ~ŠSë|ÉpKQ ›Ž–Ķū4øtå¸9ö˜[D´%čļL$iRč˜Cfw2 ¸PŐsĮžļ dqs¨–ÄGāĨ MĻuí¤Ĩkƒ,bÎ,e2š„É%Ĩzžbž‡wÃ×įŪR=4Ū÷ĖŦĮČ'‚™4ŗŗiœŦeSČv€q˛ų[ãfqC’B)Î.ŽyĖLŪŖew 잠i0 j™]"ĶÍlŅN+•Ļ™)gF&Y“°eÜļūâ43i]QßôtŠÎfģ°įFĀäø?p*"^ūū˜š4ŧkô*ō¯l–Î7Vŗ“ļ™ítŸƒęd0÷P_COķP¤ŽŌeX:idG€úå)Žõß7ĻÉ+6o~{nšegX]­JŋBŸy uîÁ(fF›:ã7õpÉ e‚ÍģŠüeČ,ø`ļsMöCda.ŗåĪ•ŗ)hBØ+Ã͈ø7ĩGką•…å'âT“ų.dô¤ wfôpXü„ËĶ×öĀ~å'ēqĨQ?~^nöS1œéS™ÍĖU9ŌĪôņúåŦŗŧ.Ų2›8•åˇq†Ļi‚E=ŠĶī*ؘ [lr)ŗŠ0ė@•îU>7ãäđuTq,-Ä7vÅŪIYŪ*=/ÔcøXyŲ|‹áhؒījÅ´|vY?-ŦŽMŨm^ōŨ˛Z&ÃŌŠ$,Ŗ]YfŧČŗXūlĒû¸C=ÃxęĄŋ¤ŧ$žžĮũ8)™_mĖᓖæЧIŒķ2Ī+ë['ö÷$s‘ŧ‘[&\hų5!ƒ!žÎ­×ķŒtĶ֒žÉ%ļ­iãd[9Ķ^F*ÛąĐFļMĩLķ$‰į$_pF[lۉØ4 O*ßĒUZTĸĨÃui¸:f‹žūĒĐíĸöZ-­j¯a=ˇ0>Töš†+ô°D‡‡ęvąh_°}§ZKE´¯Uõ_ynīFO2mjt¸¨ŸļšV•Úđv,ô)›ūķĮ۔‚ JÚÎ ‘dÉÜQąō÷š{x_pųŒ%Ÿp’ŽôčØgá—znáōúōö.ĘR˙ĩëįæër/ņ#Î#ųË ~Ârؕ a\î¤>ژtĖáģW:œÛ.jKɗ‡"įWГ:<âãXNŅčOą^™¯+¯æŋŠÔú&PĻQy ,c{qŎ‹‰ņĐU_NŠXņ ĮÖ(˙ũ§ĸUIĩؗ3‹Dķ‡íŅ™1Ęļu!\õm­ęyQ7ĘŋéŲą´ę aÕx;ŧ}ëŒ˙ÎI?mžu|ō<š Kąė pXę›;>X”wķtZ͍mˇĨv;ō7ŗö­â'Œ•ˇĮ-r™ŠŠ†§§:ŧ‹MķåÃu8njŊŦáēŠv´ké .-įyLöĶXĀZŧ‹ÕĄzRëÛÅˡãûËá9}ĄøÉļQĄŸÖįžĮũ6ō4 ũVUßąĪ‡;2|sĨmũ›â¤Ģ4¸ŽžÔøū`ûiÕˇŠâw‰ąŋƒ9˙ęÜöķt`c÷ā°Ü–šũšŠ9/'­Ą}ķŧuL´ôËF'Žn۔a}ęķÚęjN%q0Ŗâ įo2,”å -sY‡O2qŦ­¤† Ŧļ˛M‚2íŊČmâĒķ|[)‡mŋTpxE61æ'h“=Ôβ’ĪŽâÕÆ^Į>x\~wRņĮåæķļ­ãKŖÜÎuč7¨úÜŗëÛCSŋ(Õ7y^ƒ‡–Ō=Žč†|˙ q"ÕL͌šõOÆÛëŗ¯ęįæXÚZãRaŧ*rØåßô˛cfŸ´ĸjīDņÛcĄ_įS;Ēøŧéxc :4œgÛĸ-5ąÔd’aŠ‡Û¨Õ␞ˇp›fˇ‘Ęé@†kŦĪyX=ÎVq-u8˜é2Uŧĩ}.%’'cpØu¸ˇ×ā­ĢXÃąĮĸˆ„ĘlĩGpcqmĩ̏ØįčƒÕˇ:áįä•ÃpšzŋųTÔˇ„cļrs.ÍOG+øfÛ§ėŽíqRļŠ~g{åđđ¤Zu2éTéápũŠę/Å1š"}Į!cocö5O˙ :6–šĀT‹ę¸–­ŗÖY˜›ët:s t—°` ^¯'ëQ<Š“NÍ8šŧ$ßâģx.2Z__—ō lOæŒīīīwwwWVWQŋÕÕÕÃŊ=đ;„KļyŲp”É”yčbŨŖ|JeōYÕlĮŅma˜ ›f‰_ˆ1íí"gSÎÛYî$,?Ĩ´åŧKœN {dy|ļå„âܓK3ā’4sÉ[f&Ģ k’…FpØ&~”RDę9>¯TžC8ŋ*.°„ĪÎ)>įšIYĘ_VqcCšp›N•dj=¯ÁáŊī^Ėwh˙Ē’ÛĶøf]?×iwĮbë1ßŨqUŋü˜Ū—ákm,Äą=QG’áũמҿ+ĶÛúĮ\5ūØtŠ/—‰édD`Ŗøī*N÷‰úüė|ŸĀĶ 3ß Į\G+ĒžÉß5ëØb?*ö—ĒūXÕ7mü× ûnxüWZĨÛ%߅œgu[ᎭbK-?äŠ~Šųœ:sú|ĩüR3y83yĐĮĮGĐ´ĀÄŨnáŅ<å˜ë*pÉĀž‹ËË'''q]+6ßÂŖ?!ß~ŋO˜ƒË3Ûn///OK:@ØsssŊ  —W.ß(9Y\\\YYÁA•˛Ūˆå?::Šå5ÃbVÅąĪ#R/c”­%Ĩ”$}‹~RŸĒė ĩųī:ŧø¸ē=g\UÎ õņ1ä_QÛϤˇ9[gĢú#ož]­S;YĄû‹FúMɎ 6~1œé­ßÄęđÍE~:IŠę[\•tvl´é”Ží•pĘŌ‰c~4ãIuØ´QŠoĮĸ\XZĮökÎpĸöypöœ‹ä^4<Í~‘‘Ī4ë[Gū5ÚTįŖ8OEÜ/īÚįi.ĶY,Ķ‹MĖüķĸžëœXŠķ™žSŅ/ō}6÷NėŅ5ú¯íã6cTq Њ“Ɛ˛q˛ÎøFq*ÆX;–Ž1Wčä4ûˆįõ^æ÷7RΝĶS§™q0ø'Ŧūéāi jüƒsĖã(ļüøxYã'Øs×/sĻŽ…6ŠōYū­­-Ônmí šl\šM5žĩp­ā§ĩ„eT6ēČ_š'ÚĮdŽ5å/>ąįcŽ?ĪīGē6—ŋ3ž´šíe™pv)|—¨Ŗ Å8ÔRĄwX–šéMĩt~xß)@gÛßm?­ Wõ}Ëj×l?JãOvœ´ã’å›ĢžÛ8ų°Ņp•šÉĢd6c¯]Į>qž›z˙ōr>Œzw2ĖØRG‹ĘfbŠŸ„>€ÍŠ59HŒ>… Q…ŽvÕ>‰ĮŲû{…Āß0œFŦ™~qíhķB8Ļ æÎˆģåYu”ƒw§Ōæō…ixbļ€˜'gg`Ŧ%Îũã#ė7°H@„ČaÛēW…‹+øX~ÖN•˙ņņ1äŊžžz}}ų´õYYiú#Ë?;ƒÅXsffļ:oz?´}Kø’`Š&\NlņL 7zžzkŽoÎÆIégŋ™äu¸Bį x¸ėí_5^Đ>^č§#ž;xčĒī]ÃĮĒą¨îs3ZV}ëĢķ °ä{āˆoā%sqęËūną]\&Ķ’‰Î‡aR¤1‡zˇaŠy5ŸÆ†š Ācų%k¤ßjĩ¸y(ũņšę|ž1ũŌō[ŽŸ č§§hžņķ}+ƒ~аĖôp[Ā*~ēho=Ž=ĸo™ļđܲP– /†Kø*îŨ–ÁUq`Oƒæ­°tVžby’)ŋ<1\ŖdŲJŅä0h•…sÜv'ZšænģįjķĒļēšŅ[ËWŲžVđņUéˆ$“ ËÂ:VųvÉļEjĮ"—bÆ˛iģ}¨Ō›f.ŽUęŒîUém^Pņ-hDŋ J,Ē\Ūg%es/é×īæšįzcNęScã¸ütÉ|QĄÃO×ŪĘüâåŋØāÉmW֚Ģԏ“-5ŗ\†u. gø`ŋđ&0ŨŪ܄Ņ3ˇAô qrļÔâUŠ!}€]ĶøÄĻ/›ģė-{Ü|‘2Œ.`E ķS[‡=… ė/„ BT ØwaaIäcķĘä[%ĢPūĶĶS”ŋŨn^šA!/äģ ˛:ŲR)ˇBž1ũŌō+ ÅuYËōō ˆ9ŽÜŪNü°IrŖ-Ĩķđį%vE:UÛú5Ō—rę]üí(ߜ´+S—B›‚”šĒîuj¤=ˇĸņÉŲļK•ŦĘÚ.—KŌ-ŌŽŅzu ¨kŦŸIĪ5\Ô[ÍRS*ī#ŲSJS™+4vøw'Ûë÷÷ĖXaÆ´”טcN–o6XņŊŽ:ū0NnØæ[ë]ŠĄ\6l_Ž|ޟĻĶģV^šĻ؁ŧÄËļØÔOx$ |jX™įÔ¤ĮƯŽ´ũe”Ņ؆u|Η­*/›~U]JŪ-éIJy…vĄÚv/ĶuŖcļu•ūŒĢcĩ”x2‘žŅguL(Œ$%c…#ˊÂxbį˛:㞍SÛ1Đï>wǑœs˙`9ˆ_ę{^‘ËhũR ŸaáÚGņĶžVÎߏ)˛ņ€áGŖÏųųyš‡&šūļF|‚|˛ŧ3yÂöY9 3gÚNZŊ&æ¨OĖsåiÔ>슎6éj×ŧ2Īm^U<“áĨ9Č[† Ā$ĪÛ8‚Č3üˇåÆ2q'K5WäPTVUiÉšDŒJ’áü$’äž°ŒĻS…›82šÅ÷cXGš”fąøÕOø-[‹0fšņ3'ĸēˆwsČ=įtTÚ6%~UIæã‡˛Ų|m™ķiÚøfü/ļÔ)ZļJsŠmgÛHÛ[ÛÚH˛Ē­­ZØ8úÅ#éOPˇ¤Éļ„AŊŒ ‡Õg+Ļ|xDßI Ŋí›%}ļŽŽIŌø#c‘öw3.•'åcHfn˛ã[â•KÆ=;jX4–RđųÅįëŋJBΊSœŒtzb†ĨFé|hllK-hcČ:įļD"ũŠîÃĪS|ōú)ž=9E›ã ­‡Ēęˆ2Ô9Įq¤ ã:'šLCtĀͰņ€ ‘úī>Ąę”ŋæY•)į_īŽ'Ėd‘WŽāŗU&î'?ͧL¸Ē˙VõĶgį5ÎÔH˙ŒÛŽUJŦÂŧí^§mlīėõ”hA§rÆÖŗxz"÷{ŪņKüRķ҃‡…ƒw9üŅrČėI¯jëh+By˛ÃŽNiÚw3s*QãrXbī gdãG|Ū’t‚߆”Ļp¯ÖßH W=%ąeS—jĮCߓy Ûį&,1Ģâ[Ø6NÉIŽ&wŜōD.ŽIGŋ›M1ũUũæč8UiÚōWĨ?îģUifeUGn%*iSiŠá5ȡcĐĢÂģE}ËčR…žą&X]Õp•nynûTišqŊjÆÉŊ¤Ÿ†UAžŋWŧ‹âX‘ÅåvėoŒĘâlŸīūčųÎņŒÎÚc뚎`a€ ,BÁ/ĩŦ¯qĨ~åáøU4b&—‰ËÄX.R¯ÉÎmvžĢÖųÕZĶ÷×Ģ­6Íę46ڞĢ֐UžD*öīë[ģLca9üš)ŋ”dä=Ųzˆ+oež›tĒž×ĪKM(Ē „ĨÆNWËcŸÛÖxnËĻĩ´öža›o1,Ŗ´JŖ"lķ˛ĩļī†WšĨ*ębãTĨ™y>T†%õĒГJ3Ô=ŠŖ™v‰eŒAĪG‡ŗēZš¯ôîLß´}ļĐ¯3=ˇę];vĩ–âėŅc”Īk>¯šÔÖŌtÁÁÜé‰jûÁŋÄ^ęaAŌ.—C¨ÅUWđFîjø—YÛOk…+Øî ˙˜6áížr¯đkšđJRâ Į ×)gā siÍ >ĩ„­âԇ>Åŗjå›&žĶČÄĘGâÕá€'TĮ1äP(›}ˇ$\Ąiųī*Ĩ2É}{ą;Ž–~ĢŠæ§Kŋ%“.<ņ Ēj<ącҘcNå¨Rg|ķ8>ē¤ą"l%‘īzƒAŪ–Īa˛ĩŊŗ[jkWęáĖN#ūfį2yˇ2‘IŸNYk†mĢäĒļeĖğÁ_å_x‡cņiūjm¸Į͡Îģ"œø ]lZâŨ>Z8û=Ŋ܇f&Ž- ׉SŦˈ2Wä•I§FœĒ:ÖŠ{Ĩžĩš/´ŅđzÕŅ“Ë›ūáųà ˜;l}˛ĄōũsžÎuĀuā:ЙšßXmΎgũz;{G{ˇb“I€ēCļԋÁãĮØRĶĖÔi5Å/uqÍã¸Ūp|øđá5āô„ķŧģģŗ¨:ƒoj`Šqą×đøŠeC¤ÄĄÚ=jVĩöŽ™ĒâOX¨MŽĒîu2W>uŌ|Ëqęԗã\ŪöˇO™Áb§‰Õ)­3X`އŦŦKËīuŌ™lI­2ÍÁĖLŦŖ ­¯ĻÉņ=üčrs95ëøÃ#N1ũh9åYÃÃnüĒYĖ/ŧžxmĖ’tŠmZĮ'ˇ}ĢÚW7­ŒJÂŲtFĮ¯ãŧŽ–ÖI'Į–-…ĢåP'NyoĒ!ێÖ'ē>¯öÅ.Ĩb~ŽŠîa—ƒë€ëĀt€-§›a÷ĄĖ q$‚_jŲô/'ÍĪČęƒ˙}äũÔĀ"ŧ+ŲÃ,‡ˆÉ\VEŨČĘD”œ”îUôĮŒ2ŧoōņQ4ŧĻ-Ásž_Oá]ŅÃŧåzÚáĩ(Zá _¤V’uŌą2Éŧ+…‹žŗëøņĩql …į‘-¯{Ą %üŊSí{8ųnŗž’‹áq|''ųĘ ržWøÕ–-WN+Ÿ˜ĻõC—äV-Ģ\9Ŗ 'ķÜÔBį.V#“\™s[Y+\&.“‰ęáäā›ęÅ›'ã€Ą)îM>=•tāķa Âxčžī92 Î=\VyŨ€š(ÎYAéôûÆôõĮäNŠmXę*Ļķ=—)#˛ÔЎVĩX‰SÂgÛᆴ|aqRÂsMĮĻ ë-å€-7Į¯!\¸SõŽŲ¯ŠųëX(Cąîōn ŸZ(g†­â›Įį˜\õn nģŽ†WqÆ%ŧ˛•íPųāĄë´c¨] †œŸvžŪuĀu`j: ­sačG 3qĮ°,ĩđ͌ĩgq3[ø<"d/}ËŦ@ŽâHHô¯Ë*~‘{Yąô˜:)ņĘÍXFN-Lygņ}ŽĨfoFˇ69NÖŖbŠ\ÂqžRŧōåâ M<Įģ¸ĪĪĪoooãËOŒƒí­ķÎōōĸüķÂö†\^ĮĮGĨeŽœXęjŪ4aŲ žYúūpfŊ‘C^ÉU›ø™8ĪHGĘcųīR.6Į[gx_Ã|WqŽuâÛwG„‡ķІÛ#M+ÃĄé—¤Yhšuøé’>UGdVŠ÷F;7ö°Mbnn÷ÃŖ#luæhš<åÜÂ÷wē/¯,Ŗƒ÷ûüąwĸ<Ųôëååw™ģ°CĢL™Š™if–ē–Z&eÆÚ@BĘđH$|ūcb$˛#)pHĖ €Āââ§ĪŸ`ޚ‹cãO-ŒōØ˛IX\Ž ĨĪ?Öqd™"ķ:DVwûûûŨnwyyyŊÛ=<<qŽL߯AiĮŠ?<ũ*™Ô¯{L:0$/=Ā@­đ ĢåÚÂļ×§ååŨŨģÛÛ>Cvļˇ!7čQĮđÖÕÕU.ßĮĮUõBK•|O0ú,:oīR†Đä+OŌ˙ĖķB:ö]ÄD-āo÷ˋ‹ÕÕÕÍÍM‘đ´hÅÅÕÕΟ˙­,-íėėčģÍÆŅŅQŽ ĮĮ'ĨeŲ:ąÔƖZ1ĸaū´ŽOŠWDI2ΰÛÎpÕé”Ä)đĘļŽAzŦi6Í*o.đŦqÅ(ø/Ļo9גpģ-áŧĢâĘV•×ixú1ŌŦ*ĪĐ4ĢdĨ=¨ÆwƒJ=áÆÖš‹ÃčYčPįįį7WW'''@´{{‡š86ū …ĄčÚ(î?O~b!‚ŊP^ÂSLŋŽž¯ËüoÖåžd™,ģiÆ–:ÚRÃ׹ÔâcF<üÉļ „Į͈ ĄAãâââ˙]\`đP4 R.ğÚs*Õ9• eŽa-ŽŒ>ĄlÂSëeã  žQ–ēZV[[[ŨîÚņņ1 < ļōččp\YÅ)­Kũ2KžĨuZuCŌäA8Y|’ŖŌ\ųGÚ ˙ũ<;žÜũū]âôûˇXĒ-./ŸAzøioo/–Z—“áĪķķĒz….™ī QĸëĸįFIŽØŊ‹â!#ö#Iwâ‰Qļû{Oøŗ×ë!,ųj .¤IqāĻ`eôŗÄ‡2 üŲž]]_ÍhŖŅ鴁Âc:{)‘ž™ŌW nK]‡“ŽGëlČÖŠÁmG$J坞áļüú3ėŧ3é×HŗĒlöyōWÆį'ŋŠÖ]÷iVäûœ:VĩcyftiĖú†*Īû‚ĨÖ Ų„!•¸ŋĀį:ÚâÂB¯×E˜z wÂ2¤ËōĘ wsŠyqyšēJ<÷ęĘĘ÷}<ĮįŖžö÷ Ú_Äi"z%žKISęÃŗí6ōŊŊŊ–8ČĄ Å4īAOH:c¤šŋ×râ9Âxqum cÎęę~CëuvvŪëvSt^Ųuā%t€įhÁ9r+ņÖÂRãš˛Ôr<+ąÔ~xŒ(÷@ä`H‘ÃM:ÖŽÉ$t~Y—ã0”ííM@ĻąûœÅkû™¯ĶĶ3~ NúsĮĤ¨@ZŨîúĘĘ*qg@ųb”Áˆ&irŨ(ļ|žŒbHg}}ũ h;Ä?;=EŽ››ø0§e‹õ*-O>/:ÛŊ`Kd­åyx¸ ÎāŖ YA2ëë]T;ĨųđĐ]_Įķ‹‹KĒāĘ ęŽ‘Ĩ]aî|:šå˛B ĀΟ×!Uˆ(†Q÷Rųå†Ī h5Čfüäą÷z\˜.šCä á`ž@^ŋ•s6͸ŪūaÔîŗŧŊD9QÁËkL?įčč“âÚÚ*2…Ü0ííîîÄēĀ âææ*ļ;ĘID~V2å <.CŦļÔMM§;ŅdŌ$C%~ūüųíۖŦŨâæ q Ā(*ū„FÉg‡P’ĐĶĘŌÄo^”øįįgkkkš2P:<@=€°cyРЇԓCúąä[jŽQŦ×Ģ„%wS•¯EͨÂöšâNYũSKŠĻeÂĄÕ†Åąī–ĮWŒ+=פ°¯ŽS™ĨœōÕĸN|#´mzˇ*\Ģî5Ōɤ_.ķ’:ęÅGÆvÚÚÖåql^YšÕnG“žtÔto ĐUŅŅSx<ŋrŸŪŨŨĮ †ĪA˜5–ŪƒDw>˙ī? Čģģģ’Fŋ]|Ã3YNã9ú;†Ώ#Ė}”Ę x­üˆáËĨGKœû~˙āā`iiEԁÎßöû؊iÎÎδ;mŦˇid y­É÷ž´;Ų™™mTeĀd 'ô’~UŊ Ŗãc‰ŖRōpö›Fl#—ëɘ:öú2ņĶ­¯<žģ˙ŅUĩ˛Ô‰ēÎųF@ Ė06NÂtû Œ5‘Ģ.Ä8‚øG?~`)Ąaanáčč‡ŧ  jøūũ÷_qIŋ?;Š:>>•ŧž˙œ ˛ŊŊqqy!écDÛŪØĀ`‡q Čۅ˛Éģ‰ĪcTđ÷ÆFoīp?–ķīˆĖÖĢ´<%ylŠUJFV[OOذOeØjˇ——Î/Îcš($Y<įZos­ˇ„ŅĮgF˜ Jîx‚u;Ɗø!Ŧ 0A ¯ĖƤŽqDōphk—‰NrŽOî#„Æ?ũ8xā1‚1˔XjŠYj^gķO†ĪŖ'ÂĶČ#ļÔěūüyŧ‹Oķ<áSęĪÆ4ßB€VØ2åĶģŨ§%Ķį)pZ FÁ+X‹cØÅ—8ĐŌX K|Š0={Í6j7štbšTˇ>8|ÅcæbUQ—ÔéeËÆ¨–ŧXž\^2”pŸYYá-Œŧ ŒÁĒĸĖîOņIŽĨ„ fC–î‰ŗ¤Zīĸ¨5ŦôPk)Ŋ—ĘĘÔˇÅí-oĩŠgÚÎļŖ ß÷īĨ]€ÅŅF’XRLQ(ļp<˙ņÖÃ-‰i圕•Ą`K­úcklŠĪah›Q7,=ąuY[]ûņãr_Ž)°Ē^úŧđ=AV)ŦįrQ/Аę?? ažž4>`1€2!eŪwŲ)I§4ÍÜsÄYYY‚áSņ9õLÎ ë ėh¤Ũaįe>})bÔCÆ7Tfҁ÷ÉO¨üauT’&éŗæRäw5ūĐ8Ę­šŌŌ—oļu'ĖŖDî|<Ŧ.aŅĘ2&AĖ•zXÕžõŸ›žÉ=Nû)…‘/žícEĘĪaĶ|uÅ+[ūŋöšØ[šœŌ”c= ŌŖaw8Ķu~Lž’—Ž’/(đŪ—›`KFãō4ÉĀcvĐŸvggá)ĻŪĮxŌnãcōG„yŖ%čL‰5ë•F¤B9uäņįÎåģŒŠ„c0ŠĘæŦFSčhĩ‰,ĩā‰pz"˙lŠilPü!¯ÆõŊ2ČΠ[Â@ExƒĢBOŠƒ‚OŅËŧ‹˜,`]JH-ɐ&‘^€i­Û ļnYfaJúļÖštb9ū f";€Z`ņ“˛ũ•į\( ã‚[Ž^ĨåÉå%ŗKŽû,ĘJĘ ī.,,Ā ö0R ŸžRøôĮ)Ķų( T&r­ųŨ€˙(\Z6[ßĒpiŞaėf{w–ŲŸm “ÚÜŪŲ†č°ļ ļėT ōlN§‹ÛŧŦ|X˞۰ļÔˇe–|ŖR8<ņ°H†úi ™DI‘[8Åŧĸn†K-Ú2õ°–Ää+ŧĀųŨ)O#ŸZ2¯Ķ.UqR/ũwüôŲ‰xĸ€cˆô;05ŌŅ0bp‡•ūUŌ÷Aß`‡;b*_ĀUB7dã@lđ8â¯v•ũ=7 0°cÄWÖb˙-MËlŪ§Aņąma5áāôũ–UZĘ_§^šą"Žuū<ĘĐeRœ›\&Ce‚8™lEÍį´ (cŠU`ŠšŖ*W-h#øĨĻhiÃ,5ĮĨ8Ųį1=— qב¨ŲĄŅ Āíūž!#ÅW›ÔAŧėūĄ8vh}âc9CŪYžŠXģģ{ôEŦI€Í+WË +ØŪŅ~/FWRNq°ÜģŠKŦWiyrõN(į—ē(+ ‹`ķ ­6És*Œ=čs!—A QháãŖŽūMÔz÷ę"ÔZ°8ķÜbwQ%+[ßĒp,ŠÜ–—–°ä€´q_^^’ŧ°OV¨c°ÉĻō ¨°ąĻ˜+ķē!mĮķļԊbÎ¸D—°Ŗë ų ėŽšd\ÚN'ՐYŗy^Ļ“ß#ąđ텛Š +s”"ī ÃIO$Îáá>ž`ƒđö™šCČĻį’ŽŠKIššŧŸ—y˙ÂÔé`[)Ŧ¸;ĐģAɘIð2÷<ĮÉE=üøiŅîîĒEĶ K ĪĢNœ’ōŨ¨N_†ˇį××ĻS’æ‹•_åöÄôŸT÷ÔsC˙ŨØÚ n w PŅæũ”öi,,˜F;ãŌqråķgtCéÚēíĄŲ„ ˇāp x„‡ôw‘@´Ĩūüų^‘Eļ_#\šæę*mېbXCXöl >Æ”eĀ Xŋ^v|ķ°Œķ.—ÃķtxšĮjÕ%J \uXîūי[ø}}yuĮrf ×n6āé`Ģw{w‡Á…Á§ ¸–ė_ągŽįõĖsÄ û?č9päîî÷ËËëÅÅy6iQ|xüëI{– cWW3a˜C fZĖŐ K°90Ė4öę-,Đ^qŠ{8|&Ģ'N0lŲ$L%Äæ?ÃÖ‰Ķ7¸.yQĀļHä‚eЄßĐŨŨoboëUZžB} #ÕU˛‚ː)ĒÖŋûKųĸ؂‰Ÿ>;;ØÛƒ] yPby"ÁPë6&˙á9ŲŽėė  KËfë[ŽåG´å¸IŲhĐ3˛Ā†QČn …i’´LÛ!€-Ĩ-ŲƒæpèČöÅ9[ųā4H>|"G˛(ÛH¨Ž‰.ŠļŊĘĩ#^‡čđ.RŒc˛/2:†2Ŗ`@ö‚i­îE]•1_Qņ1UW’톐:ŖūBũnī¨z–ę!זšÎ–YÔåđ‡Éaõ ŋžØÜXaB37y¸t^Ό‡ã­Įq]rŠG÷Į—ƒķÍvwkįčđāúöFĶ´ ąŲ\č´āx÷s KØîpûž>:íæÆöÎūÎf …‰Ú˙ŧÂįíö“ĀŸO“•`eÚFžįũ1ŌMë >Öd kŗ~?XGLĩ–ÄĩÛØAo[Š­Âž0āß^ŪGXØqŅÃwŊ6xŋĢ/ųô%°˛wÛ]nõ–aĸæ—KĀ%ā˜’ŽÎû'—ƒĶÍöÆÎîáŪŪuŸ€ąÔÁŌ܇ķķęņƒMû˜CæĶ^pĪ“-b4cĢŊ˙āaC˛a}šŦ€ę`{ņ„§˙Ų ļ#̘čņƒŸŧŠū°Ũj°Ĩ.õļĘ&1Ĩœī(lmŠĨäԗC-<ė2ų#u@g"3íØâa™Ã].ׁ‰ëŦ>!° 19:ęQ÷ŧ!ˆĶņOųnąH  x(žž(L˜<÷p’C°Ĩ~‚Ŧ`ąÃ‰/_žˆåܟ&[ļŖČxü§6ž‚ūŸPëč—úát­-ĩôPęŅÁžÜÃ.“?R”‰H{3HéeŽ’‰ĘÃ.×ׁIëŲR͐†>yƒ´ 9ŖmŠAĻo \6ŋdÉŅh|āf­-ĩËJ´k :4!kKÍFų¯sÁEaΖ$ޞš‚A…Ų}‡a”ŪmŠE̤=ü7ČAlŠŋ­ÁՏ_.—€K`JØ?Ŋ ļÔģG‡{—ˇđ8Ŧ¨ē–ē1č´2ļԀ‰ŽĒs-&¨{ûÄB{ī°Ģ[ß\PŠą1ģÉŊ ÛRc˙åŨ+­Í°­ģ-áDÎYāŊ°Øž(ļÔ͎xޞ‘TNXŸ…ōÍÄÃ.‡w¯|ĀJé×$ąĨ^nŪ‚Û€q™ēëô°6šËD†B—ƒËaÂ:pŅoG[jøˇ¸Å6D:H‘ęĨšÖĮųÅ2–ēÕØÚŪŲŨę‰oqŒĀū:ür ŧ{ ˆ‘ą@ę?ƒĪ“žŧøŊûō ¸Dč¤p$G§`WOŠôY°ÔŨÅæÖ˰Ô!p ˜Î*Ļڏpííâp üåØ?Ŋ=žŧ;ÛėôØãąÔd˛0ÄãG–Ĩ†UčFŨŪé—ņ÷ø5ßËür–øæōĮXQ[Î;ņyīĶ‚ÅuūåtūĘV֊8bi†O!°Ú.,õÖ*}!Ŧē`•ŧP†˜ānoéŦ+Îe–V¨Í–ŨˇƒF nE.­Ž:¤-Fáøŗ%ÃeЎv÷öwoûŧæf+ęōø‘Xjue.Ž~Šq6|ũb\]ũgđyŌØ^—ƒë€ë€ëĀÛ×:ģ8€Îö2ļōˇĨūõëæCgîîžqßl`Ī&J|Ņşũ~UÛ­ĢՕ9˙ôgŖ%¯K`¤’-õÆÎÁáŪÍí=lŠGܗ;ā—:žžöG?ęžE˜§a,ŖsY…ÕsîKÁuĀuĀuĀu`Š: sî|0ųĘ7™áķā=Îõp߸m\Í4p$Či15žÜŅ”ņd?N˛×%āø“$í5¤RØi(;…Ģ&[ęëë˛;ū‘i´q\*N; ļÔâáíķÎ9yš¸¸üI:@öÍfô$ĶÖKØRŸŸ˙ęt–NƒūlØ3!î˜÷úˇ8Zvđmáašs/›­K¯öėl˙ĐûšWLŦ<ėȟœÜ¤Ęķäø‹.?RɖšYę+ōøAP:ÃR‡šĶĄŠØ'ËčœëÁ—ßQ/c6œŊËDôÉåārppx  ÷tnTvœ™ė‰ŗÎ‡íjöĮ‰4hÜøz0˛4ÉūdDPÎéŲ "Ļ3’ƒžÕ¤Ęķė y.?Lę—ZÆōQMį$Ō‰ōŲLOO¤U9Ã÷°ÉYNO”'Ä p7õ°ËÁuĀuĀuĀu` : 3ûČ{´sPš§&4_ˇÛí‡Áũ,‘…ũ4(já§aõ'ˇw#ÜČF# īõõu¤†ģEá;;Û?â¸âų?~H‘ácôͧOˆ‰ KŅF%σr æ<ŋ‚˜ō ÆÄã§cŧ‹$ĶĶSD#ģķ&ŨÁUOH<žŒKĀ%  F•{#ŨPËŠ2#Ā–šÆ˛ÁOs ZãQ†Ĩ~ îÁĶtNËuĀuĀuĀu RˆĸsXi>˛ßåų'y¤v‹ˆ ėP| ~šxjÕäPoĐlÁȰÜâ¯ģģģp3‚ũ”¸īíîĘ;ßŋžŊžžž8?ŋŧŧŒzkk 1ˇˇˇwvvhÖ\P¸ķŋūũ?Å_ˆ‰Į‡›››ûûûHw¤÷a1B%ŋŋŸˆ-Ę…ėIšū 0NNį3?]°Ĩž"‡ÕÍGđF§Ũ$ŋÔß6îîn–Ũ–Z”@äča—ƒë€ë€ëĀttāiļÔí6čæāÁĒ Ö™Œ9ħžŊįf÷_ŋޚ­šÃ›æ9Ų{’6fg Ģķöã- qÉm—įį?^^^‹ËŋÅÅEĀhä‚ōrļC´œ.ĩĨŽŋ‚ąÆáb˛_œtLŧ˗¸ģ•ËmŠ˙čæUxƒ`◚lŠoŠļԡԏƒ>zIŲlļĨ6HÜíeĶēÄy5įÕ\\\ĻŖcÛRC GKw1ކ×ؕ•Ü=7aĶ9¸ƒ‡īŌĮŊˇØØ]zčĩÍöĪįg†ģ¸ŽlËũ=ļ˙‹X@{kÖũūmOÃ5íׯ_——aŗ÷ÄÚ>ūŠĘH:¸ŖfR~€ud´ļļ3ė㔇C)õ7TŧH.w#˛ŸĻķ§ČšŖötĖ‹îôĀs˛ĨÆ?drO†cP¸?ōގĨž‚Ŋ ņ=,rĪËåā:ā:ā:u@FÅ)ØR#ŖZ0ĩXé46;ƒŊåAˇķđx{cj ^K—ÎüĪÎÆIķė˛Dnˇ;ÅŨkkĢKK §§g°ŲH>ĩ‚1K|"tfađÍģ$q‡%‰<ŧ†ĩ ØëoßžĢ–‡5Šy7XĮ ę˜‚ČęƒvR7cÔŋQæ˙¨ŗZ×ĘūG:¯œm׌ĮįcĻÃĮ¸œ]ÎŽŽŽĒŒ§aKčLģ1Xí æį@Eĩff?4[Ë×ũ?~Üā2[G[ęÕÕĩÃÃCÄ<<a [ÜãBđĮ ĸa ëØ@K ģ;ģ0ÕX^\ÄķY>X-LĘôk)KÍlôš$ް¤ƒ4‘Œ§ąŲQĐ<Žããc`n@ųɉĮSr ¸¸oĒ]í<¤ņ(h„ÅŅGã†Úį&˛ –ēxĮmŠŨ×r0 tÎĖyS×ׁ×ԁąmŠeF÷ôÄÛÛ~§Ķj5Ë °iÍÎļn.Ī—ŨõÎÆÆÚpۏč[ŦķÉÉ 4îÖ~zooī÷īߨPøåË)Ūڗ/xrÉOāŲC"Įtė9/1Œųää§$ iXQaãEܗ––$q؅Ãĩĸ9r ¸&+ĩĨÆ×ŗäwH”Ī˙>t:pÕsvĄÛ`s=×iooíînŨŪŨ-,,ˆĮAÛryØåā:ā:ā:ā:đâ: §'^t>tšėGOŽÕƒūúbķÛZåq†O˜DÁßÜô—–æÎŨŨ^_v:Íá[Ÿ…ŋâp ŧk °ĮÁųf쎺w¸ˇ{Ũˆ¸šÜiÁˇĪ?āĢÉĩÕ3MØ\“-5đŗÛRëÆí×âf<_į]\ūj įŠļÔã˛Ôā‰Áļ[÷ŊŪĒãéw }ŧđ.’€ÚRĢũ4ÍPŒ™u[bdŠoČĖļĨŪÛŨēģŊ]XÎøĨ~Ą"z˛.—€KĀ%āČI č—:˛ÔŨÅæÖDYjžKĀ%ā.č—zƒXę=fŠ…Žn,´[ķÄR žž•{‹Ũí1îKmOˆŅíŒÁS5§ĸļļÖĶŪ]&ŦEŽŽŽŽ“ŌbŠÉŖknl #Må 8.KmzÎģJ\.?XŅ–ÖÁŧƒœŲĢ-5ŧí\˙ž>ŋÂéPü…ą1ƒĶq@ęŪˇ-ė€ŽļÔ°€ŧj.—€KĀ%đ% ,õÅöüĒ–ë%lŠß`õŊH.—Ā›’€ØRŸmļ™Ĩ†-ĩîīĀØ´ ļÔb2‹3`ˆžÆŊ1C[ąÄøĨžßāé8wå:ā:ā:ā:0Žđ1 r>Yū;ؐéö9LķsŪ}SĀ ãp LVö;ŧ|”ŽZ|Äũ¯37Ī>W7ä]›á§ŨÜÚŲŲŨÚp[ęÉļ„§æp ¸\õ%đömŠá'ōÎĪĪØ_Ž áē~%&œ^[¯yãžîņ].éH kKŊ{Ų;dK­ë~˛Ĩ†A5~Ĩ‹~¸-ĩŦ<Ü>Øåā:ā:ā:đJ:đļÔõ§įnw}{{ûââîĸĪÎÎ`*OxПˆ=+ąū[Ķ%ā˜ž‚-52.a\Ņ–úœūBVԏ0õ[j>ˇ)Œbr†“ŪųM‹íŒËÁåā:ā:ā:đâ:0 SĘôôÄĖØĢVŒUsęsŧāĩÛŗ5§jĶ1&œđ!SœkęV•šđĪũú…q"î§§§ˆƒŧ E8ūPhD¸Ûí"Āzœ Æé‰ä—ÚZq)WÍ1˛ļÔü†ŧ§pÜÃ.×××ׁÖ>=1ë—Zį°á3čĶŧvĄâI{DĢbCbī6ëšš9PËØå+j`k0Öøđ—”weÁĐÖøX Øũųķ§ĢĢ_Z“0-ãOä Ģ\ĀŲF[Ō<ŽÍĻOmx.ŋAÁ~ZXęĀYl  ŋ¯/¯˜ĨĪÔífCYęģۅ?=ņoP¯ŖKĀ%āxsx-ŋÔ´Oc—ž˙ū-H6''' ĨļąsŅĘfĐØ× އ9–ZÚ˖§*üæĪ äøã$`üRG–¨û=šę—ZmŠwMļ‘ĨƞŌŋ sÕÎĶ쏏¸LKˆ ~Ší¤“VÕŦũ4–ZRĢ§‹ŽÅw`čĨĨ%IŦvŋ+´tÄĶ›››ØĄˆ_ÁCĮ’cį" EäĪĩĩ5áš¯ŽŽ¸<´ļŨUá?Ŋx…\oQŅ–šđ°ėōE؃Q°Ĩļ,uôĘĖxZœ„įÅp™Ë\ârp9¸üÍ:0][j™hë3Ü Šž}û† … ļ//¯°íö$@íNgN6,Ęĩēē†=‹pčW֑ΆÕ5ūüų“Gŧ&6" M0ÖōV4Ē~2Dđ].úHž¨KMôs<=ŅØRĶņ‰nK]_ļĶ%āp ¸^J¯eK=‘úĀL3(ęéđå)ŗ'âp ‘@˛ĨŪÚŨ;Üģ!ClŠaõáļÔÎĮ;7ė:ā:ā:đVtālŠëŗÔUŗī§OŸ@N;žv|æøÃ$ –Wt–K8‘}Tģ_ę?ŦĄŊ:.—€Kā’ĀôũR˙Qâķʸ\•@ôKŨÛÚ=K}ͧƒĮ™}Xî|(ķKM^>¨î—ÚmĮŨŽŲuĀuĀuā•uāmÛROtžöÄ\.ˇ.ąĨ& MņĪ#ũmŠbũņ"#¯˛KāIĐ/–‚Á4īPœ‘‰Xj2‘¤sļÔál*úŋs3.×××ׁŅžˇāŠ¨ƒŠ°4;A>‡ĨŽĪ鞝Gî97Aį8f Ūˆō‹é—ŽJYjœXŽtpŧ"îKĻH|ck ˇwvpāš<į[ Čaš2bũņ$lá/šūB Č(ÔâņGz(qÕø—ļÔ‰Îy1,5=1]˛Üƒ´„§érpppp¨ÔÁ€üT1K‰3 ö>Ĩ? “ŖØ@Kģ:ˇ÷ˆ$`×ŅjĩJfOkK Ô{xx(1‹œq饔ĨF:âaĀ°$xpp Įžƒ&ŋšš‰Å!āęQ ŌøC—€K /ĩĨ†IH°ũŪ “jú'oKmOO¤X‚ÁMš.މ.—‰ë€ë€ëĀKčŲR–ÚĻ/CާąÔ°{ƅÉQl %}ĻöķōŽ›s…ÉŲRõVoząŒF.eŠQ.yŽÔPHÉ&Ũ_ŋ~]\\„íG|+ƤoŅNSW˙Õ%POjKÍãRŗŲĸ—x(hlŠÅP-ËRŗ+kęˆÎ!‰¨].××ׁięĀĢØR×įt———ONNJįâRŽYbvææ"—Œ?Ɠb"Ĩ)€={t rzii ¤5l?â[ˆ)aÄąū¨&<–KĀ% ã?zÖ <~Đi/ĖOË­mŠŲ­žÛR;Ī$Æåārppx:đļԑŸ ž}ûļŋŋũ2Ë"ļX5§˜Ã+.˜8 žF8ZEÛKYjx!îbėA ųî$:xk¤߯įįdƒ'ŠG6ĨGp ԑ€ÚyD[j`ëĐͰ ļÔÁ7ŗÚŽéį"kĮæaģËÁåā:ā:ā:đÂ:PbK-^`G Ä§ŲRיSmœN§sĖ,.įam˛ˇˇ'†”¤26|Ã\ŽŦŽāØ&NÖØlŽ-6ŲōĶîî.<‡ ģ‹‹s„åáÎîî§OŸ–áīc~[9…Æŧ><<‚Įšššúŧû¸đø.ŋJjK:“GjbŠų¸1cøßÜÂŌååÅ͝ĘļÔsíæÆöÎūÎæ5{Ų¤ÎiÆ ô8’yX¤ærp9¸¸¸L\ČVąÕNŸŸĶ6äŧrĐī.6ˇÖJŒ%b4ŧõdTũœw˙*xá•u üUä'—ƒĶÍ6Î#ßŨŨšÕŊ Á`ĨĶú8ŋmŠŲfz„-ĩķ1ĸ<.—ƒë€ë€ëĀ4t kKd>Ō’áÉxĩzÎģÂđĘēū6 ämŠõ{íNļÔ°ĸ&ļu„_j1¸^ÖÃ.×××ׁցŒ-ĩÉk”)ÃĶ<~Č÷œw˙6„áõu üUČŲRĶi‰2‘H°Ĩ‰0K-'•? nK휴vįäD.—ƒëĀuā5lŠĨūĢ@’WÖ%P_Á–š-;OĢe4#įĸ_jõާ§UŠ_ęæ!œķæötî_ÔÚåārpp(ķK?Iąt:|ĶüœwëĪÍĶ%āxwPŋÔ øĨ<ÍįˇļfHM([Ў‘~Š›™"7ŖŠæ2w™ģ¸üŊ:āļÔīsx]°rļÔ § V‹ÛbŠ)PË/ĩsÕÎĄē¸¸¸LQܖú†'^5—Ā{“@´ĨæĶÉęƒmЁĒéVđKíļÔÔĀ/'äuž\œvx:đļmŠ­ßhx€†ĢéĒķɋČëëŸ%ũO#üųŗ>|ę°éK đoũ´¤ü-—€K@$ ļÔ<.[jõKˇĨ˜‹ˇĨņšM§ËÁuĀuĀuāĩtāUlŠëŖ|íŊŋŋīãŋûûßŋųōĨ×íÖ|ŨVsrōS%l<™üüŠk&˜‹V< g¤įÁ§eäošū ¨-5NxQ[ę°CąÄ–ø~ô‚ĮÁāŒĘårÎÆåā:ā:ā:ā:0=x[ę"ŋ[…ä+pŧā*äâōRū<;;ûüéŽ9ę†ŋ~ũ’‡ā‰ņŧÛ]wédDæĒ…?F˜NL g%FÆ××בîņā5üúãĮųqŌ,–0W6Dˆpû/———‘îršēG0ō1ŠĀ ŨA–lˇŲōt 劑?w üIˆļÔčO˛‘ī†ĨNļԈ?zĨK‘( įiD.—ƒë€ë€ëĀ”tā5lŠGvžBŽ÷ĩĮ.Ąnlmõûũí9.ī >@ęņņ Xm„‰áîĶ l’˜s…ķfŦķ/ŽXGR¸#Ŧ’o6ooo¯˙ƓøĐb—"'ë…SĐ÷÷÷‘îŅŊ Œr"œsžĩĩ!IሸnˇûOlyÖģëxļąąE˙“`“×Å%P%í“b?Í˙%[jņøûŗûĨv>ŪŋE¸¸¸ŧ!˜Ž-ĩØF[ūķŖ€N{OLS“™fæ•Áƒņ=::’_VWWX[[ģšš 8¸ą°°Ã6ÅÜÎGB`ˇ×ë!Đm$¤ņëˇoßđÖ&ĀÖEP< '2dwwwKKK(gŽĀ(öím_žžž!}€ļmyVV¨j šK rd.?UbK͸¨M÷‹Ŧ-ĩô4œžčļÔ$÷“-úārp9¸¸ŧĒLŲ–ÄmŽ?FûËÉ/öá‚ĩĨžžž<T.+v+ˆĻņ!âĪÍÍôœPGŌ™/Ã˖į­VKlŸí7䞺$K¸ö€Šą<@:kk̰ô¸ē璁Ë77ŠĀØgŅķã#ųärÄģ¨VN\õŸ žŧ^.ĸ˚¨ÔĻÎæĮę—z ŊÆmЧg#ø†x Õ¯ģÂåārpx+:đ*ļÔŖ;OķlŅ^9ūr0H\€ÎˆPmüŌpŒIķtÅāÁőîÖ 9æeߊį:Ë^ƒ)ītÍšÃ`ãââÜ3˜oyšš m뺌eCv1qkƍ”Ųj….^.ŋGō Sí;¤‡ę!ĩúĨV×:`Š+mŠáÄHÍÃ" —ƒËÁuĀuĀuāĨt@¸Xņ@eĮ[åŠĒgōįœ€(öÍuŽĸŊr|ëáîÄ6Ā+ KmŸ›“-b: ‰#ŽoÁCėIpc\#YjØpÃfZRžŪŪŪÜŲŲ–wņ\äCV.>Ŗ0’ø÷īßcyđäøøXÆ×VWeS#n$UGPĮ%đgH@m:˜”æsÃļß K­(ÃRS?î?’,Ėg †‡E..—ƒë€ë€ëĀ č@–ĨNã­Z4VĪŌb§ņŌ×>{gwfËđ÷1?%ĐĖ6ūŋĮĮŦpâaŸ˙ûīŋ`‹Å#ud…™Qž€ÉÅÅyÜXĘgÛú‚&Į.CŸāEß­­oĢĢkAļ9Âp,õŪžÚRīííĄ¸ø æxEā>ĸ=ãO<Œ9ĸj@äp īéČųĨÛŅĶw Ԕ€úĨ–Ŋ‰–ús K——ˇwq•<贚Û;û;›° CoÁ@PúQŠföÍ%āp ¸\OH °ŧ@oØųg__=čw›[kjÆPš˛už1nÖĪywÜŧŪK|ޜ~/åå|! ėŸŪœ\N7Û;XîŪÜXĶiä×+ æįƒ-uĘ?ųĨ6Ģd6ĩVÆÃbxîrp9¸¸¸ŧŦdXj3 ą¸Ųė9ėésŪ}Ąšüĩ’É-&"‡‡‡š…ÍkÉķu ŧĸԖú‘YęČUĶfE*T°ĨN%’-5~ļSlĮf­Ų<,VÔ.—ƒë€ë€ëĀ‹é@ƖÚČųEmПc‡ũŠ“ũKd ë6icŸ%ŒR^" OĶ%đŽ$üã‘W46ĨzåļÔđ´',ķY[jįc^–ņīÎũ쏏X(züųŧ¨-ĩŗÔâĀÚfÜpëņß˙ÁđãA/ĒKā%$ Ė´°Ôė:/pՖĨ朅 .;ŦõTį`^Œƒqž[wņģŽšŽš¸t Äã‡ĖMŖfËį0ÍĪywTšüw—€KāK@žŅ).dD0ŗė<ÎØRO{9–Ÿv~ÚuĀuĀuĀu`Ú:āļÔī}xŅ]œ’-5jåĒŠ–E[jFßnKMŊõÂ×{Øåā:ā:ā:đ*:āļÔ&ņ šŪą˛ļÔĖR AŗĨ!nKížM ĶæĸÜ~Ôeî:ā:Pԁ7nK ×θpÃ!Ūđ'U"XßŌUW<ą4‚¤I÷žÄūÖķqĘ3ĶGaŸ_OÁ%đ–%lŠŠŒÖ=Šâĸ:yüĐ}‹nKMÄ´ķĶÎÍ쏏ŧžŧŠ-uũķ(Ë}c€ĄqnK<ûđįΟCŌągŖIš¸bú'>Ŧ~ųƌ9ŧT#“oâ~šū` [jĒbā§…ĢfH €06lkb<~¸-ĩķÖĐį­ŨŽÖuĀu`Ú:đ*ļÔā†kb‚¸K“ë—/_ļ66ö÷÷åŨČøūúõkyy~čp—sŧiļR‹â?~ü(Í1ˇ ‘sūãG¸ˆ†W;yÜ0ÂŨ.Â(Š!/Üc–{ŽaâöųĶ'ë8ä<ÖwHúČôķįOr¤­] ËŲæÂ¯×”žGs ŧ; [j<øÎ‹TubŠu˙tŗņčļԐÔës3^˙Vā:ā:ā:đ*ļÔîsŧėzˇŊ…DƧvãœøĄÚÎũ C‘õõĪ8cpŧ4ŗ\úH,øõīßđKAyŖ¤{||‚?ņ‘—9.,÷Ãøĩˇąņû÷īŦ™WĨ?z—××kk_†¤i9õŅ‚ķ.÷)cK ´\fKMK[ņą‡û yÚÃå~Š—uÛV××ׁ×Ձ)ÛR‹í2æIąc–I_p°ŊG0 _{ã…ãĶÅBƒæĶTåLõģģģĨĨĨƒƒƒø+ëÍ^PxnŽōdõ\úHķÛˇoH2MˇĨv4úĮK úĨ~$˙x‘ĨæC_˚P6í‚ā΀›ž¸-õër3΍šü]\\^Ŗ:Į A 9^öäøx•ņwāŽ(đáÃP˗——[Ũn/ū玎wxt8(3*ÚR4¯ÖÆĩ.Š1¸oįJbKč9ļXß*îYž#MXiIŗžÜūxāåüS%lŠÄRU‡ģ˛Ô kÂŨxĮ°ÔŒÛûŪ×××ׁiëĀĢØRG~z$,ˆŧŦlė;<:Ü.X0onnb‡"’ŠxáČûÆÖUmŠ‹|ŗ.Û wąëĀ…ƒÄQ6”%‰<4HwŲ+ okK]šžėš<>>^Y–f§3û–‘Bķ.÷+YÁŌ鉄žÉ¨CxkeŠųōĮâ§ų.v!ˇĨv~Č9B××ׁ×Ձ)ÛR;ĶGķķķŸ?ON~‚“Îņ¸ĢĢ+[[[ĀĶ=ÁģažĨĖ aŖŧŗŊ]šuŅ–:F‹Œ˛€~qqŽŧ`U‚°DF×âââÚÚZ| OŽāņ€;ōÜå,u“ KÆŲÃĶæŽGuÆŖĮw ŧ ¨-ĩœ›Č~Šå‰Üūי›ģžŧž¸ŊŠžiÎ<į;čųۇû;p´‰…ėÃÃ]ũíĪīE(^N—€KĀ%āxãĨ*{ûā3Îuõ ß]ln­UîęCdŧ%ÖĪO¸žķî˛{ŨWŖĒá$¤Ē0‰{._ˇ¨žģKāu%°zsr98Ũloėîí\cˇ…~$,Īĩæį˙!WØĒø@ŪF§ųîļÔĶöŊúē<ķp.×ׁˇŠ¯bKũd,ūēķũXšÃDÜwĀÁė!īæėšĮĘÅ#ģū0 D[j†Î‰ĢV[j2é‡é43dKm=~@ ĶļŸ{Ũ=æ^_—ŋë€ë€ëĀ[ЁWąĨŽŽâū04`̃eÃ÷ī;096JŠiΞû–‰WÍ%0R֖Zä…3éUfŠÃl˛ļļļÔĮÃ.×××ׁéč@‘ĨÖš)|p­šŸÃ4?įŨ‘Sō‰ģę˙ūģ€ŊĮ˙ũ7Ä16JëVo¤ÉŧoA֖šYjåĒŖ-õÂõååÕíŦ>ĸ-uocķhWmŠīî ęĒ–=ī„Ž=ėrppppx!(ŗĨĻyĮmŠßļđ2¸ū6 |?Ŋų™lŠwoīų€D:vŧą2×ü[jBŲøŖz wøI,5˜íÄOĪx˜%ãrī.—ƒë€ëĀ ę€eŠí˜3Ōá9LķsŪũÛ@†××%đ÷H€ Ĩé’Įąķņ4Ąhq㏠×ח7ˇôžæ:z››‡ģ[×סėņ?!ŽsŌÎÍ쏏¸LO0%ĩZØãG‡åŽßHĨū{@Œ×Ô%đv$<~´6vŽ÷2,õō\“<~D „§Ų˙<4ÛR3OŪ?^‡pžĶš×××ׁ‚Đ|DlÍGveIÎÚd\ŸĨnˇgĮÍĮž,.Îūüy}ũŗŧĪ07)īp LYlK=C,5ķĶÂR‹ŸbŠįpPę-|O?â<°ŲNģšąšŊŋģíļÔÎÍe/å2w™ģ¸ˆŧ–-uũIú >›qf æÖxŠ!ō‚ۍ!nĄëÆcē\/*ĸ-ĩd—lŠŲ„ųiāižŗS=õėA ÛR̟įéŨfÔuĀuĀu`Ē:đ*ļÔõšįč~’85ā¸ģžŽ°L´|ūô ‡&Jšm>6ĨĶÁŋŗøOĸÅ3 ĀëHIҰ{Y^^Æë¸Ëâ~š\¯"ĸ-5ãibŠÅ–úą÷h‘wŊ&ßÉö÷6ZĶ•4ũņ8#<ėrpppp˜†¨ĪWļƒˆ ~=Įˇtũƒã.IœÔ sÍË++ŅÍķÎÎNocã÷īßڒDåíßßcVíãŸûûøndŦŊģŽD666âŅß8“e¯āūœJ˜˙îp Œ–<đáApXв"g†ÔĘR‡Ŋ‰t^9utˇĨv.jĒ\”ÛŅē­ë€ëĀĢÛRƒ ƅy’Xä`'-@ÖŪãÄOIÁ)Ŋ^Īq\2^Y]]ÅÍ­­¸ˆôs|7>A"++„4’ ÷ÅÅÅŨŨŨŌŌŌÁÁÁč9ßc¸\/&hKÍĮŧđēXÎP ļÔKחˇlõÁ VŖ[ęˇĨĻĩČ ų[ Ííé‹$\.×ׁŧŧ–-u} éĶÚCĮ°}XŒY|‚ø<7+7/Ö°í}~~633‹Ā‹ĄOØ%ā&cKŊw¸ˇ×ŋ—ŗ^€œKųĨ–õq˛¨VŦí~ŠŖužŪuĀuĀuā•uāUlŠ#<_Dîyvf&LσčÁø°ČIykÄŋŊŊ…MĀtܰØjĩ`rqqšĩĩÕívGÉ#¸\/$cKÍ^§q9ãixÉ;.˛ĨÆĨw~$aˇĨvÛqˇ›wppxUx[j.ל’Ŗ=ôęÚÚáá!ŪÂ]Œ=pÁēZŒ@`íŗŖåtŅ–zmuUâ_]]Á„ZA@lNČ"e¤ yÍr{4—€KāI°ļÔbQ­wÔv†ôpyâ~ŠŨŽŅm[]\\^U^Į/uũŠ6b\ė,„Å3,7pGXRģ|||N§Ž-õÎî.ĐķĮvŲŅ76H†Ũ`Š÷öܖē~ãxL—Āä% ļÔꆚŽ#WÃUÁĪđK­ļÔôSØm˛ĨŪŲßŲtŋÔnãôŅí\E.—ƒëĀôtāĩlЁkëŸöRgŌ†ųPĩ;ŸŽ#+ãxŗ°ļÔG{}väCĀųąąĐi&[j­@Úŧč~ŠŨ†ō•m(_•ķēģūģŧ x[ęIáéhŗkÅÅÅ7 ŧ`.—@ D[j‰L'$ʝĪu–zá§'>¤ÔKŨģžžÅȂíÆ<¨973=nÆša×7××טU´ZĀĪĪwX|6BŖązĐī.6ˇÖæ†Ė‚ĪašŸķŽ- Ŗ÷0“ÎĪĪÃ˙ŨÜܰיŅ=ŽKĀ%đŠØ?Ŋ9šœnļļvŽv‰Ĩf<kÔļÅī‡z˙@ØmŠŨ†Ōyb××ׁWÕ×ąĨžKŊļļöß°÷øīŋ˙Oŋ"ōŦ]“’€úĨfVZųiÎj[mYj9{ŧ1hˇšî—Úų!į]\\^Wū[ęIMįžŽKĀ%đŠ(ÚRKa€¯KmųiËRđ&Ū8›)ëWå*ÜŽŅåī:ā:ā:đ×éĀģļĨ~Åšßŗv ¸&.ã—ZXjŊĮį˙Đ äė‹ZīJUĢÍ{Є—šÉØ?ëŖ‡]ŽŽŽŽĶЁpŽ +Ú9ˆwŲŋėķĶŽįŧû´ũ-—€Kā]H€FžĮĮ葨aē3 ũĪ#ÆŦdE-aLJˆu5Ÿ¤Č¨ģC<ėrpppp˜†4ƒ-5f#;ËÅ!sđsėĄëŋ wŅŋ~ũĘū=đü]ā/¤KĀ%0–ȖGĨ~:qÕLK˙3ŖŽę Kķ™Šzžb:=q0p>f|Œđo ŽŽŽĀ$EėĪGöé˲Ôõ§XNĮĮĮšøpôaĪP̟šĮt ¸Ū˛Ø 5F¤Gá¤íš‰d"-,5“ŌÆ.„w2&MfŠ?íaáf\.××ׁÖõCÅķ‘~#e™ë×ÔbŠës˘Y[­ÖííM,ÉŲŲŲĘŌR´KÁ—Ūnˇ‹SģŨuą Į…ôŽŽāYo~ū#ŒLŽ9øīJøŽ]__Į[¸#ŦoÍÎ"ÂįOŸįįs‡Ķë˜ø[F$^6—Āģ–@°™ž‘}†ĘU‹¯<ŽXją™4y×#ĪÕĖ đĪbKM,uāN<,œŊËÁåā:ā:ā:đ˛:PfKÍ9˛{ĒĄ×sėĄës˘=qžËū÷ũXāãímÅÎ|&ųįΟáG¯ÛííīīJ4¤wwwyy‰Æŋvģ7ˇ7īîííėlKŧãŧ…ûŪnx‹ĄöĪ˙ū[][ûņãGĖáÕÕUĄķũr ¸^TjKÍØXģw= ƒĨæä{t–Z{~˛Ĩv>æ…ųËÁxØŋ¸¸üõ:ĩĨNsЋŲRƒ?ƅ9÷ČU :ˇ÷8gc2ũđáÃũã# 2ŪÜÜā5đÖßÂŪŠņPīÉņŠŧˆôŋ}ûŒ‡øP `0ĸÅËĪÎN{Ŋbâ~¨k¤š°° qpL,ÃŅÁDöË%āxi ¨-5ī3ԕŗ9ŅEmŠ•Ÿe-–"l´FaŅ^–‡pūÛ9o×××ׁČØR›o/eKŨīßãÂL‰;Â2=ËnE{ĶļđŲ0팋‰DžéĀ~0:Ō–#‡‡îī…uÆ)č[ƒ†œĶét l‹ĐīĖぜ.é—KĀ%đ‚ ÍļÔė&ļԊ“šĢRXmŠÃåē7\u´ĨF—v~Úųi×××ׁŠëĀëØRסĄ¯Ŋ ›aå ‹ęÛÛ[!’Ŗ4Ŗs‚ę‘„ļ闆ggŲX“ ųRŒ` ;Ü÷ööđđpcCÍE^JxŌ.—÷A^öœClŠã“dKÍKaúŌĨVÔēÃZú°ÛRģšÛŽģ¸¸ŧŠŧ’-uä§GĸˆČ7ÃôâķįuĄ¨ ‡7a΁ˆøëęęj{[ą¯ĩÕ. ¯ŽŽđ&Ä|xŧžKŠĨ5šÁąm âũr ¸Ļ ļ”–HÁã;ÎS[jũ@EtļāoņÚt[ęŠķ1ŊŨ¤ûQqŋ:ŽŽVĻnK=î”9fØ~ˆm´¤ŸīėėĀBvĀÜËËËúĢÉÆrĪ1Œˇ...đîįŌ”?‘ |ņã–Üãģ\O€ĩĨ&_yäŽwĩĢūßÜÜÂõõåŨlC3„¤íVŗûuķpwëúúKaŲuĄåĖŲĖča—ƒë€ë€ë€ëĀ‹é@*ļúÁ\x~^l…uŪY=čw›[kdX\uá­ú'ļäyÎģO˜¤Ÿö ŒLābŽBžöēŋåp Œ+ī§7?/§›­­ũŖŖŊûæĄ‰Ė}hĩ?Î˙#ß§"žĒ&:1‘žƒąv[j÷Éũ.üpcãÎÂâ"îË˟Āß\ßŪNđ;ÃũũÜZ­Ž¯ãëęę:ļ"ĄËL0ũš:F›“˛<.oWōīîäOÕ×ąĨ~2w†~r|x̓ķ>1§öË%ā˜Žĸ-5#gbĻÕēƒÃ0ųGžOÉŠ|†æ'\@ˇĨvŋŗīÅß tõęæNŦ~ūü‰Ī¯_ŋ~Ĩ,ō#ąšš˜~vvƒČc>Ŗáp˙púēÁũ1㝞L¨ŽžŽû›s:đJļÔĪņi=…ŠũķįOâļļļŪ>ôŸ‚4< —Ā4% ļÔâŅ˛:įaúŨ‘Ā6ũ/\FĩĢöķũüČ7¯ŦĢÄÕaƒ<æ˜ė §ģ°°œŨÛÜD˛zŊîÂō2 2ÂâÃ\/&Ņåå…îׯŧ+?ĪųõûÜ!8ũv{iii{gGŪÅF~ė¸{ôŲŨᏏˆcŠWV>Ãzōü×/áŗ‘ÅZ<÷ÜÚÚúÉɉ”­Ēøm–3Į×bžšēÂ)ÁÄévâ;éŅŅÂûûûËË+˜v–ēāŨ‹‹Ģ˙ūŖYŠ6Úx_B= äœá‰I]]]`ŌŅ‘ž‹ŪYĀÍx‡Ÿ‰.Átd{ģŽ.ˇ7ˇÁvK:UåáÃQĀN:ōÖđĨ……ę…b Hą^1Ōr:/ûæxY˙æŅųiûĨ–‰ųŗÔSCž‘KĀ%`%Āšž7y?ĸąī?Ôųe˛÷?nKũ§Ú&ūąõŠ|­đÄØÕP+aôö3Ea€ÎĩĩU N Â˙á]Ų8W4›üNāHáŨŨŊ>öąvŋ‚ķŽqā–Ž@{l€ē>;ĶwąPÅn|áËáV8?{‰`ޞŧ˛‚ŗĶ†—gyaĄČMĸœb/Ū]lĮ#o}ÖjyõBMQ…X¯˜Ni9ŨÛyúˇ­nKíÆ%āx+HļÔĖRë.Dã3ËR íļÔîƒöU|Đ>ŸËŲƒ……†pĐjX?Kį-ā¤a„á  ļÂã]BŒp،›á†%RØŪŲZø<=>>’į¸ĀRãZ\\dÃåÅ#—Ī=:>FŠđ:€;lž‡—'ĸŋ“D{ņĢ ˛‡íxäæ‘õĘę*p6ˆv[¯˜Ni9ŖužūMë€ÛRŋ,áåp ¸‚0ÛR‹—<ã–ē[j"ĨÅ1}4¨FØmŠ˙^ûÅ÷é8gg ėKĖ4ׅˇđOĖ–Öm˛–ž™Á,˛<ĮģlWŸU6ŒËz† Ž`wggfĀ=ƒđÆÅ=ōâQh-Ë\8ˆs¸˜…5ļęoon/Oi†ØRŖØ¨˛”V%ą^1Ōr:Gûļ9Ú?ö›R][í7oKƒÆsV"8y|ô@ü‰“IĨ3‘Âx".ŋGÁ–šL;Œ¯<€˛ÔōĶĶrw[ęžōmÚģÍw´ŧī=Xd"ķYey΀ 3:îĮwų„3œFF›~ |90ô¯ķs pÄĮFÕ ssō.¸aŧ…wŦŋ˙yņØ.dqÅ|6~•Ų—¨åāģŖĒ<ĨÜaŅŋGäæQlÔõĒŪŲŲĘɤǜÎŅžiŽöyßmūˆņęulŠëãPRX'+3ů1=UyŲŗëįRŒ9ŠtžS×%đˇIĀØRSWKMh™šj…˛Ôō—ŪķļÔĘą1¯āaá\oNŅÎvĪįŠĀ 9cė3ŧēúE ũÂŪÁhs “‰OËËâg#ߞ؜K ü$ cjØoėîíÉģ0ä˜F‚˜Y™§w/ž ãW8!}ú.ˆF2Ŧ*OŠŽąŊxĻl‘ˇF eĪ%K Ëą^1~i9GëúüæôŲĮÖáĸ-ĩčĒ>2Ŗ?Į!F}3*}t 'ķ‚Y …ž‰ąfŨõuÁÜø”„qGú‹0Eã‹LÔfgq—?ņa\8Æīâ˙esˇģNš„šKĖ?~ümāÆë똞Ŧ-ĩ°Ô|Ö8Ąj)Ė˙æ–Ž//¨7‡ÅuĢŲčnîėoŅa•îîč9Ëī.?Qđ=w› ×p—€Kā-IĶTëÃ:=ąÃ§'†˛Ŋôé‰Ā¸ŧ/bô%1{Ũ.>X n–w௯Œ/_ž ü¸°ŽļŋޝŪŲŲÅ2ûôĮ­-8äÁ ˜l!> Ŗw:,ƒņģĸą€—wņ'Œéā“bâŪ~í—KĀ%đ˛Ø?Ŋ9Áé‰ŊÖÆ÷ÃãƒŊĮj1ũĄÕü8ŋ˜ąĨ–˛´›ÖÖđQyYą˛žĖÃ.‡?E’ˇoĶ?ĨM}ŒúÆędKžĪčNö˜ŧžÆRƒ?ļ\˛Ė‰b¯eīqŪNHZčäČà (YJ"ļaôkøyee”3žœŸ‘Kâx"Å>;;ĨĪeî§MŪĐa0Ķ ČÉÍņtl¸^XlKÃ“ÉudŠcŋļÔÆŒZmŠyd€ĨĻú+ Mpƒīv9üQ:l”]ˇŊģŧ Ķ|i&ĸīĒļ] r­š@Ÿæ[ēŸ—8~ ~åᨯDĩĀ\{™ŠM3í<ŪŨGœŅ–)Ār—åŧŖ 4Ŧ5čŧ§FöZ ™Ĩ´x+āx3Ҍ|9Ū…}šdđõå%ōåŖŠür ¸Ļ!ĩĨ~lĮĸ-õ ëņCũ~ˆ-ĩö[:ŸÜųž?īqūĩš%O.į§]ۚ4ˆĶÁ\Dķ‘-Û ÛRG.yä,9ix Ÿsß$ Č+wI*Æ˙đá"üøq” ôŒ;ÂxCDCRzdĀ,­sīJ:°Á&lÕŅØzdi=‚KĀ%đ ¨-5ŧ„ĨÖđOŌŊÁRg;ē~™Ō•6-”éĀc˛÷ˆü´‡…/q9¸\\\^N+…ŦĨŲËrįâ6gčõ4–Z’ŒüôČŲ7rԈšˇw€+ōаúāP°Žûįįæpؓ$ģļēēģģÉo„ņD~Z]]Ŗc\ɤä`]Úŧ$ –9Љˆ_.—ĀKK@ėĻÉAōKMg(  l÷Kú­bņhK-‡ZOāa—ƒë€ë€ë€ëĀKë€X ž´yåđeé<ú4[ęq§d ėQZ˜pD¤ëę_ŋ~ÁđcŖ×ƒ+Iųßãc@dX}n^[{-ˆ™ŽYE˜m¯qá]|:Ãģ¸#,ŗyiIAoÃÛēˇ ß%ā¨)FÆ4Š-5ķ͉Ĩ†=>¯\¯DXSŧ[jįc^ŽqŲēl]\\2:P°ĨVųŧ˜-ĩĖ…õîŸ ;€€›˙ūû/€ōÅåĨėSÄ?đš)VÔÃ&÷\‚~Fŧ‹;;ĨËæeÃH|]‚øåp ŧ¨ĸ-5@°ā°ŽŸ‚_jf‚ņŠÔ–úĨ9 Oßy/××××Ձ‚-uxū˛~ЧÃpŋčÜī‰ģ\—@´Ĩ;,ņŒ–:RĶ˙ČĮ$ųžîÉfËmŠŨfÚmč]\\^E^˖ē>K=ņ9Ût ¸ŪŦ ļÔTRáĒåú‡đ5ķĶ´QīōSā­Ų/ĩķFΚ¸¸¸LS^˖ÚYę7‹iŧ`.W”@Ū–š‹B§(GHÍÛ‰ĒæC]ĀU míļÔîĶÃ}ŧ¸¸¸ŧĒŧy[ęWœŨ=k—€K`ĘˆļÔę—:zÍ7,53Ōl[mYjˇĨvnŪ}ŧ¸¸¸ŧϏ-õ”!ƒgįp TK ÚR‹Į†Îú /ÁĢĮ?ÂW‹)HāĒÃæDņũé~ŠŨ'ˇûávpp˜ē¸-ĩ×€KāíH gKÍŪņīüÆāŸĀF'Ģ‚Ũ´Ũ–Úũpģ Ŋë€ë€ëĀĢéĀÛˇĨæãÆgų6ûņãĮ¯_ŋžŠŗ oooáĩúcģ=˙ņãĪYD}ĢPVAâHûķįĪCä6qœ„|ƒ퉧í ē˜wļ~ŠÕ˛C}{KŨK<~€Á–ķ¨č†IÄũRûųˆ´ÉmI_ՖÔåīÜđßŦoŪ– 7Ž˙îī˙ūũå˗^ˇûFĐŽi\°ũüųwŋy}=—“:&8á-Š(˛Ķūü9DņŒÉ Ę ųNĒ‚,•'õ'I cKÎpĩ'šū#fÔrJ"¯đh‡"ĩÛRģ åkÚPú9~^Šë€ëĀ›ˇĨNŪŗ8ļâ`Áā­ĪÎÎä DP×ā4DÜ-Ŋŗŗ n{~~ūĮōfađʈŲíŽGΧ0âüE<ÄũėėTbÂĶ?œÅũôTZø˛ˇˇÛíõ€ō™)kâD=#JËcšįF-Pļųy*$Ē#õī&Ü|-ĄHČH¯‰<´ĻĀÕL)p:'V‰UF ąŋūãč1“p‚cĖ(gä‹ÜŨâŸaßZ]Œ-5÷ÖĐgc9ËũRĮŸŨ–ÚũŅžŠ?Z?CÎŋ¸¸ŧ}[ę_ <đaö”ã1Ÿâ r:XąßĮ}owWfØīßŋU^___œŸãHEyˆ˜€’ˆŲíöö÷5f¯×=<<ÄÃũũũŗŗs‰ Cü‰cq/őˆ)„qņ*-­K Ŗ°šžū ŗŠ]ļčšų\ĘGGG˛„` Ŧ?j ŋ)ą Ąø.g@âĄTycc rØÁŌüz˙á!˛€ˆšŪ]ĪÅ´rFî(Ci•ũĄKāų0ļԔ˜lP_yr–ēҤŽĸ=Aą¸°×Î×:_ë:ā:ā:ā:0exE[jŠU÷87Gž\)XX°ÅŌaöŒđÔ˛ [ÜO™ëÅ´?QĮևņDq¤ §—¯ŽŽž+÷ÜjļPˇKKKō:^ŧ¸¸€u‡}hA0k<Ė<&JËcŦPŒßž}Cl÷Íí­f]MŽŽŽææærql @įĄđ) Pר,ūf^…szz&üúÖÖV$¤ņëĘJ>Ļ•3rGžœ<—@Šĸ-ĩü*˜YîrũCcVŖq{?¸í?ÜŅ}đ@ĪŨ/ĩÛģ ąë€ë€ëĀĢę€îđáųČڔ3?4üzŽ@$›ī1kkK žx7RĒŅ‚&ŨûĮ°UŠ ¤+¯÷ûˇEȋ_a⁠&Ú(|0¸G1ĀæFȈB"ōÚÚę§OŸJq$R¨Ú+YZkßl9æXŲXĩ*[jđÎąF–į.IÁ$ˇ…QZXn@&™–¯áŠųĸž&f_~ˇrFîQŧŖÄw Œ-hK7w÷ëū@î’Ö?Xƒ ˇwƒūÃāöî÷{D g—Ķw7Qnļęƒ#ģ\\\\ĻĄa‡Ī#9s5sė˙z=įÄúīAöŗ3ōį`đ-ƒÛíNōâW¸K?|øđíÛ.ŒC`ˇ?>ÂLâââ<.l$ŠÂé+f'ÅĢ´<ļ.1lkÖĪļ‰ÃŽ%Ö(Æ)OÁŧ†ˇĀ^K}Åä_´fĶ&Ļ ĮĻÜŖxĮ†Kū‚K`”‚-5Åàä|7ĀũáîađĀŌ?´Qū$ZiyڝÎééÄDžÃ9æNge.渰°pss#·ķÜķs)TY ysuå)ã!,ÎŖ$×VW%&„cZ9#w”ĄfÛy4—ļÔxí§bG_ybKMī O?â<öē:ûÁ7į§§wppp˜Žđéc:Ųo¤ąUMŠõ™æb õß­âkÅÆ”ą¯vĪĸË=\€ŧ`eaėĀ(ËCü ôŒ˜0ŗŽT4nôzŅV"øioO Ŧm]`GTŠ /..ÎG‹íŌō€ķŪŪŪAĻ€ŗÃ9f¤‰8q'bĖeŽŧøpžû_“ōEŲČÉI¯%=ã!j%šÃ1áŘVÎbž>.Nōø.š` ;4ęßâŊƒƒ27˙ëĖ-āsŌŲÕ-‘Ôôem°2ßŲŪÚ>ÜŨēžŊ…Öęb”)‚°ßÃb_črp9¸¸¸ŧ”`ÃÖŊķķŗųoz sĐęAŋģØÜZĶpĨĶĄužQsžŒŅžķî¸yũaņ77ŋnm}?zš€˜ŲÆZÍ<†¤ ›°ōqûæDr÷D\VßOo~^N{­ī‡X ^Á„:pÕkK­ųųEeŠe'đ4Đ7]îļÔn;îvķŽŽŽ¯ĢoŪ–ÚGNāË'âÆ6*bÖƒųuäŒ|# _'žĮq Œ+hK-ôô#‰’ģ|”"ŋÔ°™4íĒ´nÎȡĨvÛqˇĄwppx5xûļÔãNÉ||| žĨŸSYXŗYKŽķâ 5ÃDž#-ėŸS$×%mŠ%3fä ;Ô­‰Ĩϧ„Ŧ™ĨæįnKíväîãÅuĀuĀuāutāíÛR;Čx! ĀVvį°÷øīŋ˙&hFōBĨõd˙ D[ęA°ė ø,ģ#K-`[7!k‡=Œ´Ģ‘<~ŋ–Ŋį.—ƒë€ë€ëĀËéHæųÄąŦīŠ!ûevŸŽĮŋIx5].ÂÍdŋ!ÖŒ™iĶ4ãįG= ÛįŽ/¯O/Čŋē0Ø Y|pŲÛŨ¸Ŋícķīāááģŧe3žŧîa—ƒë€ë€ë€ëĀ ë쇪3;öĖ ûŅc™¯=ŦĪ7ž}qwiŽs\.éI`˙ôæørpļŲęmíīíī_÷› ¸œ nt—;‹‹Øž(~Šgg0VÍÎĖ2KąĨfKkĩĨs>æåø—­ËÖuĀuĀuĀę@Ņ–ZäßQŖ—ļō ÕYęé ĪÉ%đwH ŲR3N&<ŨĀÁ¨3âGûŠÉ/5Pô€59Aė`K+úĨf~ZĪPô°øIu9¸\\\^BŠļÔ"gú+[î̝úžĨ‹iÔ—ŸåÛėĮá§ĸę đWđ+‡ƒáČyūãGœō„˛u!ä Õë-+,ĸÄo÷Čb^öõ!o!Ų‘'nžJyĻīHƖZė§e,bÃj’ū‘ĩ>ߛ­æ,@w´]“įnKíŧ‘s‡ŽŽŽĶׁˇoKų‘NŌÆ÷÷ŋ˙ūōåK<0üՁΔX˙L×ī~˙ōúë„xcũ˛üÄáąŪ˛)÷Ëąˆ#S(]R y GĀ<Ąúõå1˙ [jņ‰§ü41ҌĨ˙Ņ3=‹́‰ŒÅųü'„œ‹unŪuĀuĀuĀu`Ę:@>^Ãąŋö;€ĖMçđúLķsXęœõ 2Ŋ¸ŧŒL-ĀɃ ‡€K8Ü-Uŧŗŗ n~—üø!oĄ^ā•é(ÁîzŦ#ÎSÄž&<ÄũėŒNäÆË~8‹ģĶģöövq!Pž T¸Ĩ‹ŪčJËŪŽQIĖf8Ę <ūÄŊXŖŽŠQņĖÅXTĢö#––ĮšŌD ŠB05"Ųák9ûøėĖįææPŧˆúJņŦdlaVdĨ‚Q8ö}.//B,1BĖ 2Œž/Įė˙XĐk= dmŠá%Oŧ%D°Ĩļ_ŽČԃ‚q[j÷mâ6ôŽŽŽ¯Š¯eK]öÍŲRÛíÜ ē]ūÄWßč•ĸŨî!¯đŦ`—å. ‚‡ūöm/Ė'âöGD˜R\\\Â(\uąĖānÅė¤x•–ĮÖ%†m-25š‰5ÄmŠaŲķ ŠK¤ĀĢėāąlƒ|¤´6– KK §§gHÂ`}jįŪ*Í1ņŧ~ãzL—@NY[jĩŨ Ä Õep­,5Cmz}UˇÔŒĒŨ–úU¸÷-0}ß.s—šëĀ[́מĨŽ$†ė‘´üîęęŒĄ‘ėááŽÚ–ôÅ´u +n.pÁ2Xė}AbĮ­¨ņ×Ō|onnüx úÚí1ķ0ļÔ-üÆ6€Ķōš†"+K͎>é ,ĩl4 „hiŸģ‡].—€KĀ%0 ¯#0wĶĮ™H0âđ ˙9?ęC‰!ÎØ ¤€÷¸—´+ŦQT0ʒ)~zFLXNĮ‡ˆ*7zŊ¸Á"øioī XfØZnâ‹‹‹ķĀßqaiyl]bNB@Ãā$'ķōB!bŨÁŖ#_r`ŌëÅŠjģ/Ŋ^¤Õmžģ;ģ0nA9d…OíBíC7PA˜_cķbÜø8<_$^ĩ_ŗ~‹{ĖŋYĖRĶX$ŒŗP.ØZ¤˙Í-,]^^\ŨņyãäE0ßnnmoīná@ō[ô, /øNīá|rËyŧ.—ƒë€ë€ë¤uŗ|ĒŸuæh'"ĪP4ŪŽôģ‹Í­ĩaÛ­ķŋŧ¯ēƒG>ŽÚpųœēĪ€ŗ‡ąûsņw˙r |?Ŋųy98îĩļŋíîîÜôųlD؍ĩĨÖüübÆã‡Ģ3K åäņãáá^NLĈæw—€KĀ%āp LElŌĐĖxüā|ŲãGãÛ˙ˆ˙§!ĐĖ€Ô‘už`õ€jŦŨČSö¤ū °ĮÁY¯ĩņũß|~Ũ⃠Ÿ5ŪlŦÎĩ’Į`KM`[mŠų(Ø`K-VØâ“ÕÃ.×××ׁׁdK­ôTæôŨˆÉĪđ~¨Į{:h֗ĀĶ’Š3īQb^æiJ@mŠy"ۏ°įPäüR;KíLŧKĀ%āp ŧ ĀpļŲĘúĨ~y–”üNĀ=ŊØG>>b{ÜŌ4gnĪË%āxƒˆ~Š•ĨžyP÷xÍæę\3ĪR WÍ,ĩņKíü´sķŽŽŽŽS×eŖéģj8į…Ë0q–;Ūākâęęö×/¸…¸ģ…ķēfgКģmÍŨwæFí„|ƒSŋÉ%ā˜ŧ˜ĨĻąˆPr<䉷QËg3ˇĨvq—€KĀ%āx‹˜š-5ô‡ÎÜŨ}ãžŲ€Į¸>ģ~hāO8†nÍ4ļ[WĢ+sn60y„â)ēŪ•ĸ-ĩœžx!ļÔėøc}ÁmŠŨ.Üíã]\\ŪĒLĶ–úážq;Û¸šiĀ1ō­nã'<=Ā †ÍvÕ1„ī xa].gI ÚR7ÃĻÃŽČvÕĨ~ŠņŸžČ~ŠÉĶžøĨf_â÷ÃÃ.×××ׁ—ׁ ŋÔáĢëĐÉq,ŋÔ°–n50Ÿ~VGØüq8ûáap? s‡ä†ĶÇüúãĮYâŠ.ĨąurũķgøTūøąŊšų5âuL¸pôWĶ15>˜|–oŗ~"đ—].gH úĨ0PÆ˙PøQĮĖé‰nKíūLܯ‹ë€ë€ëĀŅLZ|žÂSlŠĮš7;ƒ‡XIö9ķ<Ų¸‡Eõ`,=h~ÎR9ugâ°øŒÆĄ$€×H˙ĮŅQocãúúú÷īūōō Ž—ŌâėDŗĻÛĶiŪøÎôĻûXõōČ.—Ā%`lŠ›ƒpB"ĖTZļĨÆPĸüt(—x§–ËÃ.×××ׁ—Ёĸ-ĩČyâ~ŠA_]õo›íœ {Kۏî?46–Ģ.†ĐŪāwQ08ĸ^ ãØ?ø¯•s°¯~ũÚŲŨũųķgnŽĮá‚8F1> đ,Úđ‘'åp 7#„ j\0ųGoÂB:\jKÍūA’-5ĮQ[j‹MšËÁåā:ā:ā:đr:mŠÃ<•æ u]6Uæė%ŠœübīšWŲLy0 G}ØO“¯Ė›đķņo¯ĩÜŧ~° oLĸ˖J Ē-ÎÆ4jķEœíímP×öĄÍ Æ!¸ĀaÃzäääÄΝ™ 6ō¤\ãJ@lŠcO§>> œŸTÚRË(NOt^VĪ ƒLœŗwžŪuĀuĀu` :@ŠáßÜØ;„Ĩ›ãh,ŗĻ˜mØ{n6…scđĐbƒHÜ{‹ŨĨ‡^û×l˙lq~føfĮČ+Yj u‹ŗEhrÁ¨˛×ƒMõÆŌRæ™R7Ø(Ūáááū÷īわīp LJbK-Ø8ÜûĖO2,ĩ2¯4<𮐰æv~Ú9i×××ׁ)ëq:üŊķ‘åÂãÜ4Š™é|øĐēŊŊ^é46;ƒŊåAˇķđx{cęååÅ> Ī(ōĘE–zŽĶšēē’׈F °÷čvģ ¨WWWs‰GÎģ$ĶQĮ°OP ž”KĀ%P”1ÆĸÄLkyRdŠÁŨb‚ĝ™ãr~iģLOßuĖuā}éĀÔlŠeâl6gڍÁjg0?*Ē53ûĄŲZžîøņ8omįÚČ+ƒK†O6ᨔŲ,ôîî.i\ôē]<„돝øÔ“m‹šËÚRũúUŒ=đúö6%å(Į%āxE [ęĀR NÆ˙¸ŋ'„›ŲĘĢ3Û@oßŲčÂ~ ["āZč>˜ŊĞnOS”Ãåārppp°:0;3Ķlĩāûķ”ßáŊA÷øLīîî›ŗíÖåÕŨ v+öÍŲ<Îbacpģžž<„ĢŽ~9€•A<#ĩƒƒƒõõuņū įĀÆ–ĀÚ;īzöšõŽũ‡ &đôūū÷ËËk€uLËEJûą…gíøÛ$§'>œô>|ŨŪ?8Ü;ģÄY̞;ąŲ]n/,.&–šÎpa+/fŠ•Ÿv[jį§ũ…ë€ë€ëĀĢčĀĶlП6ÍßŪö;|Y^€ Hkvļusy>x¸ėŽw66ֆÛ~DŸw€ŧ˛Ąļ×%ųōå’/Á͏ÔÚÛØ|Û2‹?>šÎΟ˙ŪÂēÂņôĶZÖßr LJƖšNxi6g—]ĩûĨvÛŦk΍I—s9¸\ŪŽĖ΂/&–ēĶn#eŧK ˊ››ūŌŌÜų¯ģÛëËN§9Öų‹“šŗ=—€KāÍJ úĨîíėīíī_ö͙Áā6cŨåe~Šą D6GØ}֌;•Ãö°pų.—ƒë€ë€ëĀKę[GđwUÆĶqâ jØ.ž'LÉ-ēGG§íÖ}¯ˇęxú 2ôW\ŧԖš24î­YuãSđKMš•ņKÍB"ːĀézXø—ƒËÁuĀuĀuā…t€Đ4mũSΝVÎCüR?yR‡;Ž^o-:åxr:ūĸKĀ%đGJ€×÷ė 2ہ ǚ™ÖfļÔĘš’×kˇĨv;r÷mâ:ā:ā:đ*:đZļÔõ'aŗū™Žßũū%ģŖ–Ŗ^˜ĩj˛ãé{ø™ūũû÷Úڗč—Z> ÛKæcĀčŗŗSpäđxrrrzzZŋ$Ķ%āxi [jĘjf–ė¨ÅŋgŽĨ~ 8ũķb"âļÔ¯ČÍĐâĮũĢČ"Đåārpø[uāĩlŠ#gõ8ü*ü„įëļÅŽŽŽŗŧ–-uä§G‡oßžíīī‹k˘|ũúUŪrž#ŧ‚āSi@g„ąa8ãÛÛŨ퍤vwwz# ā\.iJ@lŠĩwŗ!H°õ Rü¯Ķ™ģžžž¸šcޚėB:ÚkŧŗŅë÷ûX‚Ŗ{[¯iŨķr ¸\.ŋV0„€ąe´Ûm‹P׏Öįßž,ŧɀWŪŨŨE!gggVVV˜mĀz¤Ē„°Ėūūũûõõåüü"pyÜšŋÔbr œ-ŽDür ¸ۈöOoŽ/Nzļŋîííũē}ffûŽÆĘ\îäՖZĖ@šMäôÄhKMOÂūkŗ|čr™¸\\\^Zh?ų^ĮŪ!đķgßąNO\XX€í¨ëëßÖ`zžF Ą˙ûī?Đá¸ÛķĪá?ä’/ĮĶĪoGOÁ%0Y D[jâ élDÅĶâá×˙`ÂE,õÕoY$XŨi7œĨžl3xj.—€KĀ%0ŽĀû‚čyã,õ¸•ōø.—Ā;•Ā÷Ķ›“ËÁI¯ĩųũđ,õ 3S7›ĢĘR3'-'&Šũ4ąÔl)RĘ ŧ4'áé;īå:ā:ā:ā:ˆļÔēĢĮ|/};,õ;^l—€Kā `2šNqĄũĮOc¨ ,ĩØR_Ũžv[ę'Ø_q ¸\.Āģ°Ĩ~z{’.—Ā[”@´Ĩ–úüæAŊw4Ģ -cKÂ“]yŽNļÔ9 6Ž ˛Gv ¸\.—Ā K ä{аDõŨ?5eK=~ōū†KĀ%đ.% öĶ4ū°‡ØwG&õB5‚_jrL=xŧ‡7āÁũ#˜ęGyœļüĒRŲŊ‡]Ü\Ū\\^TdŪRīÔŲ1gˆ×įįĪÕvŋāķSķ\.?DdÍA׀‡~œâÂxZ03Cjļ Q~ē9ƒ_Å/u7 >ĒņĢØ[ÆÚÃ$D—‰ëƒë€ë€ëĀu€7øDT™1ÖYę?Ŗx5\īJjKöuK­˜™!5,=_3Ēæƒ#K]Ę ŧ('áÜ'K€W?΅ģ\\ūz°ßKīõĢiøŽúb3ņX,5NlÁ-Ûíų777ãųäđKŨƸˇg?ŌO_ãOxž+;âČÔgĮøQä/Öļž°Kāéëhę§ԚŠĶ˜ĢΞHģi# y˙¨æœƒ™ ãÜ6K€.×+—ƒë€ë@Qâ÷R`Mø~ƆÅč“ęéãäŪŧģģ[˙L×ī~˙ōúXG´Hō(yžŠīīá|ú÷īßkk_āsZqsĄ2ãšīt`Éũĸüäjī)šū. $[jŦÕ?^ƚúžøŠ&ô=CÖ!×mŠ˙z~Čųr˙Vā:ā:đŠ:`mЁ27q—ōŧ¨-uäŒGâ…ŊŊŨn¯'Į˛ lkkk˙ūû¯âæ“ųo m8Øh;Ÿ°|MÆhūķįĪ­ŨČJy—€K \ɖZ÷lŠŠ+šŗ-ڍ8ƒoO3nKí\‘ķ…ŽŽŽ¯Ž9[j€ižŌ./ĘãfÁđ0tqvvšį\ŧ\ ĪÎÎĸ=I1ũ@Rk/Z;GK.—Ā“%lŠu!{üũ‡”$<~P˜FŽ™˛-5SžĪÚ9›WälĐ>.—ŋë€ëĀßŖé{é`p{sģ°ŋ>‡BŸˆ('öK͜4[­Ņ#ˇĨvŪëĪæŊŧ}Ŋ}]Ū…X[jãņƒšŸú°wü95ōĶ#_ũöí|Ūžžōœ:@āëׯō֐Â+.xßC4āi„wvvl^/Zģ‘•ō.—@•‚-5ŖeözœķKÍ,5ÛO?ˆsjAÜnKM"sîę rWŪ.,×OŨsíúđëC´Ĩ&ķķėņCg¨ˇ0ëw:cžÚíöââ<ė­÷öö¤`C˜f8ŲÚÚø†š6\Y#ŧēējĢã,õ[h\/ƒK 'dKÍĢf…ŗ~ŠŲŠK,§Ų;5ہOģ-ĩŦBt_§‡eMærp9¸¸LMė9žĮGöø!ûë‹{ü&d'bÍkaaļđ™}}ũÛLŖ¨CR€eö˙ũ:÷âÉ2Ãß­Y0æp LVɖšWÍÂOã,`kãņƒYj"¨ŖĩûĨv~ÚyP××ׁWՁœ-ĩũføĸ<îX§'NvÎöÔ\.ˇ+hKÍËzØtžKM(šJmŠ q3?ũHĢ˙GļŽ&Ûįh—upppx°ļÔ9^üí°Ôowú÷’š\“–€ÚRŗÁ´øĮËŲR'ģ´[NĢ˙ˇĨv[Uˇ#wppxe¨:Į×YęICOĪ%ā!hKM'$ž&įĢifŠŲ. jōø)afŠÕ;u°'–ŦĎÍÃ.×××ׁ—ցŒ-ĩ™ƒœĨvøãp LYɖš-8š-ú‡=P“?‚ÔŦy?"Aeēá EeŠĢ… ]âŒŪÃ.×××ׁ)č@ΖÚĘÜYę)ƒ ĪÎ%ā T,hXHiŊeXjņKŨâÃĶNę*nāĨ9 Oßy/××××™°Ēž—:KíøÆ%ā˜žԖZHiš)r6,ĩX„0cÍG)’]pwDâÂO ÷°ËÁuĀuĀuĀu`: g&„īĨIæo‡ĨƉ-Ũn÷cģ=˙ņ#œLĮķÉqVy˙áŪžũH?}?áy žĀyßúįΈüņcÛÆŸ>nđ].R ‡ÅÆlßÁcCj…Ú Š ˜…Mmp7…e?Ŗrģ\\\\ĻŖüŊ4zĄNsЋ˛ÔõÁÄŨŨ@0Žßũūåõ5ŧīá(Dy%”Ôá|ú÷īßkk_zŊžü$ߎí%ķņŖŖŪÆÆ5\˙î//¯lllÔ/‰Įt ¸^\Ä5g2ūPÍ0ų5ám‹Ĩüt7ā\ĩķôŽŽŽŽSĐųRZúŊôEYjáŒë\{{ģŨ^ī˗/‚Ąq,âŋ˙ū̏Y&×pm_\\Č_Åcj„ĩú÷øĮ(ĘjiŽuâLŌz—€KāYČŲRsZLRË^DbŠ)<ËÛÕ 53ÖU܀s3Ķáf\Î.g×ׁŋ\hŽ2_M­>ŧ(KÃÃĻāŗŗķČ=įâåJxvvO)Ļ_ä­onnįįŸ5ũûË.—Ād%YrŧŗÛņķA,5õeŨģMgģĐI0âëŖ”pnf Ü /|¤]x¤õ°ËÁuĀuā¯Ô)ÛRƒŸÆ…šOl e:ļØŪã4 ģŽVĢU:kŖä֖úđđPbĘŦl¯owa@˛ŗģ;Y<āŠš\Ī’€øĮcXÆ'&’wj=ҞÔ3äZí¤OŊX$î\Ņ_ÎEŨp9x_pp˜ĻLŲ–vΏ0gŠ ´ĖžÂ.Û{œ•ŧãĻÃJÎØRTož ĶÛHĖ7 Š—––ž5ũûË.—Ād%lŠÉ#SØãĄßŖ2,5ĶÔ|Êåt¯âœ7uÎØuĀuĀuĀu` :đZļÔE[įĒyyyyųää¤ô×!ÖŪš9ØuġÆųaøE ŖęɂOÍ%āxŽ‚-5ĄåÂĮ&`kĩĨV–šs‹,ué>ëiōž—ķaŽŽŽ­ŧ–-uä§GNĀßž}Ûßß?==%ĸy0@āëׯōÖko f\đž'šl„z<6–:ųÅEÃR3â&ÜíļÔÎEM‹ōīū=ÄuĀu §¯eKíxú"/ļKāe%mŠK-\5úŧÁØØRĶCõû! vų™Un×ø×Ú5?äįhúĸŽŽĶԁמĨö3V^—xę.w*j[jv•—ŗĨN,uå™UÎŖ8—æ:ā:ā:ā:0x[jgŠß)āņbģ^V[jeŠÅ Ļõø-ŦŨ/ĩsąū-ÂuĀuĀuā-čĀ”ũRË|ė,õËâOŨ%đN%ũR<~°K=ËRģ-5ķö´æp[j—ƒë€ë€ëĀkë€ÛRŋSāáÅv ü™¨˛ĨÖÚܖÚíƒŲrŪí¤š˙ģÄyŊËÁåđ&tĀmŠ˙L\âĩr ŧS mŠŖ=ædŗ~ŠŨ–ÚyŠ×æĨøã‰+āŨÃ.īŊŧ}[jœØ‚ķ?ļÛķ?ÂÉt<Ÿ<ú–†ķéôĶ×øS{v6‡(G N>ū ׈¯ÕUGŋS4âÅv ŧ{ mŠŲ/5M×LFĨĶŨ–Úų9įĒ]\\Ū”ŧŠ-uũ‰į6ŦūüģßŋŧžÆžF…(¯Ŗäũ{ēpãīßŋ×Öžôz=ų‰WĖ™‹—N?~lmlŖ___ã™ŋ~y<ĻKĀ%đ‚HļÔԍã9/ĀĪėņŖá~Š tÛq××ׁˇ¨¯eK-œqkooˇÛë}ųōE04ŽEü÷ß7 LĐöÅÅE@Ûų´ÅŌīâx5¤ƒ GĮøuJâq\.—@˛Ĩ&NZĪrI,ĩÛRģ-5éā›°›D9ÜĮ‚ˇ…ë€ë@ԁײĨ΂áaĶôŲŲy— XlßĐŅ7_1ũ"o}xxōûÅ!‚gāp ԗ@˛Ĩ.ųØ+5ˇĨ~‹Ü ĩī_oCé~W\\\ĻlK ~°0YBŽZ|ęŲ{œ‚a×ŅjĩJgd”œŖt` H ”Ŧ,uá ŋaKˇŽŽwwwëĪõĶ%āxq $[j×4:X“ŊOÄMē-ĩs´Î 쏏ŧQ˜˛-5ėžqa†h™Ą…]ļ÷8sûVm"ĖŲRTo™ãÕįkcfÕ/<—€K ž‚-5ž%>CĶ)ãšÂ¤|M{Ũ/5/7HîgĀåā:ā:ā:đÚ:đZļÔY“aķíōōōÉÉIi åŽĘ~ëĖÍŨÜÜÄ_Æ“4ėIܖē>Ôņ˜.iH ØR[~úᡔĨ†%+`5ŲŗŌŊŠp;?ˇõtppp˜‚ŧ–-uä§GNŌØD¸ŋŋzzʄĖ8ŋ“ˇrļÔ6)xÁĪx<đÎÎÂxWĖKŲR3;î—KĀ%đV$lŠcī&z:ceЃw=°ŲŌĨ…!?psîÖåā:ā:ā:ā:0M˜˛-õ¸37\ŨķčÅÅyâŊŊ=IdK Į [[[аš†+k„WWWņ <‡ “‹ëųųÛۛƒŖŖqËãņ].”@°ĨŽ,5đtSļ˙›[Xēŧŧ¸ēcÛFÕķíÆÖööîÖÆíÍ úĀī&ž˛“ēČ>ÄÃ.×××ׁÖĖI>|ĀÆž9c9hõčĄģØÜZËKLp2ŽŪ9&˜Ŧ'åp ŧk |˙qõķēqÜkíėa÷đMŸ—ü„ĒĢ ÍųųEˇĨvŪŋ?¸¸¸ŧEx-[jĮĶī÷xá]/%‚-ĩ°Ô‚ĒqnKí~‘I%ÜGu0Œr}p}pxUx-[j1höË%āp d$ĩĨļ{ÅĸÚũRŋEn†ZÆí×Ũ߂ë€ëĀ_¯¯bKí,ĩ)—€K Dy[ę°7Q!›ûĨ~U™ģßįÅ]\\Ēt`Ę~Šeu–Úá”KĀ%P"ā—Z<~´Zt—pĨvŋÔÎ ģ_××ׁ7ŖnKí°Æ%āxCļÔ°#Ā>éxÎ ĄjŪ§čļÔÎĶúĘųrî´.ˇ#wxC:āļÔoLxQ\.`K=“á§å02×u[jˇĨvģm××ׁ7Ēoߖ'ļtģ]r&ũņ#œLĮķÉqVy˙áŪžũH?}?áyœ Ž}‚˜pMíÆ%āx[ļÔt~‹ņĘŗÔė—šŲJúđŦCœŗyCœsÉÎ%ģ¸üU:đ*ļÔõgqœÛ°ū™Žßũūåõ5ö5â(Dy%īßĶ…ŗ˙ūŊļögŒËOėn+;gmllôûũúÅđ˜.—Ā4$lŠ 'ffäLļÔ–ÚmŠi¨ķs"ũŧL××ׁ7Ą¯eKãŒ‡LÕ{{ģŨ^§ †Æąˆ˙ūû¯âfqT. í‹‹‹€ļķIZ G‘ƒĸÖSާ<—€K ž‚-ĩ°Ôņ„DđŅÔۛMˇĨv[jRüį>\ŽŽoJ^˖: †‡Íĩggį‘{Μ/ŊņŅ7_1ũˆžonnķÛˇoõËP x,—€KāŲˆļÔMĸĻɲ#Øw°yĮĀmŠŨ†Ōšy××ׁ7ĒSļĨ? ŗŖØ@Ë ,>õė=Î˰ëhĩZĨ5JnmŠ”A?+K]xAā7^5öŅŅ‘ōĪž€KĀ%0I $[ęG$ –ē1ÃwąæĘ°ÔnKMbqžÖmĮ]\\ۊLŲ–vΏ0;Š ´LÆÂ.Û{œ¤ŧãĻÃÜĖŗĨ>88¨ßaBnĀ{kkëÇ:CO xZ.—Āŗ%lŠKΗEąaŠŨ–šYZyø™m.××ׁ×ցײĨŽoĮŧŧŧ|rrR:Qŋ^quææ`āDOđįÉÉ1œ‡ˇ2āūlā ¸\““@˛Ĩ,u@ÎÜÛŨ–Úmˆ›wppx“:đZļԑŸ9Ãčy˙ôô” ™_ŋ~•ˇrļÔ6)°Ņ¸ā}§ŪŲŲAX8rņ@ŽûČx—€K`z(ÚR[–ÚmŠ“&]|m.ĘËāzč:ā:PĒSļĨwnît:Į|ĩÛíÅÅyØ[īííI"CXj8Ā7Ėĩa<đęęj.ëĖŪÆq‹åņ].—@Ņ–Ú˛ÔÍæ˙æ–.//ŽîØŲ<4æÛ­ííŨ­Û››•ÕUøŨÄol~M—Äņ°ËÁuĀuĀuĀuāEu;ęaXŒ}slķZ=zč.6ˇÖŌÃÉΞ@ÆŅ;ĮdSöÔ\.÷+ī?Ž~^7Ž{­ī‡?vwŋŨ>„Ē<6–:ųÅÅ`KMû1ÂVŌŊYÅ ¸ąsēŽŽŽŽSЁײĨv<ũ~A—Ü%đ‚¨˛ĨŋÔdK;΀ÁV ižŗ_R:=‘ŧ•}ßî‹ãMų‹ĨīŽîÆuĀuā%uāĩlŠÅ_ž_.—€K #‚-u<텠4ŲRP㠘æ¯ųYjačšÛÚúŪ×××ׁŠëĀĢØR;Kí@Ę%ā(‘@ĩ-5Aéf,5EaÜü@„ XjzB,5ąØ|w>Ō9r×××ׁéëĀ”ũRË$ę,ĩÃ)—€K DŅ/5aæėīlęĄļÔ8ë_ũlŠé7b¯…Ĩž‚Íœsá.g××××ĢnKí°Æ%āxCHļԄ!kaŠŲ˜šn2~ą]5ņĶnKÍ é<ŊÛĶ쏏ŧ‚¸-õ^—€K ØR"Ã÷ãdŲy(tdŠaõ?áXūQü~P<ˇĨfA8oäÜĄë€ë€ëĀĢčĀÛˇĨƉ-8ōđcģ=˙ņ#œLĮķÉqöĄœƒįĶ駯ņ'<΁ÄÁ“?žëÆ%āxCļÔÂL?°eGāĒŠ˜lKML,sŌĘČ27ëļÔÎOŋä^~čØôí2ũ›ƒËÜuā}éĀĢØRןÂqnÃúgē~÷û—×ר׈Ŗåu”\ÎAęˆŋ˙^[ûŌëõ䧜f nčšÄg(Ö/‰Įt ¸^\Á–: dÍPæ¤&ĀĶÜĮqĮÃGîînKíü´ÛĐ쏏ŧĸŧ–-ĩpÆuŽŊŊŨn¯÷å˙Ûģz€Ä™-ʡtØI‰tŌIÉvØi'%vn‡”Øa'–Úi§Ũŗd;í¤Ķ’í´ƒō;7†đ؅ääŊÍÃd299ÉܜœÜ99ŅÃ"^__{qŗæËō'D۝NĮļƒu{Žũ'"@ÖßKíē¨÷C; ņRË]Kæø_ ą5´Cų^ęÍŌr¨Ŋņ|‘äĀ7ã@\^ęŅ`xVßūôôlĩį@9To5ƀ´šųÆë×č&‘|>ąÍ‹ÅâÃŨŨzlØ^|/ĩõqč7‡ŽJ-—˛DŌOËPã)ĢRĶK-ŧĄ—ššxÉr€ˆ‰{ŠĄOcBß§h 4§ž;ˇ!étzb„–ģ^j„Ôív[KŽ‹ŅúÖøÍLŊ^īöööūņ‘šüļ7t㑯'Ŗ^j_ŸĒԈ§qīH%ņÍDŌs¸ŌKũÍôyZĸ7šūxr€Ø(DėĨVŗõ4kˇŽę˛;ˇŨ="oûŅa xŠ¯ŽŽĻßFģŲ:›Í"ūž¸¸XΏ‚­"[Š@ĐKmžEUЇBļÉĻ—”ŒĖK-÷8æúđlõ#F?Ĩéox.x.ȁ­ã@\^jīĄcލûûûi*õ´ ˛š\ˇÛĩŋbkÆ Ŧ#s4‡EˆX%/ĩŲ•ē>LāŦ^ęA"Ĩ¯Ž’2g^jęĶÔŗÉr€ˆqyŠĄUĪŲ-×ëõËˡ‡Ķŗ°pzzĒÛΈ‘˛īĄâi,7 ,c[5{@ų>?;k6›s6ƒÅˆˆ1/5všNĢV-¯šLƏ$˛|Ø0ųĢe™yŠŠËR&Čr ^DėĨ^´W†CžgLøĻ°PČ# nĩZZ‰7ĻÚ¤‘¤VĢ!€†]ŠŦą\.—Q™C #Å5ū<ĒTö÷÷mË"°B|/5Žī”šČíh/&”Nü—Ëíŋŧŧtē_úXųlēv~ŪŦŸ}tģĨry71ü‹|¸h&ÔÂeâ@ä9°j$RŠŨŨ]|ؗs|čƒĘ7_•B˛v4Á,ą”Ū‘ąÍÎą” Y  ߁‹ģ×ĮˇÄm5Ũ¸ŧšl6ߜˇYÅl"Ÿ/üp˛ëA°–Āš^jęRņęRğø“ä8——šņô7ˆ~xD`ųø^jŖȦb­yŠÍOúé Ôlc a^ęØ}„ĸmÔˇųôžō|‘äĀ9——šŲë–‹°F"đ đŊÔ:~KJĖŪ\âįÁĀWŠûĐ¯ûƒO҆ŦJÍŧÔæ!ƒyļ.ĪĪģŧ­Š)1÷Ë{ŽËXŧÔTŠŋAđÃC ËGĀ÷Rëø-°D#ĒÖšhĶIÉø!aŖDĪũARÖö%ú6*ĩ)!sę.KÔ]ˆ§a9yĨ/ˆq fq âŧÔÚSĨ^~,‰Ā7@ĀĪKPŠUë]LUęäāK2 Ē–˜Ú%ũėÔÔĢ䥃Z5ĩjr€ "åŊÔß á!īƒ€īĨļ*ĩÕĒ͝^j‰­%ˆQːNš´ųņ5$r€ ȁ8 zĪ”÷Ĩ+…*õ÷‰x$D`‰ ŊÔbęׇīĨ–ûŖxŠÅ1™4w.ŅŠ‘$ĪĒÔĒHc¨ŅŌWJä99ÖßK[*• ’Iį÷ödڎOŽąĘ3ø‡yfgO~:ĩ?a} —Gë9ųųķ'˛\ …‡ģģ%ŦЁE`čĨ– ĸâĸ6s “ /uZvU'“)\Õ)ĢRĢV-‘8ķNĐķJä99bņRĪßīb܆ãC™Ū{Ŋ—ˇ7|׈Ąus´ŧ÷)Æb|?::ŠVĢú“QĒF&¯^__1Œ"‹éõz훛įNgū–°$ +G`čĨöTj‰VmŪâ،ø;•R­š^jæā{ r€ bį@\^j̇öĐ­VŗR­bÔCĄ1,âõõĩ7k˜ėOˆļ;~ˆė=˜8ŋĒĢĻŨncr4ķĢĢĢа Ņ!0ôR{*5v­ZĩīĨöœj;â¤6ĨU“Ļ—šÚ<ķQä9#âōRÃŗúë§§gĢ=ĘÜŪŌææ¯_ŖoÄÜŨn7ŸĪ˙üYüũûwt÷DˆĀ<Œ{Šm=ŠÕK-î‰ĸÕW-yŠ‹š^jA%rī ĩ1bNä€r b/5ôiLčûÔ­Ŧ~­čÎmį _G:žØŖåŽ—!5Dh-К´;ŠnÚāũ@`ũøø|΍zž ‡eˆ@tŒ{ŠM^jšS™ یž¨c%:ÎéÚ5›5œ2âOüÉr`{8ą—žgLčÕ­]ĩĒËîÜváˆŧíG‡cQōˆ—.ŽiÁˇß!ԙ‚oÅpÔp€ĀW]ŦĀ="Š€›—é<üq^ŧŧÔÉK-Ũ“föøREvĻ6@í„ú9@ä@ˆËK=îužÖÕ‹EČÉõōeMú-›ËÁāaÁ2ÖāĪ\.•ÚŽ_iĸĀĐāˆ"āæĨƗ‡FŸÍøĄ‚ĩŸ—Z߲iĻjYŒüûnęOÛŖ?ņ\ķ\“äĀ ÄåĨļúthHQ¯×///¤× °pzzĒ[͈‘˛īĄâi,Ģ [6EËX JŠ? H°^jœG'Ü|/ĩUĻũ\ÔôRG ÁČé W›š~Ér€˜ÂˆŊԋvĪŲlöÖLČ$](äáˇF <­d†JÄ ĩZ Á7ėÚHeåršŒM°Q5æøBŠõÁÁÁĸíay"@VˆĀĐKí}é;ˇ?~‹ČŅ˙årû///Ī]˜>tōūA6s^;o5kŨnŠ\–'æž Û2I^k.r€ ȁs^ÅŨŨ]|؇øŌv“čƒĘ7_•B˛v4\šÜN‘ąÍαܚY ›‹ĀÅŨëã[âļšn\Ūāk‡nOžõpJšd>_€Jí{ŠMøswæ™9IDATt:=ą FË]/5BęvģíkWÁ-<3‘ŋēҏh4ŒmˆX/|/uߌ5Ž×÷ˆœ?Ģ*ĢØQŠũĩFÍ6ãÁĐKÍ\Ԇ'øG‘8ä@ôˆØK ß3&(IęÖî\ÕewnģyDŪöŖÃ@ßđR_]]M žeCGĻūũüŧģK‰zŊB)ļ†˜ëTžD4‘ŅIcލZŊ[ŽJ­Z5æžJM/ĩyĄNO/59@1p ./u@3žL‹Åûûû‰ŧwŧ“~ËærŨn×ū‚eŦą6//í7ŽŒcˆX#|/ĩĒÔĸOë#ŽK+GŊÔ^ÄíûŦ‡™õ¨Sōr€ ȁh9——ÚęĶĄ}yŊ^ŋŧŧ|xx0Ö §§§ēUĀKíV…ˆ˛īa%âi,[›Įëëk:™ÜßßŨ5 "5Ŗ^jҧ}­ÚØ;/ĩjמJ-wzŠŽČŊƒÔʼn99@ʁˆŊԋöĐŲlöÖL™LĻPČÃoŨjĩ´’*5ƒ ›‚oØĩ‘ĘËårYˇBløzŅf°< Q 0ęĨļ*ĩ\īæ1úŋÜūÁËKįõ§]ŖD>“¨Ÿ7kgŨnŠ\FŪMüf‚m™Dåæ2q Čr€X1đ>Žb|Øįæž@TžųĒ’ĩŖĄYbšŊ)"c›cš5ŗ6"@6‹ģ×ĮˇÄm5}Ņžk6ë_ŪĄ *.í&ō…ŊÔÔĄé'Čr`9——šņôæ=l9X!c^jėKĩjzŠEĻŨūHâ@äĀúq ./ĩæËãDˆA€^jƒ~hæ-!Čr`9‹—š*5)"@& æĨæĨļŲĒ™—šē5õ{r€ ցįĨÖN”*5Ã)"@& 0-/ĩŪ+îč‰ĖKM=›y¸Ér€XĐKͰ†5B€^js2č™Ö‘}ˆq ȁáŊÔkL°)D€Œ{ŠíhäF‹fü`^jÂ\°ô“ä9°>X/5FlŠT*{™L~oIĻíøäĢ<ƒ˜gvöä§SûÖ‚”ÁĒöPYY̧ uΆx÷Rû#ēh^jzŠŠŨ2į 9@kʁXŧÔķ÷ÖˇáøPĻ÷^īåí ß5ÚąÄŅōŪ§L‹ņũũũčč¤Z­jÍ&ßÖČd>M ž. oooĶ1ˆŒ-?{X’"@/5ĩgs —6ĩ7â@ā¸ŧÔĒĪ3ĩZÍJĩzrrb$Ē$†Eŧžžöâf “ũ Ņv§ĶŅŋŧ„Î¯ęFzyyÁHЍ†:ˇåįi Ë"°rčĨÖ;ūņû}â@äĀq ./õh0<Ģ›~zzžĻ%#,vˇÄ67ßxũ}ŖĀŨŨŧ.˜... ~¯ŗ DėĨ†> ą°z ĩĢ՜zîÜvÁđu¤Ķé‰=2ZîzŠRˇÛm-9këŗ Z­V6 +uæūūËķ÷õ,IˆĀĘ —S—Ú ]ŠįËöšä-yKDėĨ†ī4cõ@k­ę˛;ˇ=7"īiŧÔWWWĶ‚oŠÍČÔgfúøF@üļļė• Ü ķ 0ÍKŨg^jęjr§ĮÚteāī1ȁõã@\^ęq¯ķ´ŪļX,BNžøĢčëSĻl.×ívíXÆü !‘´:F[?>>ÎĶËŗ  !0ÍKŌģ§“ņ!žĖͨiÚu#ęFä9@p ./ĩÕ§Cûi|Dxyyųđđ`ĖX@ō;Ũ*āĨvĢ‚üŒ i=°ņ4––‹ÅÂåŅÆâ777ˆ×CĀD€D‡ŊԂ5õ§õ͟x^€Ā*ŧ§Ŧ“×ûwâ@Ä^ęEûf¤ēģ5ÜĪ…B2ŗ5@ĪPŠ‘™=|ÃŽTÖX.—Ë&Œží~ŧåķyÔöû÷oDՋļ‡å‰X!a^ę˙rû//×?‰žķ|&Q;?oÖÎ>ēŨRšŒŧ›XÛ÷Û!›ËĸåC â Wq äĀŌ9€>iwwöåŒ/ÂÖ_žųĒ’ĩŖáĘåvĸˆŒmvŽåÖĖڈØ\.î^ߡÕôEûŽŲŦ|y‡‚š”Mä …áč‰ĸ™™`šo™Ļ |'ũƒĮB= ȁĩå@\^jÆĶ›ô°åD`…ĐKmĀe^jÍÚDˆ9@l âōRkžRšŠWŋŸŸ1˜b§ķōôđ€AŸUĪ&X’D€ŊÔÔ×ųž ȁMä@\^j̇öŅ­VŗR­žœœ‰*‰a¯¯¯Ŋ¸YÃdB´Ũétüh;Xąfvlĩ/129ęIīîbÆËv;´,@ˆ@tĐKm°fŽæâ%Čr`Ã8——z4žÕ_?==[í9PNķeŲ F››oŧ~žŋž>íV0Šutą÷Dˆ@(ôR Dô‚ĶOäĀr b/5ôiLˆ…Õ­=ŦæÔsįļį…¯#NOėˆŅr×KēíĢÎ#ąļĒ>fUŗy ß6„Ÿļė~˙3´‹g"@ĸC€^j`M/&uzr€ 6‘{Šá{ÆÍX=ĐÚUĢēėÎmŽČÛ~tč×Ņr×K}uu5-ø6ēĖđĨ#vT.—÷÷÷ÕG+pOD€„"@/5 â÷ūÔéÉr€Ø8ÄåĨĩlĖęf‘šãūū~šJ=mËė¨ŠŦŅˆĒaš~yy) ˆĒCģx D :ōR›gåAJŪ@Éč‰xMęûēčAÜ0"N!5šMÔäØfō–°ˆËKmõéĐŽē^¯#5ĮÃÃé<X8==Õ­^jˇ*¸;0!ûV"žÆ2žJÄ2ļũũûˇŽŦÕj ?yHh3X€(˜ßK­ņ4>ä3yŠO3/5sr399@qr b/õĸŊr6›Ŋ5S&“A>iø­‘ŠC+ņFu˜T#ƒ bF ģ6<ĶX†Ķu=ĒÂ'Íf3G•zŅķÁōD`Ĩ„yŠ˙ÃE‹wL¯ˆ¤E”Æm ŸMÖÎĪ›ĩŗn–.|'1`ŧąa }ŠÂ›¸Ŧ@‡uĀo`å90™LĨRxazV­æsšeqĩ÷õ…^ķųųéĢ÷™ÎdĐ˙U*Į;Éô˛ęŸŗ#^ ģ|Ķ5ë€?ÛĀûĀŌ9€q~wwwņaŌ_؎8—ož*…díh¸ršŨ("c›cš5ŗ6"@6‹ģ×ĮˇÄm5}Ņžk6ë_ë˜äĨl"_(üĐ'éūח¤™Kl­OØ`ĮŠOĐû¸qŪGg„E‘įWņpØébĢöåĨęŲō RŖ’ˆzŸ[cF{ŲkK\-|üz´ī‘p,Đæq\e4ĪūqŲz&ļsEį"zũžZõwÅ</5UjRD€L@ ĖKũÈ\ƒ$îĮũžjn’ņÃĪõa50SõˆnG}‹˜Ŧ\MÃčė} škšJĪ‚Nd~Å[˜ ¤b_?Æļˆ}wŌi˜¤[—†ÚøBm(ŲĐŧmX™ņëN2)ļf="čŗZM(îõzzí@Ū†3r2sŲlÎnĪū~n\ƒD…ĐŋÁËŧ(sĢ[ãXĘĨ2Ž ÍpËÖ3ąäđZq˜÷؉œŸ˜{jF>¨R˙;†Ŧ|CæËK˜ãK´5ĢŠo2~ĐKŊŽBI-0žDŽ;)|6āiÆŲŦ4¯côŅQĸë$–UÛÃļˆwąŒš‰€'xÁŗ™ ĖČA{vvšúáîNˇEųrŠ”+JĨâ‡ŋ­<ŠzķĸČ,ˇÛ­¯¯bwÄôWQĐg´'›Åˇ•ÁwDÖ/.ŽņįgĖ­ncA¨ Ą!5FаĮeë™ØÎÎ ų˙]õ㎋^ęo”đˆĀæ"0Ÿ—īŠZRč•ÚäÔd^jŧđīÛ{ŋÁ1|ÆOĪĪ1õ¸LdęGÄŲ6 ŨŲAĖ-ë…íÆŒ@ĄķŦķžLÂ7rÕj‰šÂl‹ŒWpq˜/#_ē//ē-Žan]yw7‹ üŲ¸A QŠQfZ{&ļa†—ĮZÛ`r€xĮeë™ØÎopŪym~ã{Ŋԛ{°åDā"îĨ6ĄôĀ×ęÄĒÔôRSÃhQKÁŲĘŽoķîääDÛo5c,#ΞšŊ…tŠ9ŧÖsŒ?ņŽ"4dŪņö\^\<>=!āÆ^`§F< C…n UIž°žûúZ3ã5¨—Úâf—a¤†Ŋûs0Ā ÆVSŸÖž‰˜ĖđRc cHį(Đy}Ŋ¸¸°Įeë™ØÎ :ŋ›ÂCļsšī%ÖßK ?Ū;íe2ųŊ=|ŖlĮ'ĮežÁ?Ė3;{ōĶŠũ ëŅĘč/>{ĀVnģģ;¸ŧ0é'לˆˆ9ŧÔžšzG•›5WŨˆf ÛF&C ‹9–õübÛ\6‹ėykggæ*P{ˆ§mŖ¤vœ°MCcFˇ×l4ĩ „gėîđ°Ôh6ļęļŽ.n—áˀ!5`<ˆä´öŒˇaâõhuk˜ŧßŪŪ´rččö¸l=Ûé#—'bž)ü˙ŽíœöžtĨ^ęų{k<`ĘôŪëŊ˜ ëæhaŦĪOŒÅøūū~tt‚[‡ūdî #“gK$pׂ“Û=:<*›¤ø2áéáõüÍcI"@–‰@˜—ú?X-Ņ?wēÉTr IĢSšlZÔĒŊŪd<>ŽčĀY- 3¸xčŪŋ îĢéÆåÍeĢųÖCČ,ãŧ`žŋ›@Ödü0*] ¯™RđubŲę[ôRĢÖ`1áōwÕėĮ=Ę<×ßõ\ķ¸6ˆÛqyŠ­f^<==[í9P8 ŖC`ļšųÆëčÖîļx?fD`×kĄ­b"@V‚@¸—Ú^Üæm5Ždņ2/õæxˆéŨüwī&ķ¯˙;†ä!1\"öRC?ÆE=ĐÚ+kN=wn{kčĐH4ąķFË]/5Bj$Åגę*s'Īhæ¯ōîHæOė°ũÜ + X) ŗ˜ĶKHĢŖTn vôDŗ†툧ÖŪŠumÖ%ŦõFÎŖq}0¯‹×דÛ߉ۛ~,{Šá÷ĀuI=ĐÚšĒēėÎm§‹ČÛ~t8%xŠņuĮ´ā[6•ŠŨqša§47('"@ĸG ĖKũCr}øÆ`uĒY•Zĩj´yÚë¤ĻEä90ƒšuJį&îöGn”šôŽ5 ΍_$#MĐÄ3Z˜Íå\˙–௭ÄŨ6›ËęøS˜°`M K?jVHˆ@ķåĨ6B†÷ŲáĀĒÔôRpŠĶS§'Čr ÄåĨžķÛDtõz9Ãü,`tUí•gä$AVL0IŖâi,ãŖCˇ/wˇ­VΐÆZ8&,Č❜ˆˆ9Ŋԃ/yúW=€^jęFÔÉr€XDėĨ^´›ÎfŗČp‡ Ų< …<üÖ~~L_YŸTãŅŅ’„ ø†]ŠŦą\.—§ŠÔ(Œ ļL(Ļéö9"a^ę˙rš}øíõĪ ŅO&SˆŦ ŲŨÚųyŗ~öŅíâ FŪMäAz=`ãâ2q Čr€X5›jwwöšnôA囯J!Y;1K,ąEdlŗs,ąZVEˆĀF#pq÷úø–¸­Ļ/ÚwÍfũãKâa‰Šû‰R6‘/~¨gk€oŠĄH|Š*Ņ×5bû —š>ō˙"uģuĐíØōpK8——šņôFĮ=l<XsxŠ%~–$ú˜cąßˇŖ'ŌKmÎ =”1x(E˙›™Ŗƒį…øߞqyŠ5_'"@ˆĀá^j“ŧGŧÔø‚hŒã}Z=ų;kęC[ĸ ˜īES[â@ÄāXŧÔTŠH"00/ĩ?zĸi\ž2ÆČŠF”ôFUd^jjĨÔéÉr€ˆ‰įĨÖN”*5Ã)"@& ž—Zd8Œ›(Šeî¨ÔôRjŌÔhÉr€ˆ…ôR3Ŧ!D`÷R‹&=Hî¤E˜NaüՔúķĻiô/~{˙"Î>‘<'ȁØ9@/õl  á^jOĨN$S’AĘ´dü0š>T! —”Z59@ä@,X/5FlÁđ+{™L~oIĻíøäĢc‰Ë<ŗŗ'?ÚŸ°>œ ŒŽÁņbėle Œ¯a`Cˆ@<ĖíĨŪAmüĶIQ&ÄâiÕÔ,͙cŪæũ Čr jÄâĨžŋˇÆ¸ Į‡2Ŋ÷z/ooøŽC!ęæhyīS&ŒÅøūū~ttR­VŊ¸yl*^aĘgŗprģŖ'æķ2‚ĖŒąįo-K"đO„{Š%|ÖÁČáĨQŠéĨôPÆâĄTš†ør`›9——ÚjÆĄŊoĢÕŦTĢ:ĸ!ĸ^ sx}}íÅÍ6L6#Úît:~´ŦXŸT0!4||ôŪëš÷÷šĐVą +A ÜKmjQL<-(ú*5ķR›SB]*j]Š˜ĮîaīŲō0vÄåĨ †guÍOOĪV{” čĘŌææ¯ßŠŊ:Æ5iĒÔ+‰X)Xp/ĩQŠŋŧĨn;z"ŊÔĩԘōŅōũšG{ŠĄOcBß§hím5§ž;ˇŊ0|étzb§Œ–ģ^j„Ôív[KZMÚnč%iô˙vUj]7žfĄH€…‰Xá^jUŠÍëÅĐ:’"ŊÔÔ§™‹— ȁX9ą—žgLЌÕ­}°ĒËîÜö͈ŧíG‡;āĨžēēš|›ydkĒÔKˆ~XX:á^jŖÂ~}âŪŅ˙ČDŸ†§iԍ¨‘ä9@D¸ŧÔÍxFŋ\,īīī'˜Ą+gsšnˇkˇÂ2Ö¸•PĨ^z,Ä ‰ĀõRÛ^}ķ¨Üīã;E,0/uė>Bœļ~Vr€ØZÄåĨļúth\¯×‘ķîááAu(,œžžęV3ÜĪČ ‚ Ų÷P ņ4–†ģ/ĒÔĄČŗˆP/ĩqN'ûŸ_úōž {*5ŊÔæ!ƒy'Ė+Iâ@O99@D΁ˆŊԋvŌŲlöÖL™LĻPlw­VK+™ĄR#1H­VCđ ģ6RYcš\.SĨ^|–'Q#ęĨN‰$mrč XîK Ž˙0/ĩ¨ ԉUm!ā bá@Ä^jíĄõKÄ9§ũũ}x?zŊŪÛÛģk˜†{F pf˙ī˙ƒŽšÍbˏo;ģļ9›ĘbD€üĄ^jÄЈ™Œ2ôÆM4ĢčĨĻ.WRČšîÅã"æäĀFp ./õxŒûOŨ07&Dā{ ęĨV•zŸc$=į.†O”([úc(R§ÜZ/c,ēųFž‘ä€ŅzĻž/]ižæ…Tęī*đ(ˆG`/ĩÄĪø41åĪEąN99õ¨#r Er€ ȁ88‹—š*uxlÁD` ˜ÃK-šôNja´ÎŨŒÔēLŊ„š9@ä@ôX/õÆGˉų–s€^ęõŒ+Ø*"°Ĩ„zŠ!l%ŒIîįũ˜Ą PĨfOä9 ÖßK[*•Ę^&“ßÛC’i;>9Æ*Īā晝=ųéÔū„õˆet ŽcĮ`+[ßJ#ī5*A*ëÁākKŖ6ˆP/ĩ7Vĸ—›ÚÄÕFˇvÕԊļ\+Ér€ ĸį@,^ęų;î?ūĘôŪëŊŧŊáģF …¨›Ŗå=d“ūüDōé÷÷÷ŖŖ“jĩęÅÍc;0V™ōŲ,bh7ŸÉŨŨBv“÷ú #ÂT*^%ķ7’%‰XĄ^j+ņK.h<ûŠ™—ÚČ˜Q‹"ä9@ā¸ŧÔV3í†[­fĨZ=99ŅÃ"^__{qŗ “Í߈ļ;Žm+Vw&„描îȋ¨ÕÚúm%Ąmc"@–Œ@¸—ZsJ‰Lz27VęķRG¯ĮP#æä9@XÄåĨ †guĘOOĪV{” dÎ~zz˛šųÆë÷Ej¯ŽiYˇģŨn>Ÿ_r”Āęˆ˜P/ĩŒ˜˜H|ö?ĄĮęÜjÕĒȎ¨×2‡9@ä@äˆØK }ú>õ@k?Ģ#ŋ¸sÛ˙ÂבN§'vĮhšëĨFHŨnˇĩ¤Õ¤í†FÉNŽJíŽ‡Ģ¤ŲlÎŲûŗ KF ÔK­Ŗ'âbk3Ú žVL"°Ļ—šZõBr€ âå@Ä^jøž1ĄGT´öĮĒ.ģsÛO#ōļ:úęęjZđ-ŽĘÔã*5‚l|¤xvvvpp°ä(Õ"0'Ą^ęž\É& ĩDÕ_ri›UĖKMmžūQr€ bä@\^ę€f<Ŗˇ-‹÷÷÷ LSšQ8›ËÁÂaˇÂ2Ö¸•ļER|Ųh4đyâœ]?‹"°|BŊÔĒR›ŧ’ëCffŊÔņj3ÔÆˆ?9@l9âōR[}:´KŽ×ëČy÷đđ :įNˇšæ‡ÆOđo`B ŒeÄĶXF¸ėîËŨ†äæģŊŋßßßm "°BæđRËĩoãi1˙KÉŨ^j91‘{cԄxŧ@€ø“ķäĀúp b/õĸq6›Ŋ5ōF y„ŋ­VK+™ĄR#ƒG­VCđ ģ6Âe,ägw[ø=^:ėH}Ūã9­m3Ë"đ—„yŠ˙Ëæö_^^ž:x\F$-.ęƒ|æŧvŪjÖ>ēŨRšŒŧ›‰~_˛˜ Ŗ–s™8ä9@Ŧš‰Tjwwöå_ú ōÍWĨŦ˜%ū˛œ´"c›c‰Õ˛*"@6‹ģ×ĮˇÄm5}Ņžk6ë_KTÜO”˛‰|ĄđŌŪ§Æę÷HĨŦJ-Š55Z~ãOä9âōR3žŪ踇'ĢB ÔK퍕(iƒ`\D´­0ĖđÍĸuƒI´M_ã–ûÉs•ōZĐgpâ@V΁¸ŧԚ/ D`P/ĩ¯RËØ.ĻŖ4_(ŊÔHÚ ÷ čg%÷Čr /5UjRD€L@ ĖKũÃæŸļz€§[›[Ĩ>MMŽ Čr zDœ—Z;QĒÔ §ˆ˜€@h^jqNûži;Vĸ§OĶKM­”:=9@1q€^j†5D€Ŧá^jįEôh}Ã(sQļ™—šÚŧaßQ;q075â@˙t¤ —z‚ 6…P/5Æ!7´ Á:øÄ‰XĄkčĨöŸ1¨QŤQyĪxğø“Ûʁõ÷RcĖJĨ˛—Éä÷ödڎOŽ9¤ežŲŲ“ŸNíOãšĨQFÃ/ƎÁV6zAÁÃÃCäŊF%He=müsF;D€ŦP/ĩįCnŊū§&–‘ûŪ׊ôRGī¤HĖÉr€PÄâĨžŋcƸ *Ķ{¯÷ōö†ī1ĸnŽ–÷>eÂXŒīīīGG'ÕjՋ›Įv /‰1åŗY8šŨŅ1"ãŲŲYõŋŧ ?ˇ­dūF˛$ ËA ÔK­*uđe‚j‘ǐĒÍč‰ĖK-Č0-sžä9âōR[Í8´nĩš•jõääDch ‹x}}mõfwsD۝NĮļƒĢŖBķĮĮGwôDT¨c+ĻĶiŒŗh+ m "°dBŊÔf€I@­.j„ĶI3†"ķRS'ŖNFä@ŒˆËKm5ãĐūøééyšlė*ͨū ››oŧ~_¤övØÖ6ŖŨn3Á_čIa"°*æöRËŊËû6ŅWŠ™—ZÎJÚ ÷Ë÷ä9@˜đä÷ĨŽŽģÄîú4&ô}ęÖš5§ž;ˇ{„¯âņÄ …Ž—!5b-i5iģĄ a§ņŖƒ—j°•,ņ¨Y s!0æĨ†§Ŗ‘Ë(‰ra'XŋūL%åÖ`UjS ŌīģMøM=1'Čr€ˆÁK ß3&(LęÖŽR…awn{_DŪĶž xŠ¯ŽŽĻßRÛ¨L=ŽRÃKŨívĩŊÔs…>,DVĀ˜—ZĸVU›kđ’´}¯ßp 9õpqĶK-ā—9=”ā ȁ88——: Ī蚋ÅâũũũÄ3tôl.‡øØn…eŦq+™¸-"r|áøę˛W0°N"@f!0ÅK-1ŗlæĢÔ;ŠŦŌųpŒdΉ˜âĨöZ2üĀe*J€\˙)㧖`yô˜—šú4uzr€ âå@Ä^ęE;él6{k& …øûææfŅF˛< ËA ĖKũ_.ˇl—ĪŨ/O#œėgwņĐÜǟ}tģĨry7áAŠ=Lęj.r€ ȁÕr.ÅŨŨ]|؇|ĖļGDTžųĒ’ĩŖŗÄrēLS "c&ÖX"žŦŠ|.î^ߡÕôEûŽŲŦ|I<,Qq?QĘ&ō…ÂqNëäŪ’÷ClĖK PŖŠWŖ"ūğØZÄåĨf<ũ= X2Ą^jÄŌ<ûQĩæ¨V›YƒN´pî×|?JbˉKüÍ͈<ä=06ÄåĨVī2'"@ˆĀķzŠÅ÷1øB-˙õTjOĢF}q|ë-‡Áũ2Ī9@[ˁXŧÔTŠH"00/õÕ¤MøÚ7ŪI¯‡ČÚæũ VG­Ž Čr L{_:#ŸÆŋ‡TŠ˙CÖ@ž!säĨ–ˆiíKöeŽßĶKMœVr€ bäŊÔß0(á!ÍE ÔK›œØCúūÛUdĐК^júšéŖ%Čr >ĐKŊšą[Nž!Ą^j¤‡Ę4–1 ĸ=‘^j5Ä`ŖNÃ6r€ØZŦŋ—#ļT*•ŊL&ŋˇ‡$Ķv|rŒUžÁ?Ė3;{ōĶŠũ ëŅĘč/˛PcĢņp›cß0Lá!MA ÔK­9%Ė7íÆ?ÂĩQŠeŊ™3īķä9@D΁XŧÔķ÷īˇáøPĻ÷^īåí ß5bTŨ-īÁRųų‰ąßßߏŽ0šxՋ›Įv`¤™ōŲ,œÜâggÕĪá펑%‰XsxŠåÚ7—ŗ—HĪĒÔĖK P¨OS§'Čr ÄåĨļšqh?Üj5+ÕęÉɉÆĐéđúúڋ›m˜lūF´Ũétüh;XąfjĄĐüņņq|äE EžĪfŒČÚT Dā_˜ĶKŊ“LËXä”÷c¯Ļ—:z=†1'ČrĀr ./õh0<Ģ~zzļÚs \@iƐ67ßxũžHíÕØãCēŽ×ë+Ísō¯Ņˇ'߁9ŧÔ?˘Š&’FXmrScœzŠÍ]Ž^ę-ΉKm’ü'âå@Ä^jč͘ĐĒZ#ÍŠįÎmä_G:žH åŽ—!5”f-i5iģĄ÷Ŧ˙ˇĢFcmŨ–*õˇŲx€kĀœ^ęäéTra5yÛF/5ĩ"ę…ä9@ÄˁˆŊÔđ=c‚šĸhíŨU]vįļ×Gäm?: „/õÕÕÕ´āÛh7#[ģjtŖq^ĢíîîJ,Ŋ×:ú`ãˆĀˇC ÔK-ŅŗšœM m Õĸl‹JM/5č@*^Šør`k9——zūĀĩX,ŪßßOSЧŲ\^û+–ąÆ-ėĒŅ77ˇđ–hūQž}íüÛE+< "°ö„zŠ%zļĪžšņÃĪõ!A5s˛Æ—“äŠWĸ>GüÉr FÄåĨļúth3rŪ=<<¨+ §§§ēÕ EYA0!ûŠ!žÆrŖŅp÷ånĢzšĖ??EųöĩķĐļą KF ÔK-cæĨVE„^jjc[Ģ™÷°ĖGÎo ȁø9ą—zŅ8›Íۚ)“É yø­[­–V2Ã÷ŒÄ ĩZ Á7$gø¤ą\.—§ŠÔķŦ_´Ų,OˆĀß ęĨö3čáúīĢõÃ$ĶŖ—ÚAØ¨-ÄAU'â@ȁˆ9ą—Z{YũqÎiŪ^¯÷ööîĻĄ+΍Îė˙ũīœ1ˇ™@lųiÛÎŽsÎŗ ‰@¸—ZÕ¸ū'\ߘËĀäžJM/ĩ Ã|ÔkÉr€ˆƒqyŠĮcÜŋė€š ß P/ĩ˙Yĸ„ŌČúŅīÃ"jķRĶCI]– ȁ9——z!•ú; <"@f!ęĨF$í__0{|Ę,!Ŗ'ŌKM}ڐ€:Ŋ\]ā-9bņRSĨf\EˆĀBŊÔ2Ā‹g †Z"ꄈÕÃŧ¨”:MŒ: ņ7´ĻY ŧmÖßKÍȃmA ÔK-’´äĸ–˙˜TÔâĻV•š^jj´ÔhÉr€ˆ‹ôRoK¤Âã$@¨—ZUj?ĩėŌLN/5õ°mĶÃxŧä<9°V —z# 6’l sxŠĩ‘Ī`ôaõ`^jæú ‡˜ ȁ˜9°ū^jŒØRŠTö2™üŪ’LÛņÉuŧC™gvöä§SûÖB;&"ŽcĮ`+[Ā­G—ˇ%|áquC`/ĩҤ͘'w’i‰Š}•ZĩjĖ×Jˇ`{ Įx^č-&ȁī΁XŧÔķ÷ãūü9>”éŊ×{y{Ãw Q7—‘eĐCûđũũũččãŠëO⭝Ėį¯2åŗYäqGOtëŅ1įoK"°LÆŊÔũD*…\y*M~˜lO#n6ō4Æ'W_5ŊÔĖwanķĖuSŽæD'÷ļœqyŠ­fÚˇZÍJĩzrrĸ14†Eŧžžöâf&›ŋmw:?ÚVŦO‡˜š?>>ē#/Î…1´y,@ˆĀ2÷R›xZœ˛›ä“—:ąƒ0˙ÃÜWĻéĨĻ6O-œ ȁ9——z4žÕ#?==[í9PÎUšņĶĶĶ“ÍÍ7^@ˇvˇÅPįų<|%đŧđđđ°Ėø€u"°ĶŧԈĒ1  R›ØzĮ bjێ‹Z-‡Ų–ë%ā Čr bDėĨ†> }Ÿz ĩˇÕ‘_Üší…áÄH§Ķ;e´ÜõR#¤nˇÛZŌjŌvC#m 'W™~“ âu5ÜŪŪūūũ{Ą€…‰XĶŧÔ"G‹ ũCĮJôãiDÕæ ÅrT5ŊÔŪ­îû{cÔĸ„ęūxr€ã@Ä^jøž1ACR´öÄĒ.ģsÛC#ōļē퀗úęęjZđ-ŽĘÔ…[kŪŨŨEHŨh4–°""@B ÔK­OÃũĪOˆĐ:ˇŖ'2/ĩ C-ßQä9âōR4ã}nąXŧŋŋŸX`†:›Ëuģ]ģ–௭dÚļX?1Ú^(*`a"@ūųŧÔÆûĄ#“c((R7ĸnDä9âōR[}:´ß­×ëČy§gôžX8==Õ­fÄžČ ‚ Ų÷P ņ4–Úŗģ-*TĪ äp”l6ŠR‡ž ĢA ÔKŨ7‘ô×ā ÷y…ģ‚?z"ŊÔrNâĐf¸_ž ČrĀSyŧņ}GŪŽIŒl6 3&ķų`ąoĢÕŌÎ|F ‘¤VĢ!V†]ŠŦą\.—ŨĀŨ…ĩŖūãã2–÷÷V,°V"@ÂķR˙‡÷M/—›'/šę+äņČ|ÕŦtģĨry7ņŲĸ~ΈÉHØ\&ä9@Ģå˛PÁ@ŒĪōrŽ/}PųæĢRH֎FĖaáŋ#2ļŲ9،E‰øÖ\ÜŊ>ž%nĢé‹ö]ŗQ˙@f?/u)›Č ?TĨ†2­˙7ĪÖ33/5õiúČÉr€ˆ‘qyŠO븈Gū×Kô2RäĨÖą]lDÕɤ—š^jƒ:s}(;ˆq ȁH9——ZŊ˜ˆ #ŒyŠaې]l^ę>bh3VĸÜ)Í*õ m€šMŒšœZzģ™{ ļ†įĨÖî“*5)"@& 0îĨF8m†u1ÂcōG*%ņŗO{š¨S3´æĻ^Kä9 "ÎK­(Uj†SD€L@ĀÍKôx&’V•ÚŧÁü€JmLNûĄJ-ē5ķRjŌÔÅÉr€ˆ…ôR3Ŧ!D`÷RĢJí Ö•Ú,&1zz?DēĻ—:Rŋ <åđķ•ûUžâ@Ŧ'čĨ^Ŗ`‚M!D`ÔK­~ÕĒU•FÆ`~Z/ĩDØ)ÉøÁŧԊfÔ¨ˆ9@Ņs`ũŊÔąĨRŠėe2ųŊ=$™ļã“cŦō ūažŲŲ“ŸNíOXNPÆī”ČB­Üŋ˙ūųķ§I}]Đae8"Ŗ^jũ*ŅK2m€)Õ¤%vėëˆM˜!Ėö—Šáņr€ ȁx8‹—zūŽã6ĘôŪëŊŧŊáģF p¨›ŖåŊO™0ãûûûŅŅIĩZõâæąéFĻ|6 'ˇ;z"†WÄX0WWWŊ^īææ†>īųĪK%#0ÉK­Zĩ Ÿ?$|ļã< øILÔFĨĻ—Zž?˜[€:=9@qp ./ĩՌCûãVĢYŠVONN4†Æč†×××^ÜlÃdķ7ĸíNĮRÍ3ú9ĩë&„描îč‰ÍfēĩvŗŋŋØ:´U,@ˆĀJ˜äĨV­Ú<'¨@Ŋc.h$˙ĀĮŠrĩ•šyŠÍ)‰G›á~é/'ȁ-į@\^ęŅ`xV×üôôlĩį@9WiÆOŌææ¯ßŠŊ:Üm____:X>ŠÅ" + X) ķ ęĨ6Ąu‘´¤ÁŽŦĄ—šú´›#ŽXVK6Q/õđû=ĒFO‡šs™ØZ´jũzQr…øn0jÕôõ’ä9@DÎûžQu>Ÿ—īöL¨RovhÂցMD āĨvTjõR˙đ4靊“FĢöUjÕĒ1§^B͌ Čr z¸š§ú’3ús8~ÂĘēdĒÔ+ƒ–MFĀÉK-c›CņR{ČđR›¸˙Ocnrį%†^ęõųæųx.Čr` 90âĨ uÆŁ*õ&&l;ØL/5,hž‹Z˙cĸë&†N ׇ„ÚŠ|ÁŽĘ4ķRG¯ĮP#æä9@X¸^jôI…BÁtOôRof8ÂVMGĀõR›;‘šGųG/54iŗ6•„÷C& œhÕôRĶ7šor u8šæˆ3¯5r` Ü÷Ĩf°đĪuķRC;¯T*{™L~oIĻíøäH<ƒ˜gvöä§SûÖB ”Ņ58^Œƒ­lˇ­mĶÃļŸl*cyŠ5Ē6—ŽzŠÅ?­~ß"!÷ĀjÕeûiö¸lŖŋ|č "7Čr€XėûR›ņ#/õüũ=Æm8>”éŊ×{y{Cö= …¨›Ŗåú €ąßßߏŽNĒÕĒí|ģđƌH$ōŲ,œÜîč‰Rƒ_??<<žŋy,IˆĀ2xŠM(4ęĨÖüĶž‚­É’„yŠŠQ;$Čr >¸^jÉø‘Íb—z~%¸ÕjVĒՓ“Ą1,âõõĩ7Û0ŲühģĶéøŅv°—ˇ¯Žš?>>NsŠ_\\Ôëĩe†Ŧ‹ųxŠ@í\čÆKĻ~Ąč=ĶKMšī%Čr€ˆ›#^jĶí‰ņcõ^ęŅ`xVûôôlĩį@9WiÆOŌŽ 3^˙đ'SK`[­ę5Æ?ĪfW5fäüqK-EĀņR#Ũ´ Ÿ^j_Ĩ6ąö0äĻ—šž^z|Ér€ˆ—ĶŪ—Ž(ãôiLЌ]ײæÔsį6ž@ˆŸ–Ō&LhĄëĨFHŨnˇĩœÕ¤ífÃ/œĖljGu­F‰zKc9öZ āxŠ‘nڏ“õĸõŧÔæëiīO5†ĐKM¯0ũâä9@Ä΁iš§&ę¸˙ŪéÂ÷Œ Ŗz ĩBU—ŨšŨ"oûŅa`ī/õÕÕÕ´āÛ돝íĮî÷īߨ<—ŖDũī'™5ŋE čĨÖ¯7Nn–ČøĄ#QiT=TŠéĨf Ŧ9|kÁåxĩ:âOüˇŽ—ZÅ[Ė"đR4ãŨoąXÄ'ƒ ĖĐŅŗš\ˇÛĩ[akÜJÆˇm6›FãonGˆĀ2ķRÛ{”ŠŨzŠ]wŊÔôPÆíĄ7W—C€ųˆ-9°xŠŨ6¯HĨÖ^×ęĶĄpŊ^‡ãááÁü,œžžęV3ZˆŦ ˜}ÅOc9.ļĨDz"X€DĀd/ĩhÕ2I^jãĨÖĖzôRoĄ¤<ŒĮNČr`Ũ8ą—zŅ^_ ۚ)“É yø­[­–ßŊž9ÖÄ pE#ø†uŠŦą\.—Ũ]TjÔisķ-ÚB–'D`iLöRËč.ęĨū/ˇđōŌyîÚ {°ŸM7įÍÚŲGˇ[*—‘w3ŅīãÛF0#—‰9@äĀĒ9HĨvwwņaŸë!FTžųĒ’ĩŖU‹ÛėKëŒY ŽĀÅŨëã[âļšžhßÕõ <åšĻĨ\ÃģŊÔĒR›OĨ ķRS¯Z7ŊŠí!'Ɂ­â@\^jÆĶų°ųD`5ŊÔfôqœeķR#œļcS‰%„žZúĒÉr€ âā@\^j͗Į‰"0‚@ĐK õŲ}\ŠMôR÷5ˆä qWSÚ*}HųĪ;q ȁ¸8‹—š*5)"@& 0æĨ–ŅĮí‰IUŠũÉ=:ļŦ×ųF|Îvę{â@ČrāÛp âŧÔÚRĨf8EˆĀÆōR›8yR^jO™Ļ—šē,õir€ րôR3Ŧ!D`˜âĨ–ĘK}zŠŠÁSÛ&Čr`-9@/õl  SŧÔŪí“^j—GûĨ?• ȁŲX/5FlŠT*{™L~oIĻíøäĢ<ƒ˜gvöä§SûÖ‚”Ņ58^Œƒ­läą=­T÷•`GĶÆ?g´CˆĀĘ˜ėĨæĨĻ—Z´züŖ_œ8ä9°nˆÅK=ĮŒx÷øPĻ÷^īåí ß5Ú1YĐōŪ§L‹ņũũũčč¤Z­zqķØ˧ā2åŗY8šŨoœÎÎÎr…<÷ˇˇ7$žĩ•ĖßH–$D`9LöRKŪ3‘yЙׂ:=9@ëȁ¸ŧÔV3í†[­fĨZ=99m&™Ä°ˆ×××Vov7G´Ũétt“Ā+bŗ 4||tGOÄVĩZ•cÂ8‹¯~%Ąmc"@–ŒĀ4/ĩÜĒäĘ6*ĩ¸CŧüĖK­w<üŖ^EČr€ˆ‘qyŠ­fÚ?==O“]Ĩõ` H››oŧ~_¤ö#l'č†~ss# Ø`pqqQ:,…ļŠˆX ôR Ŧ˝l ˆ}Ģä9°YˆØK }4õ@k¯Ŧ9õÜší­áëH§Ķ;o´ÜõR#¤nˇÛZr˜šÖß2 [ģ*uĢÕ†hO6›šŋŋĮ_+‰X) ĄĐKMŊŲŪÊˇÅ¨ˇ‘‡ä!9đˆØK ß3&(ęÖVÕewn{^DēĶž xŠ¯ŽŽĻßFīéĖ]…æláŊ^īãŖ_5ĻĐ~ŸˆX ôRVęR›ĨKņ|ņ|‘ä8——zÜë<­{.‹PŽ'ūę*́Ų\ŽÛíڕXÆˇŒģ-ŦÕŖÕKØú7K_IŦÄJ‰ĀĐKũēĩLęŲä9@Ä΁¸ŧÔVŸícëõ:rŪ=<<ˆĐ<`áôôTˇ xŠŨĒ 6œęĨ6ĻzŠõ-œĖc×iØž r€ØZDėĨ†īēAõ@k¯Ŧę˛;ˇŊ5"oûŅa  xŠ¯ŽŽĻß&ˆŲÚUŠąßãcļKÅbņā`?đåâJâVJˆĀD&{ŠÕô!—1ŊÔĖgBm˜ ȁuä@\^ę€f<#ē@˜{?Ĩķ ¸9†Ĩ˛š\ˇ;ôo`kÜJ\•ëá÷øß˙:oooų|{d´Cˆ@<LņRûßBĐKMZˆI=žī%Čr`í8——ÚęĶĄŨ6\Čy§šícŪ Ũ*āĨvĢBVLČž‡•ˆ§ą k‡[ĀŨ‰ųôCK6ĩŗŗŗĐVą +A`Š—Ú{zĪKM/ĩœæáÖאā8ä@ŦˆØKŊh7œÍfo͔Éd …<üÖČÎĄ•”fˇf$ŠÕjža×FČåršėpˇ…ņ‘4ęGIØĒ÷÷÷m$Ë"°ÂŧÔ˙åö^^:ĪōJĸ(\ÉûŲtŖqŪŦ}tģĨry7Î)ų†Ė›¸Ŧ@â@äĀŌ9HĨ9öå_p.ß|U ÉÚ҈Yb9]ĻŠ‘ąÍÎąÄjY ĀÅŨëã[âļšžhßÕõO“ņÃDĪĨlĸP(ĐKM-v=”*ō`ÆüāØZÄåĨf<ŊŅqOV…ŊÔŲĩķŠļí}ÃļŅÃJäĀÄåĨÖ|yœˆ #ĐKMŊ“Z/9@ʁXŧÔTŠH"00/ĩ=Ņ×qũ‘Ęåm›Ž×9õTęÜä9@Ņs âŧÔډRĨf8EˆĀ˜—zCĩúGˇÖ?J=•×,9  —ša  k„€ëĨ†bm| x“ÆŧÔônōũ9@kÍzŠ×(˜`SˆpŊÔXˆ¸9ĻäĨ6ƒ‘÷MĻģŊė˜ĖŊĀÜ´ä9@‘s`ũŊÔąĨRŠėe2ųŊ=¤Žļã“cäđ ūažŲŲ“ŸNíOXNPF 'y…÷ö…zXūîîųš0ŨÜÜ0Ē!D 6‚^jUŠu0r  }/ĩ:§5q™,ĶK­xĐGNČr€ˆąxŠįī°1n‚`LīŊŪËÛžkÄPˆē9ZŪû” c1žŋŋTĢUũÉĶ´œŨ˜G•ÄŨÍMõė ŋŋ÷ŠÅ’”ˆ1” ĸmLHŅđzūæą$ ËD čĨÖ@Ú\Ņ&ŦöķRë­ß#š°zš6@/=žä9@p ./ĩjÆķL­VŗR­žœœhī‰a¯¯¯Ŋ¸ŲīUõOD۝NĮīgƒuĢéúöÃ(jVÔŠ_IļÛífŗ™6Æ-Gx=OÃX†å#đR{Jô˜—ÚĪû!NũĻ{š6@í6úoŪ‰91'ȁ-ä@\^ęŅ`xVŋüôôlĩį@9ÛĢęzĖ67ßxũãēuˇÛ-äķØÆ;x$F#Įúå Ŧ‘yxŠ=ģĮ˜—ÚxĻŊHš^jęOčOÂŪČ}™<.bNl"öRCŸÆ„žP=ĐÚÉĒZėÎmį _Äã‰}1ZîzŠRCo֒ĒIģ“—ĖÖ_…ma i4›X]¸Ņyŋߟ§ëg"@–ĀD/ĩgúpŧÔæŠ5Ÿ/úsæĨŪBMüŖČķNëÁˆŊÔđ=c‚¤hí’U]vįļĢFäm?:‹’GŧÔWWWĶ‚oŲБŠQ!”oŠđ âr/O€ŲA*•Z~ Ā‰˜‰^jŊ|]/ĩ“Ųƒ^jɇjîq2į2q Čr ÄåĨhÆ3ēÚbąx?ą€ ds9×ŋeŦŅ2XFūHÔ0Uëšl.ûúúĒËX°&y–!D`™LöRÛ÷NšņCäiīMÔ A/ĩ÷¸ą>: 53ž r€ØBÄåĨļúthg\¯×///‘”È0,œžžęV/ĩ["fL0Ik -FË0‡`™ōā™ļå!XãķDHט°€€;´U,@ˆĀJ˜ėĨö_2 4ã‡Ķž—:‘T=†yŠŠKÅĸKÉeĀ÷|OBū ~5ōÎp†ŧ’ŽtJĨŲl)80e2™B!ŋuĢÕŌ˛3ZˆÄ ĩZ Á7ėÚHeeÕ¤ĢÕ jØßĪ؜ÖX‰Â˜`;Á„bš]„ 1 æĨū/ˇđōŌé|˜ëßdĐËe’ÆyŗvöŅíâ FṺ́ķAl\öã.ë%ā ȁĨsÆáŨŨ]hˇŽÛ8—ož*…díČ3K,ŊgE\kŗs,ŊrVHˆĀ†"pq÷úø–¸­Ļ/ÚwõFũãĶäĨ6^ęR6Á˜ŧŧÔî(‰ĖKM}š:19@ąs ./5ãé xØl"°ZčĨ6øŠSœ^Lâ@äĀq ./ĩæËãDˆA€^jƒžLú2Ér€Ø@Dœ—ZģOĒÔ ¤ˆ˜€@˜—ÚdüđžMÖŧÔĒ阗zƒ´jđ|AߒįĨÖN”*5Ã)"@& °x^jÕt“Ķ´ú Šy“ä9@DĀzŠÖ"°FĖåĨv2h2/5ĩŽoŠuņ=ÄäĀÆq€^ę5 &Ø"@æōR{4„yЁ=—蹌@3“û šAnr`ũŊÔąïėe2ųŊ=$™ļã“ÛÜŌH>Ŋ'?ÚŸ0Æx 8A5œĸđŪ^Æ-0 Œ–áDˆ@l,äĨöG{ĸ—Úzʙ'ÄF„ Sĩ-€¤"ø˛Ņh¸+ä"5ôRġWãáąķ9@l(âōR[}:´ÃŽ×뗗—FT`yîtĢ€—Ú­ 3&ĘC‹ĮŖŅĀ2Ė!X¸ššŲßߡåa8AnžÛû{wehÃX€å#@/ĩ`Jũx=”|ˇ@Ū’ä@Ä^ęEûāl6{k&ä.ō[­–V2CĨFZ­†ā6k„ËXVųšZ­ †ũũœÍi•đ{ŧt:ؑúŧĮsZ/Úf–'Dā/ķR˙—Û?xÁõú!×ŋq­ ™d­qŪŦ}tģĨry7ũ~ßßJ|ŪÄe‚8r€ –΁D*ĩģģ í6įû"ô~[žųĒ’ĩ#Ī,ņ—ŊãôÍ×ÚėK¯œ"°Ą\ÜŊ>ž%nĢé‹ö]ŊQ˙ø49§Í§ĨlĸP(üĐ'iŖ˜\Ԟķ˜yŠŠm̓Nä@œˆËKÍxzC#6›ŦzŠ žôR3g9@Ɓ¸ŧԚ/ D`zŠzŠéĨ&Čr`9‹—š*5)"@& æĨūĄ_%Ģ‹Z—SÆb×Ë/Ĩ5ž ĶxxîøŽ‚ 6šįĨÖN”*5Ã)"@& Āŧԅy¨Ķ“ä9°q —ša  k„ŊÔÔ¨6ZŖĸžīˆČ­åŊÔkL°)D€ĐK- ‡r=”ÔÉ[r€X/5FlÁx‡{™L~oIĻíøä6ˇ4ōIīÉO§ö§ņÜŌ(Ŗ†Œ’ˆÂ{{[9‘÷k‘ĘzÚøįŒvˆX9ôRSŖĨNOä†r /õü3Æm@ŒéŊ×{y{Ãw Q7GË{Ÿ2a,Æ÷÷÷ŖŖ“jĩĒ?Tރ—ßŨÜTĪÎŪŪŪŪß{Åbéėė +1"#z¨˙åųšm%ķ7’%‰XôRGj]ÔēČr€Ø8ÄåĨVÍxžŠÕjVĒՓ“Ą1,âõõĩ7k˜ėOˆļ;ŽmëÖ/߯oo1Œĸæ@ú•$*ÔąĶé4ÆY´•ĖĶ<–!D`™,âĨv3~h™›Ö0×q Čr€ˆ”qyŠGƒáY=ōĶĶķ4ŲØī=ŊÍáß°šųÆë×­ģŨn!ŸėģŨn3Áß2#$ÖEB`/ĩŽĄčkē’SĪ_ŗqچ@D˙4ũĶä9@l8"öRCŸÆ„žOœĐžV­ją;ˇŊ0|'vĘhšŠLę !5bOĨ۝üõØ’Fŗi ÂK 5ØJŠX˜% 0¯—Ú¸ģTa^ę­ũž^Î>ßKđ 9@Ŧ "öRÃ÷Œ ŊĄz ĩVa؝۞ķ´ī^ęĢĢĢiÁˇŅ€†Ũ=*„ō ˙ôÁÁ] /5tëF­F/õ#VAūŧÔĒI}Ņudät+ŽnMŨ—^Lr€ ȁČ8——: Īč|‹Åâũũũ4•zچŲ\ņąũËXŖbųC QĢڝ‘ã ĮWߐũw!ˇ"Dāī÷R›įcҧÍRü3:ĨøĒéĨ6¸GęÄū¨˙æÅRŠP(ā3ųŸ?‹įÆ‡ôXK;w__Ú77ĮĮĮ¨ķģÛ[ķ´š´úį<ī8Æ'Ņž9ˇ%¯ĸ?_Äü1ËKmõéĐŽˇ^¯_^^")‡éBX@ž;Ũ*āĨvĢBČ Ų÷4†GŖe˜C°pssŗŋŋoËŖBõœ ~¸>ŧ›@hĶX€e#0ˇ—úSŽWhڞJ-ŅK-gƒ^Ė ņb‚ąH2Õy~~ŧ‚tô =–ҁ–ĸ)ĸËC$ 9 õŖÃÛÍfÅŅ97ô­‘ģ_žGZĘų]OXĪŌī™{ŠíŗŲė­™`t.ōˆ}[­–Vâ]­“jDbäî@Ŧ ›5RYcY5éjĩ‚ö÷s6§5Vĸ0ĸvŠ?ŸGüûĪĸdy"@–ƒĀœ^jĉ~_bhy¸6ēĩyõĨsj-˙¨ĩCÃæÕrÉr5™NŖÂËĶ›Û[Ũ/ÔëįN]–áSD´-1÷¯s,+ˇĄõĸ'CrY3’‚\Î÷zļ~ŧ~…ĢŊ –A”‹š–J˜kċõØãÃÃĶΟ?Â$%___­ÎũÜŲiV{žžüˇCÜÆ¯G›“ĮĨ Įu8.ŋž‰íŒāŧŦúŧŗūī}ŽØK­ũŽĒÂsNP”ņ° ¯3ŌIģ†i¸ągԀ{Č˙ū÷?Čá˜Û$ęäöæ&§5j@´ũøø(õŋKũ3Äī9ĖbD€ü%s{Šŋä™ē˙™QĪ8@čĨŽ\ƒ¤Ūö/ēo@¯-—Jŋ˙–ËÆŧoÁˆ 荰 u] ÄfĖąŦ˜c[Äģ÷÷čÛTd œ‹Z­Ž¯‚îî>ūüŅ:mHØęņųšrtÔn_ęzÛŊĮĮ{l%+MyyšÛh ˇFŦüül2ÔNoBä֕‘ģFķđÚfuë›vņt§ķ\>:‚ŽeËÖ3ąú˙nËëwéˆËKÍDupp3"đŊ˜ĮKíy< ûîTķ —ú{k?ßOÛ äP‡–Üīãå‹hƈgõÛy,#˜ŽTŽÁoĖ€*Øąo2™† „ØÚÜF4u ĩ?z=kŖ÷…”ÁČ ø5ėKDĢC9Čwįĩ*<:*|ô”KhŌK§3øúBcš&=֌öėKƒƒē>öĸ~q™ī¸Õ­!{ã)›”ŠEĢ‹KĢüz&ļSÛ0~ŧ\OLցqyŠRŠŋwÁŖ#D`ˆ@¸—Ú(jŽsÚdü —z9܀ĻHké:–ÕY>ã¯ÁW*•RüÁđl&ŖËŸg{AäįįđŒäˇ 0ĮkW[§{ž˛Ų ĖxËäVø Šĩ ʗËŨ"–í}xÛbūļ˛3]nˇZ_ƒÆZ;­T^_EAŸŅx4Įą˛~qqwž1ˇÚ<ŽAvŅDÜōžØŋŠm=ÛI~N<×ŧNWw.Šm,^jĒԌĸˆ˜€@¸—ZR劎•Ú[–ŋéĨ^ą÷÷ûéÄņęŸ1F5ķûEҌmÛ$ÎöÃÍüaßÉ_5l˜a†>ˇŨwŠuûæJˇEy¸8ēˆqÍ˙u[ãŠö4fģŒčA9l‘Č„Õhč Éií™Ø†^j ĸl4ĀDÛ{\ļž‰íÔ6Ė8ŪxĪ)ÛFü×ßKÍȃmA ÜKí§Đ͍ÚHjôR›(ˆ^ę Éõa}ÃĒš_‘ņ ~õäDĪŖŖgËg‹ƒ @ÖĩÛbåį`đtGō¸v{yyņüô„€ežūü1YŽ ē-Ę?=<`}÷õU3aŠ.nųc—kėŨh>į×_1ŸÖžņ6˜âC÷đ›­§\žC&/c ŋ¸h0™ÖNō|"ÎŧöՒWTž^ęm‰TxœD`#õRĢīĸ´‘ĢĖrĘs—2/55ŧ Ō/­Īø°T†L‹‘ęNÛījưm ‚iĖąlŊÔšl?ž˙ŽÉJšÜc?Ģž}ô>â 0œšš"3KĪІK%ÄĶđ~čļŽ.n—ņ+ämÔ#uŗåyЧĩg"÷~qsÉzí<;ĢžŊŊá¸P÷ū>šá]Åļž‰íÜ ķKŊv 5{zŠ7"Ė`#‰Āļ æĨū/›ÍŊŊŊ<§vvđy>Čd˛æéŅ냎oĮųDũd8J\=ēdÔiĩžŸŸđ2œ]Ȓ‰g×Eƒ×Vŗ3îŲ Ũ’xW&Ųņ‘JčüüDßČq"D`•\ eÚ\Æ0ŸĘVĖKŊžš ĩ´Uœäë0WĈ¯b_Ŧ“8“sr ./ĩ~<<Ī×’ŲÃáÍē!bkDØP¯%ĢŊg_\\ėėdđŲä‹ŲLī;æ0žî8>>ÄVęâ€Ú}\9ÆæøÜBÃt ­ˆ’:ļ" kŽ}Lš\Žûņ1OkY†BĀņRk:^sRVč’…ÚäĨ6šš¯RĢB å0ŖCČr€ ĸä@ÄyŠUF߇šÕĒuäwn{e§?đŨQąˆī›aư?===`d(ü)¯úë‘ØŪŒ*•Līîę@­ÚķšņĢZŊŊŊG|Ŧ+QUŠTƂäō7ˇzļŽcĨj›ĶŠũOÁ7&DĀÉK =ZõiõRkp •ZÍՒŖ@”iCQ2$ĐK=§Ž$Š9+r€ VÁˆŊÔĒŖÄËBh†{wĄ"Cx†Ž ?"lũŖ/ŲnԆŧŊŪ‡Œ*5:Ą’íf2Íí/Ø5#˛ĮWĐĒmôŦ :Ž•Nęa´CˆĀĘ˜ËKmäh‰ĒŊxÚWŦ}õ:JM‚ûĸFä9 ųŨ§ãkõÚUtĸšTvŅ N ÄͰvč†;;)˙õī—¤ĸ7R—Œ[DPˌuƒ ÜūŠMa›Č^&=ëĸm$š×e˜@dTND€Ŧ9ŊÔÉ´ÜBŒJ-?ŧeŖ[›ŌcJČr€ "åŒ÷ĨķgÉø‹NÖęĶĄÛâA¸24ÆYíŦÅš\>Ō$wíö ü!Zž>D؍úΟ?ŋ~ũŌ•z,P¯ĄvĢWĶQšŦy<0x“-‰tŸĒdW*ÆQ•ŲJŗés"D`•¸yЧxЇÉĻ5å4ŧÔ3´j'ÔĪČr€ ĸá@Ä^ęE{c„ŗyá õĸT*BTļÆHΰs ãæ6ŗ^Ŋ^‡äŒtØDjÅdwäāCĖŨ8?ĮJäņ€{{/“A­žL×××HĶ6>a<::ŌJ° –m9Ë"°0Ž—ÚĻeÔKũ_6—ÃwĮˇĪ]Īb=H”ŠŲķÚųUŗūŅíb”cž%nĢé‹ö]ŊQ˙ø´sĸ”Mā%2~اdXŠņ‡äĨör}ĐKÍ<'FBÁŒšq ȁˆ9——šņôļ„Hfō ļ‘ôR3Ŧ!D`÷R›1˜LļUqQËÜ(ĶĶ´ę4Ņë4Ĝ˜“äĀr€^ę5 &Ø"@ÂŊÔ}͉gœĶfŽ 1ŊÔÔæųŽ€ ȁx9°æ^j ļ‚œĶÅbų§‘"úōōō/ĸ;ļbčļnI ƒŨÍŋmhå,@ˆ@á^ę”ČÔę¨Ū‘qÅKmæžV-ŋōģûíūîž0—ŋ+⨁ä@ÔˆÅK=`QŠcÅNᏠ6ÆjąŖēĖ_Éü#Aē%1^ <ßķo;{X’É„zŠÕī%FÆ)č`›‘œ’ôRĮĢÍP#ūä9°åˆËKĄį )>>zļ$FGĒŒ§¨k `c\C¨×˜ëˆå:5į{{{ˆīîît= (Ŧ뱌us-ŲŲÁÚĻĘôûûûããŖŨvÎŗ @¨—4.Öã¨Áē/.jUŠmŪęsÔéÉr€ "æ@\^jÁÎ5ĩZ­ršŒAČŨ YˇDlę5æ-?Ξ¸¸ØŲÉŧŊŊužŸ1ną–TĨšÛíbŦql…ÁÆMäŨ8Žcķŗŗ3 Ķ{ŸŸ(ŲÃ>M:gÛšÚĘBD€ü#Ą^jUŠeÄD‰§ÍĘļÄĶĖKmnĢĖ=ÂÜ#ä9@ÄāˆŊÔĒŖOÄÜj՚SĪÛ~Yã鏏Šáũ°?===TĢUü‰ųƒŋūūūĸ‡Mīî"×Â8Ƈ‡‡_ÕęííŊzU•JeüŠšmĩãš4UęŒ‘¸9XP/uŌ¨ÔIuQ›9™5ĒļOĪÔf"Öf€<1'æä9@DėĨV }"æXÖžVG~qįnŒ *2„gčʡˇˇˆ°õ×ĪĪaú,Ģ+÷zđ‡ēpc§ĶŲÍdšÛŸ° jFdŸÍfĄUëúqį4ŊÔ ÄC,JūP/5†xŅīõiYú0?ãķR‘-÷2R7ˆq ȁˆ9——Úû ÁŪ÷āāņ4ŦēŨÎNJ%d Klķrd2Ųq‹ŠAąnßÜ (ˇŋbDØ&˛—Ië¤JŊā9aq"°TBŊÔĸRŒ•8P]„^jęCÔÉr€ˆ‘qyŠ­>Ú#Ũ\cŪnˇXëVåōūÄBģ}ˆŽÄˇ†ģ˙ųķįׯ_ēR•f¨×PģÕ+‚é¨\† ¯¯¯ļdĄPp•lģmh;Y€% ęĨV•ÚøÕ4ËG˛ī/ĢB  N“0bMˆįs^īäĀúp b/õĸ.‚`„ŧđ„Āx]*!*[ã$gØ9˛s›Y¯^¯CrFēl“´îÎjĪČÁ‡˜ģq~Ž•fîíŊLAļzN0]__# LÛļôR/zĘXžü=a^ę˙ršėKįåųųÖx>$rÜß/!ËOŗyõŅí–Ęe`œöTj‰ĩéĨĻo2Bß$ũúün \ĐKŊŒ@€u"° BŊԐ SCž–ŒÔfî ĶôRS›į; r€ bãŊÔˈX KB ÜK­š¨“i‘ĒÍ܎›H/ĩ9 ĖyÂ|/ä9@ÄĀzŠ—°"@–@¨—ēoˆž Ÿu.*ĩĖéĨŽM›‘SB˙ēąųæG'ļ™ôR/#`D€,P/ĩ$¤Ö¯š‘EĪø†eæįú —šžFęôä9@ÄÅzŠ—°"@–Ā^jĢRCĢVYPūÃŧÔÔÆļYãą“˙ä@ė —zQë D`IĖáĨöTjÄĶPŦ1§—:.=†ûĨHä€åŊÔK X  Ë@`/ĩ:Ē=ķ*ķR# ŊŧĖu@ä@œ —zë D`ĖéĨ6ēˆ(˛O™ ŦVí9­ũ¨!QC"Čr€ˆ†ôR/#`D€,P/ĩįŸ–˙$æãĨ–Œ2V9ķNPĢ&Čr€ˆƒôR/# `D€, P/ĩfüHĻLčĨĻ>Mž ȁ˜8@/utą÷Dˆ@(/ĩņLōR##ĩ—ëCrT‹nM/5ŊÔĖÉMä@| —:´‹g"@ĸC āĨ6.jtc^j8=ŦJM/5ũĶ+Ž ȁ5āŊÔŅ… Ü ŗ˜čĨÖ8{ÄK­Ę´Ÿ—ZGOT­súék$Čr€ˆžĖKÍ ‡uA`ĸ—ZUę/ĩąT›ŒÔ2΋1ÎŅKÍ\j´'ā bāŊÔëI°D€™Y=ũA?åģ¨5?ž™l^ęĄJßčĨĻ6¯ !ā bãŊÔ cˆX#&{ŠŊ8{˜—!ˇčĶĒUKŪæĨf.jęĶä9@ÄĖzŠ×(ž`SˆĀ–#0§—viņOû^j‰ĒéĨĻFË\ä9@ÄĘzЎ<†áá5B`N/ĩQŠÅâĢÔôRĮŦÍÄ5V÷Kī89@Ŧ čĨ^Ŗ`‚M!D`N/ĩQŠ‘ÚCũŗ&G5ķRĮĒÍĐĮ}nbNĖɁĩâŊԌaˆX#ņRËaôRbøļû•×$k—m ˙ɁõáŊÔkO°)D`˘ÛKmpĒÔôRŊž9šõŊq ä9čĨŪō†‡Oց9ŧÔęĸ6mÆ<Ĩ?čĨĻVM͘ ȁ89@/õl  sxŠMj//ĩDÕ6ׇ öB?15Zr€ ȁ88@/5c"@ց9ŊÔČH„>xZ„a^júzÁbzĘéí&ȁX9@/õÅl ØræôRĢJŊŗã›(9Ē™—šēTē}Ûæ–E˙:Į,$fãëŋAŨōž‡Oˆ@„„zŠũļˆ2Ũ AITŖRŖ[7Ëqzé¤ylu2r€ ļôRG,pWD€„!ęĨÖ IC§–Œ¤žšyŠ×*?+ĩ[jˇä9°m —:Ŧ‡įīD€Dˆ€ãĨF:2›Q\´ ƒÁ6Qå¨ÔF™–×ÎЧŊŧԉŠĶ“ä9@ÄÁzŠ#Œ¸+"@f"āxŠ!Cûq˛qjšāú‡›hV@ĨNËĸ„ŌÃŧÛĻ‹đx-!¨ĶĶĶLņr€yŠã"°.ĖãĨF íkÕ"VĢš^jz¸9†9@1r€^ęu‰$Ø"@ŒäŦƎū oŌãyn•Ļņķ=CŸö¤IÄÖĖKM]*^]Šør€¯äOŅtdÁü'ĖøÁ‡¨zŠ‘Éc’—ZUjņR{ 6čĨfžæZ!Čr fĐKuĐĀũ"0 1/ĩ~8ÁK-Qĩæ!6?2/5u2ędä9@ÄËzŠŪ"°.ŒyŠÍ;4/Ģ"g/ãĮĐ?íÉÔĖKŗ6ŖQ¸ËĀۗ˜į]ŪÔņŧ¯ĶĩO/õēDl &|žčĨ%Z~đ3~$Õĩ&s1†¨J­cĐĮ¯nÁör€ ž=čĨfCˆĀ!0ÅKí…É&/ĩņO3ĩ™GOT…€Úĩ+r€ ȁX8@/õÅl Ør&{Š%ī‡ÍKíĨ¤–DzHüŅĮoĸUĶKM ėÛk`¸ xŒä99°Î —zËc>X#&{ŠÅĶĄ_!ú^jWĨ6–æĨϟ˜žRr€ bäŊÔkL°)D€ĖëĨ6‰Š=}ÚÛôRSģZgíŠm#?ɁoĪzŠÃ"°F,æĨ†ßC !žŖš^ęXŧƒ1jB<^úeÉr`­8@/õÅl ØræôR{?R^ŪqTOŗęÛë"ôךK†9^˜ë† âįŊÔ[Ãđđ‰Ā!0¯—Zõi˙ÛÄDŠ^jæffŽ^r€ âäŊÔkL°)D€,æĨ•Z2~ĐKM­šZ59@ņr€^jÆ0D€Ŧ‹xŠ“FĨĻ—šēTœēÔZų8ékgž r ^ĐKŊFņ›BļyŊԒņcôķ~čč‰ĒUS§‰W§!ūğ ļ–ôRoy ÃÃ'k„Āŧ^j3nĸŽöb2~ĐKM­šZ59@qr€^ę5 &Ø"@ņRûŖ'úú4ƃQ•šų˜į„ Čr bĐK͆5B`N/ĩwLx×$§žĖVm$kĖč)$ä9@Qr€^ę5Š'Ø"°åĖéĨ†ßCrQĢŖņ4ŊÔÔæ ßQđ[r€ˆ“ôRoy ÃÃ'k„Āœ^jQŠM^jõR{ú4âqęĶÆYN]*J]Šû"ßČr —z‚ 6…EŧÔ"KŠV-*ĩ¸¨éĨĻPĢŽØ?JĖyŨ‘ĘzŠÃ"°FĖéĨ6ú4ėŌFĢVīŊÔÔ§ųŽ‚ ȁX9@/õÅl ØræöRËį‡6/5ŊÔÔɨ“‘ä9;čĨŪō†‡Oց9ŧÔÆ+ÜĮ ~õRĢJÍŧÔôO3Į 9@qr€^ę5 &Ø"@æđR›QuÜDņR{š¨éĨχ˜br€ bäŊԌaˆX#æôR›ÜyĸR'á¨įŊÔqj3B Xũ‹Ėˇ@üÉr`8@/õÅl ØræõRc4ōDbgĮhՒx ã&ęœ:MŒ: ņ§Ÿ• ļ–ôRoy ÃÃ'k„@¨—ÚoĢ(ĶũÁŅŗÉEmæĖKÍ1#Š—“ä9čĨ^Ŗ`‚M!D ÔK­!’†ZÔhŌŽ>ũņņ‘NãļF­:ÎņÈ?ߓäĀ6r ™D$}ŌØûR]É"sxŠMc4í‡ČĶjĨ–ŒŠÔÎëëë××`gg'•J!æđZs™8ä9@Ŧ”čwĐ ĄÂ^ãøævĶiq"D€D…Ā[oŨ‘;OĐ7znyž7S˙—Ííŋŧŧđŋ^ĄPČd2ÆĶ&kΉ D€Ŧ^¯‡ž)‹ž'“ŲI§‘čU'ôdoGôm‰B֌°¨ŨįD€•!đÖKt{ƒÖq2›N^´īęúĮ§į$JšBe Šß^^n_áĸ•ķũL˛Ņh´ęÜŖžžžp_ÃdĸqoŌ;—N\&ä9@KįäÄĶ;™L:özIŅ„ŧČųãĪ×ŨKâĨįŋZõ×ËkV.ëëfâ@ȁåq ˇ3¨–ŌY܍ÉÆåÍeŗ‰ûĒˏÖJ™DĄXĒÔOc^ʉJŨĒWĩ“@iLŸŸŸōޝ߇íŖßīs™8ä9@ŦŽI¤ JĸĢ’ĖęĶŽVÍeÕė‰q "æĀPĨVīF"QĘ& ųÂģŲũîÛËũĢ1]u1kTęZ…ΏžĩœĪyË{ÎŖv2ގbBä9@l ŦJ-q˛‰Ē‹ŽJ-!ĩoã(撍ķķvũŒĪ=?÷Po Eä9@äĀ:sĀQŠ5¨öŧÔ?$ˇ<Č<ŋ´ŅĒ×aÜ,ļãˇ‘ä9@ä9@Ŧ?üĪ ũŒPŠUÃˇ†y)—†JŨǟQŖĨNOä9@ä9@XŊÔĒRį PŠ>mÄjoî'ĻÆšõy&āķĪ9@ä9@ä@ĖæĨļc;yŠ‘DĪKĄ×”ōéķÆų ŊÔÆČC?Ķ:û™Ø6ō“ Čr€ ĸ䘗ūŽ$ōRû^jWĨ6žj>Åü ¤šŋÍ;ž â@ä9@äĀ:p`ĖKO%җ—úļŖ^jYY6*õ՘—ú÷īß777oooHDzpppūëWzw÷Ο?­VĢĶé`åūū>˛ī!-ŋ–üøøĀ¤äų¯Lz—>$jŪëÆˇvģ öî€ŊBÔs°ĪúšBÁŽjŲžŲ‹Eaģ“ V×Ėx>~¸ģģŊŋĮĩUŠTŽŽt[\#¸X°Ėq`s ŧŒĢæę抧R9ŪIŽŒšôëqbl3ņüēØēø+“ģ//ļ…^›•ĶĶëkĄĸ[[fg'ˇŋūTœt͏īooÁ7mڏ+ĸûņf"Ž’vd<<<ÜŪŪ æ+''1 žÚm°îš×û֕ŠÕǰmøĢvn0C9æĨöãi``˛˜¤ ŖúčũũũÉÉÉķĶpĘÍ&Ę Čfŗē 8—ØęūūĄÖ<>>âžÕh4ų\ĩĪUlƒËC<ōĄû, ĪčīīqGk‚Ō†ķčš:ĪĪč,qs‡KĨxŽÛjVˇ'O΄÷ ¸‘==?ßāFusû„éáAˇm5›W-Ėčæõŧ`ĄVĢņņ^ą(Ā@°ÔÅ ņ֌s5.l;¯¯ ÷Ųų9.ĢFãŧĶy‘LމŽ/šĘükMãT\˜Ē.ņ°ÛžÁĩ9Ūf\ŋ_ņAũÔ÷x¸~QÃču*€˜d+}p ´AúĩLWúDüŨ"ĘA¤bÛs˙ô„€-|ŧŋ/ŠįØPú>é%‡Ŋ¤wß°uv__›­@x(úT8ÆÁ×WĨZÅžĖ˙sģÕÂ’zwB=ˆĀėŊHīQ˜#ĀĘå˛į¸˜2ãunĘz„ĮĮĮˆū×yÆs‚ Đ`ÕŧÛL<ŋ.ļ–ˇĀg§\*ˇ3ôڔxZŋg ėĩp{ŗ™“î`Ō;ę§§g<>%Ķō|ĨÛú×Ķããŗ‰¸‚FŋƒÎ}%ũŨÃÃ80T6DÃēš°n'Ķ4ŦÆqũE;7š‡á×Ë/õ”yđU}?˞ ĸ¨ëÍ/UQæ¸'â^ļČĶéŗŗ3y:O&!ZŸUņ´„gw?sûčŖzŊl‹[^>ŸG˙ąY’ķëpžĀ@ˆ”úVPۃ@ Ί–Ģr‡l˜;¤s/ÅŊÂíáņņáΟŋŋęōΟ?ŸŸ%ČĶû*îŊz_ÅōđžũôtzZÅ-Ø\,^˙` Â\㘠ĀE%Åũ}\?ā<â”1÷Ú`ŋpûp'×KWĖ."´jŧĖk§ƒŪ^wļ vŋ¸~ŗŲLˇÛĩÛ⠌ čáƒc×ctÛ ũZĩ*¯aĮŽqԃgcČĨîžĐ!âĻ!ā§ĶGå˛ę߸‡āv!7ÔVC/éŨ7lĐšĄ%įrû؁Æ4oûĻn ũŠŅ˓¸- Lאõ —ŋA G<är`C¯ÍŪĮ‡å-0<,•Y~}A—=4cĪ%° nۘ뱌XœÄ)>ŽTĐwΈ1&ōsbl3ņüNģŪAT„ 㘇^›8›^ æ÷#8FĐáĮŸ?rE× ôÍŽJå2b0qډ~G/Ģėî.^w˜fØĮé2`ŽË`Ęę]ÃēKŋOü‹vŽÃŊqumH%SP˛…Ģ2šŽö 6/ĩFÕÆû!C“Īōī⾉ûč‹gĖīî°īkä][ąˆZŨg5Ü×pG^Á&ˆzņ|uŌŲ;œ$"! xœÂOK˜cYël_ļ˙ā™Ī[xÉĸû‚X,P˛|t1P÷….Ž4ÜĘĩ“PMq3JĸĢ@¯ŖuĘ;ĶR šA›ˇ/ĩNȇRFJūÂü›?omĨoÜĀíxĸūÚõ *J‚įVKP.Yž[-Đ>Įģuĸ‡†Ū å3ŲėĮĮoyg§û Åęw&“…Ļˆ ÷˛Āĩŗjm†õë ÄŊëĻˇēoŅÁ#ļ°:ŽšCž$wHÜĨ­ÆųųŲC°‹Ô]ÆũPëąw`<ųĄËD0×W7¨—Ŋž$”L$­†ęb‹[=.„ŌáODĨ&â‘ú! #‚G=åÃCéãÍ5ōųųééđ_åpŊ|L;G34xÔc‰ÕūuŠ€á‚ŊŽíļVˇF{đØi/ģĮ¯ ˜4Đ0šG¯ũĄöüņ!ŨÜč}”ŊoØ:qÍãųwDE€bâ5ˆ `NۗmŗĢĶ÷žūLė7î¯Õë¸â|!°x"T[Ā"ŦÁŨ! ēf,c Ö#’ÄʓŖ æØÖž÷›cLä§=nlãâß;ŋîšÆyDė}>đsöĩ9ÎIÔüõĮœÍ}‰Ļü,=EĶŽ ¸įũŽå'ډ‹qŧ¯zĨ’<'ĪîŨënF;ŋ}_6ÍK •ZΚÉHíkÕējL?ĐxĪ÷¸M@T3q‚"Ā•Û"^==Ég[ 4üdãĪ ˆëÁ~<ßcCŊTT3P_)–q͈*LbŽÎ@ۃŗŽ§7Õ0ŧa šœĘĨ2JĸXÖ}Ą ^‰á6‰ íķ=V‚Lžžā;4+=EG:hCS,ū%ąyŨh*30ŲP=@qŪÚãę÷ûŽ'ž;đ<Įs9ŪĨ ķSß폖0Mˇ­SHf9Ųīę2^Ņâ% ē"|fwĸĀ5‹‡/W:ūS*Ą1PRÆ÷ÁŨōr¤ãĮˆJ¤›3ĻGĢjąX(˙)P|+8ÃĪļīs§Āķsû}ŽŪ‹´ŒÕÚqPēsģā>‹ ˆ„¤¨šMw"0ĀŦ4!ĩŠē5n(xˇŽŋ&zŒÔ—†9žĀ@y,ŖaŲLF—qëŅS‹ųį'V˜:{=aƨW %q[P7#OĪnĩ Ļā]ĖiĨ-Pë„î‚Û,Ū€ÉĘWOéõĘeá1Ēč}HKŒF~ƒ;2JĸSAɍ{Ö×ãĩį‚ËãĪôø¨ ™øŦīzŠ˙÷ŋ˙ÕkyXm/€íl/ĩaĶ@ˇ˙RjG—ŠEԌû#4\; ™įŽnˆģšįë{čĮҜGe nbˇˇĀ ąšy/,ÄķîĨŸæizH‰˜}īĻŪĐHIīŨĨY6ëq_õzßd˖˙ŲlˈYá6cË‘!"t˜vŧ¨Oiã‡ĀMUßCú×Qr7-FáVKü͐ßđ‹ËˇnŨÅ´:g{ŠQ?ēDލS^U*ÚŋŨņs‹4S>Ō~-pã …ĩc"'ÅTķë0đ¤aīˇč%ņĩڏÛ_ķĀ“€ Į=~¯ÆQģ­zvíņâ ėš°^jiáŅŌŋÁ=1Ū–@ĒUđX‚ĪģqŧdāüáãNŦ×wX#!õ` !¸1aËkŸņc>ØFë™x~ŨsŠDø^ī(Ü6Ėž6]NZ/ĩô8›žyØæÁFjx<&öīˆmq˜ˆ+ĀÕÔΎņnéMaÂ÷†fŲ­ÎieŽWūøû×ķ=ĩÛ{ĖåĨ6ú´qTOđRÛįĐôäDŌĸ%w:ø|f ŧ}Á%ũŠīNĖ3ÍGˇ‹ŗ‹gÄcsú'j ę{ƙÖ0Ú}ūÆ2n=öö‡„ úŦ†ˇãĢö4ƒšO^`1˛˛Įe !ä¤ZÅŨ\ë^­Žk Ã}Mëėx–mÍ˙ĩũ(ŠWõ/)‰û”ˇ¯‰ĮB]mņÁûë§´ßõR+ėy¯ŒaŅãxŽ7Ķi€CÚz:a›6…G¸„Į<°a¸Ģaą<€ģĪuäÛs^_ŗų j4’c üčõĐ/Ę{aŅŖ< q˙”;#nŲæ9ūžÅõ%ÛeleÃ8Ücú.ęGØĢ9mŊŒ{Ŗ^vŋ¸StķŌĪc=.Lûi;.ˆiĮ>ÛKz ‘āxą_īJ@ßÕ}™§īZCŋ† Xû5÷XŊIbäĶ ņ ūüZMĩŌá}Ā|ž8{ü;"s1y3Œm7<ū˜— #ũĻ=^Ûf{~Ĩg4×o™žÉõbŧæø†[˜ĻīÆ;m|@•Íå€-n•ļąFrb$“6pŽôkNŸņc6>nlƒ’SΝsŽņ؆ļi˜‡^›î;Û64Ē߇Éāáō ęįzœ3&âj@Ú׀;ĐŋےõŨ.ēĄüxyh1Ŧsˇĩ[¨ÛĐÍįĨÖėÔSōRƒĩŋ?Ã÷ šĒK Ījāņå%¤ß¯Ī¯/P<›—S…×dx†t\34ˆ¨ wOh'BãĨļåq‚Q@EH,ēž$JĖ-ôoø‰Ôdbž_ËØ5څīĘ/..4¯5¸b%žøu æęŽÆ‚š}'ĩNõąā¸ārŗubߞˆú@7Ļ[DŖ?qŋĢÃ=î†øŦÛ0ú8PĶWˇŖž‡™ ÜāÕD>#WÖéGŽhƒeÛ'ÎĶÎīwĮ<,/õØč‰“ōR#ŠÍ<§R¸Ué¤8IHą)N;'/õœų‘Ÿ<ĀķžDE0ų€õģi,ã¸âŽøN îbØ.¸—`9Ę}}á­!VšŒ§ĸ‹Ã2‚WB¸5Š'i|fŽ:?AVc°SxæÔˇ Š`Gø1ŊŗƒƒŌ‡< ˇW)™ÉĀķČųšŗ0Æü¯l›ōg^ē¯pø€cäŠĨƒŗŗa^jËÃņzĀ´;$h˙ø@†Žę¯_–6_Ŧܝ6ā[1ÜĒ@3ĶæĨÖ2¸•ã)īé$ˇ(’j]ā 6’o į?§ķœ÷oVÆæJG‰\Ρˇ÷;IčÃ柃/ÄŦöŠĒ›O}â2úT÷Ŧų˜ížCp.ÜÉ=mĖÉ_¸õŪ‹:ĢČÎ~r‚mŅŧĶĮˇz¸ãŌ@nšo??á;Ë?ŊŪn6Ŗˇh­>,›3ŧíGL^° ŪU*ėz{ŧŧÔļ_ŗ|Cį‚ Čč;Ι‰yĩ‡!•ŅŊ^RqsÛoŌ™ŨↀļAɞČmė׿ĨÆŨ O,pÂh4‰ũjūcˇŸÅ9nx ąšŠįŧŽÛĩm=/ž|…CI ‘†ųæ íDwlōëIē^]Fî^؍¤ŋ~x§Ĩ¸yâÁ Q5ĘČGȋ:cØüĘ.>¸÷ŽĮ6ĶōĻģ÷y,×jgæ]ĘČ=ߞĶĐkĶå$(Ą.,$)FT;“ŗiÛi2›]_KvČgÆ#.íŋ”uZO\`ꟕ—ÚcōR÷~. ÖU4všŋ[r¯ž–—úŋŨlBŲMį OKxÄŧ˜O6ÍVM>LņÖŠĶÎzŦ—ą žšŗnöš‚úY'ą%Čr`Ĩ€ĪNb @@œWŠķŌûßm8_Œ1ČÉÕņŧqyƒ÷/=š%B.fä)×=qLĨžŠŸ­ôųÕÕE6ôyzĨøę”ä9°Î0˛™|0cĩdž¯u>_ÛÖ6ÆŧWĮų1•zPĘI?/ĩqQc„Ė4ûĮĒũX3ž×––Đģŧ ßĖōÉsr`39`rrČĢt<›yŋņšcŒÁ8ju÷%ĮK-^“1 SŌĒÔ_Ö|ZʧĪįã^ęÕÅû|–"ļä9@ä9@ë΁É*u^TjÕ§4lŽÃ¨ÔÆßLíz99@ä9@ä9`9`ķRËčKĸRKȜ´*uįK 0ķé§û‡—×|räˆFMqƒ`Ȋ¨—Íč译"m()đŧŅŋÔzŽvĄUŌr/s§y@@ŠmŲԝëz‚‚ÜĀŽÄ.YBŊo0ÍWˇŪ¯Ū˛ŽÔÜ҇ôiR2_Ž0éš(—6¨…Įøgŧ‡#3Îŧ÷Ü4ú=(ÎŦĀäAŖįW‘0cjrNˆˆ ÷^äŠ?z[õ5 ÛsÉŊĢoîf^_ ÷ąuš?GŲp_Ņ÷ŋÄ|Ģ0Į`Û2æą œååiQ/ĩÍøņGGxÁ=(ŗ“ČĻ“ˆ=%ĸ’;”FX~T=\^Vŧ5Šūá~…¨~,¨Ōš„Šę\ŅčÖš‡zŲĶ&DŌ[ÛÜj~ŦiŽy<‚ÔĄÄŧ{ĩ{īÖ7&v÷ī×nŋÜåų2ÁˇÂFĖf´Ķ?:/ž9ęáG䂡=KŌc™7fŲFۓ—u[Ķ“1ūG@yâŊųYƒå 9Gū=A/ZĒôŠoÚ˛<ÆúeüįÃpö~ŋ’Ëŋ?¯äēžįē˜ĀU{wĸę‘ûŗÄĶũĢÂ"úËd…e\sųÛ5JVUpVąԆfˇsv4ķvŽ]ȘëĀíéÆûˇß™ļėõ°Vŗō{Īš×/šĖīĶ=ŽŪ ϟ¯ Ũé<‡¸Ŧ2sC2á#ŲvēRé"<9& ;īËq¤Ģĸú܉ÜĻpŪ §Æ86 ú4ŠeīĶwL+åā˛(!uŖŪČŽä7SܗVõĄËûöPĢĨܸjūežĩÎá˛íŊčPâčŽ"ņų)åÕ§eÂp›øC–ä~ęĘČ~¤éūWŋZpįzŸÁ˜ģ:‚—P{aĩŒ#û27iÉyîōZЋė]õz.6铍^ņŗ—=įģ÷$42n°/¤{™bü’Ŗëâ*ĀN†GÁ…큚E{ WLĒĮĢ“ßO` pˇvîõX:g¨skÜãoš$0ŦņšÎõŧk ]^ô¸­Õåm˙Üåũ;†2@ī!aËcß~Č+]–šĄ)úš^ēßā˛ÛßO+3\¯ŨŠę5å}Ûį]_î§ËÁ23Đ œ÷NÃ' ŒI;÷y\ äßq5ėa}xmzm–ÛV3ŊŋŠē!Ëî] C&™[›÷ö2™–;šŌ/Tøˇį+ėVÚfö˛Šåöûˇ\ŋOņ>—2ũ‹ģ<|×j1´:‘˙•Õb}v„C jxû˙ûĨŠ}č<ũĻŖ0Zā(bū/ÎųŌŗ&s‡,ū­kØ­ųöz{[ŌzŋG–:˙jŲkŗÛūå.ûŦvšmáĖĨŋ?˙“ļ‘9]ÉsîåąxÉ쇸q](˙ĮË[FGŋZ­æ''Õ×חûįNįc0CGÕÖčŅëļcË^”lÖO[žĩ˙|äVĒÎoûvnÉôÂņ(ëæŪĒø´if<­WHĘšĸl˙=ÔŋũŊØØÚ‹ŗũ€6äžĩčY˧°gôhũc4P™.éŽI{ÅÚįI+nÃūÉÕ­ÚíõM;ĐģÅÖR|ļžčëŽūûaLĻÛūíz ôˆVŗÜīûmKčmXvŸēĮ—˙1j҈ŲīÃŒ›õIOŲčÛšôŲĪÚšŦÍkøĖQ,/Úfˇŧ´_TÛ<ēŦkĖÜJ¯A˙Ũ×Äe7 —wū•ĢË#suũ¸Ví˛Qã*]ã/‹Æ\öŖöYeŧˇaē­Ū\&-{W„WÆčĘʛ[€^kžVí-ûåŊ ĮFØŪŨĖ+ođô¯ž!ÚĒa<-MĶĒŊķbžQĮÎŨ¤k3ė ^îõ2}yøÎJË BËŗąiŗQŊķhīrĻŪģ5,›;žęĸčcŅpr8éßÖąŅFē>o-Ŗo˙ŦV=uŲŅŗ­(*ÛN[?ģ=öŠS‚Œö;範Ųū]ŨīSœūÅGlČ[G3ŌČÛįŋģŦuę4{YËym˜gŲī1Ŋöģ}č°ĪõŋqīOGÛc[8ės]^ø‘´}àįŅSŦcYöĩjÁ{홹^čãjíË^Ö6(JķŒU,[æ9×ãüV?rũÎΎqNNáđĪŊúõî1åZđî-ÃØlÍ$îP?ÆėüQ¸KÅây­ööûĄ˜MdŌöæmîTc†Ŗxëũ/eģŒ(PãfUP\õČ]Ö9 zƒWF÷Ĩåí~M¯Œ;Ŗ§Fhyu[­ŋúvëá×xÊĨ¨Āk¤ŋ‡ēņŗaa_ôiŖéjÍ&^ôō­:Ų åŪ-xâ—Ük<ײz—ũŊŒ-ë¯#swÛņe]ãĩ؜ŋËŪ¯æ ō¨ĮCŽhÜõĄGĒ'Ķ‘\fφąz\"øIĖ-J~l=ât4õ+ōęũĐ=†-›sī•_dYųaųYnŋū`™āzë qŪ•ģeĻ-t¸ߋ[g\Ë˙ęÃqq?§úĢžwŊ,M”0my˛Sßß ēĩ,ę\ī'îōlˆš YŨÚ]–ûĖøļÃĢŪčžvēė]*—ŊkĮ˞d‚¸á˛?ņ5r˙ZŪ äôËOŧé:ˇa˙:ÕûíX;=‰AÉ~>"Ž>īęķ΅žŖĐëŅŊĻF¯¯I×Ú$>Ë52›įúëŦšĮ(sgö?Ã{ŖĻwr3÷îløSb2Ķ#ę|ȡÕ,[ī‡iíPWžŧėxEpކeĻ-ĪnŗŒæÜ÷5Ã5zŌĨxũ—§ö8N_ã/ێÍ@éurvŲÃÖy{ā÷G ēԞee= ŨväˆÜ>wlŲ-o÷;Ōۓú=FžëÃúvtžĮč—}“m•ÛÂiËÚZ/Ē^ę˛ōĶs}ø˜x¨ÚsĒ‹̏Xāŧ)ã_ŗZŋ ˙qy{§p~Úĩ0ŧjĖtčĶ§ģF­‘Ëf˛Ųė­Ģ[ŒįūõÕ{~~VžR=ą°(8ë0wŽLm˙ a~áÎųõöLŒŽ÷NŅßŨȃÆxÍë°Æŋ1:Č*īũ‹ķNÍ;ÁŖĪNžģÚÜqĻ,OzŪŌŽ_Qu—˙įUsĪ{8R Æ"Æe˙|̓ÉĒÎŅ4.ũŨz'(õOī(ãŧ§|VzÖĘ×mieeîŨėė\˙ÃÄôŋ?~ôû}(ĶĮ'§˜Ģvöö6?Ž›ėį žŋ‘Íi‘9ũÔ\Õ‘ĒēXãĮ ķĢ?û$ûú[“‚Ãé–ŌÔpN™ĻÛ˙zģĶķ×˙YŒŖ€ 0Œ€ŨNŪ1ėķŠ ˇíVą>Ąn큞ÍK”§iŌ/kfötŌ\€6§Û´ëü[ō'’õŪNøĪ>HEv{ćĖ›dWiî̜ĮQú§OŸūũ÷ßQ4ŪĮĶmoo?žŽqŠãŨĪ[ß'ŸnÂÍ˙œüøīčøûááP’ÕÕĩšNgöŨė˙NNN Oíīīo~üØë5ŖJ—ú3*ļ—’^7âŧĘr¸¤× Û:VN?Z9]m$mãŅELÖŪ¨gøĨ“ ?<´ffZō”u•ĸhå.ŦŠ\#í!ĶTĘÚLĀGŖZ.u¨{9͊ ›Ö’ËĒ QÅwFR)€–ĩáąÖĩ,ë‘5ˇžÎú뙅 }XI˙!ũœĶ_*ë—3uŸĮ˛xdÂwƒ>øš+úZtepˆhąBg Â¤ļ ÖōD¤ ėōœą”Ôz›?ƒžŽŲRgÎTĪU’Č˙8.C}f —ĸT<%Œ­h{fFåX˜TKøēCå-MĀČkđr‡‹į1uŸžyØcķOģŊTDũ'´Fî'Œ¯°T|[SOŠšŠįäēæûq¸Ē+r;¤ ûR9_­&W×ÄiÕrAŸknĒü—uÄņ°9ŸĪĄ.˙ēôĨŌCĶ›ėÕ ļÜĸlĻ5ĀXĻ֑ s‡øĀčędŪöŦ™ĮÁĘí™ļä/9ķ­<šĄVŽÛmHŗ8ÛÚŪú||ü â‘|ĨRÔxėņpØyķ:Æ\ģu||ŧŊŊÕë‹0 ē›K=$kzûxîæ‘`žkˇOv?mî|šŋŊՉÚ:;ęQĩq1 –&%ũ'DdĖõ“'ÖķķÛģŸN˙ëöŲöŗŗŨíĩÍÍššÎ_ŠOŸėŲßX˙Øí‡ž×•“Î !ÃøÂ3b•ņÃtqcˇ ™hU‰üâeîbéQ`V†Ãۘ2~s.GˆŸé}ØębĖ 1\zČhčņŗ Bŗ4.eļÃ[ĸpŽG8Jõų SĮcĪ mhīčŧŸŒQÕŪRÕxr§Å-Ą*&ãR‰ĩõj5ŋ o‹áĐē\YAnX‡Üˇ(Î×[1…V°&Q‹ĨF›‹ņoÖ§Ĩ™lؕÎüãķūžũ›Å<ŽJ_Wķ†9‡ÎŒbúk3ŪĀWY_¯Ø>ü(&I3šäņ`Ĩų\zŠŗ‚$%fLf4iĢēoS­:ͧ™ƒä&RH%q5Û"ƒŪ¤Į×Ãũ=ętß{@pp/m>”äĄ=……‚äD­Z§dŗZ^Ÿ–qęlNĒėOĸõKfÃܚëЅôŪÖeėđDõæ!úˇ˛”RI-|ŦËGÕĻČiÆ!ĪPŽäŸS„¤įmÍ\šĄ‡hؙū]ŪJĶ|L‡ÖŠ‚šĖÂ!F[úlKŽē&֖L>‡ŲĮĪāáōl[ĐqŨ,lģ‚˙ķŠČWdö˛ž]HãąÛlˆø<9sQK ûTQļXãØ 2G%ŋɟr8h{JëlžŌöå~šwAoÖíĻņ¨Ok<ßfOæįb2Æb<•JČ C?Ö04æbX¤™iÛĻU›>­ņå0ķ ņÖRjē*EMģm<ŠUļš‚Žô˛xČ‡Ų.ŪHĸ5 aÁúB¤:*ŽoEY§DˆE’˜tÕ÷3~9l_ņÛÜSJc,ˇėí@KTú؄ÖēÜô+kk‡‡į÷Ŋž¨Ô¨ÉæÖĮžÃzĨ9BD؇]‰fLáw”*ãUBQƕž˛˛ņų0—´}ôi_ŽC.Æ×?æˇČ#ļQÂŊÅˡŗZēvvVĸ|Ė(),Ŗ[i™DiUņÔ¯->`ų”’Įu-ôi[Mē•bšĄ͘5{/ŗDĢ :Ĩ Ô`úøķa] „Đņ`xžŅÍrR9H܎2:JĐZōģöų2„ŗËúœĨ‘—!VBɨɑŦá'ĒIŽ?ŒWļ1ā¯a;™™ŖFĀkũzFš¨˛ ō!Ü -IāvÕ& 3Ë}ú.ˇ“kŤ•iâîAá­Ĩܧ}=ŖVęâäRä‰'&­mTlO“FŪcÅ ąÖA¨ÃzpŨĶøV3{ ÆŦQ˙fœ§uđˆf1õÖāžIüp/ŗ‰JiŸf€ųO;F?ļúkEÍö:†EnÄøĐRļWGPü–:72Írˤ(ĶKáío kŗ­ÜɅ Ž:ŽŅÄv*JíÍŅĘĶ-OÃ…]ūBÛ°Z”ņ9$÷$Ÿ0Ū‡CŸãÉâAæČÃ`ŽČĪšpāĮ~Ęą|Löš¤õ#(7šŦ¨Ęš5ĖĄRåü4eĒ3T§Q0ŲR5_geÅų(7öãčã܍‘įq [žÂ6ƒĐĒ™L`8Ô4ücÃđ•q(—ÂR(ƒ1Ū…y¤ƒßæ um+ŧ”ZûËë\Xs&’ŸŠU[|&歒G–ž–Fû!ŒÍŒß¸Ķ(ž9¸';ĀąüI9ãg gTÍĨšaŒĢ\Eķė´LDLäMDgÂܔÍ&"pÃx´0e¸­Áö12×ø|4–Bŧu%wI+”čÖ?˜īvģ˙[ZZÚŪŨ]]ûčÆ1ãĮO]ؘMžös'{*vĘa]ĸ÷[ÅpN+åĮŦtøåj[ļČ˅H#˙sŠ´ĢLCR×iɘ ēnÜÍa6>ė3ŽØ’/¤wCĢEođûāÔb5&6ƒķŊ|I~ĘÂÆBNįp ĶfŒ¤9t92§vQ9GŧaÜëu˛#J0Ôđ,ë¯KŲŦfÚŠÄZ˜ž˜ ×W*háCĒMĢ’‘~;ņ    z~ô,ocžĐ‘žælpąËĖKZÅ47ŽŗRrõt-,ņI sn šN\ LuŨĸE‡×PeāQôE~æp•ęr¯ G č“V,3rGŨ(÷ņ‘7ŊiÉA"GžąR´V2æAcšĢaü ‹ۏVknnVë3'ĪYÔ/9ŠJ­t…\un2œÂ$AÓMÉb2[‚˜žIKiü9Î !Ø8Ī\>#„f‰Ë´2š0ܞdĖŪáëŽ_ŠĩËzÄúĸn­XŽgoÚĘš.Žč¤R*ˇIúŦÆ>ŦŗįĨrؔˆúé.ΘaáHaūEļ6yVMĒy|ĘÍŧ>˙ĄáĐ^“\S† ÛLĘ9_ŒüFˇ$w?ŋÁ¸Îŧ‰öĖÉl?¨ĐöŖ6,t^é´7ˇ>˙ #ąĒ‚éæfÛߎwˇwBžäpIyvrruy=„æ+++kk1=û48ŨŨÚØŪŊŋŊĸ?.FL˛‹œ×}?Ķs$åņM÷&šĪJgvkyūøĒ{Š&)xË?ŅúfŲúio῎wvŽ/.¯ÄTZh>7Ķžüqvttđ^ÃäC˛ ķõŊV*+Eū6aYĸxŽ^˜Á5ˇŒ1°ę(sÍĸŽ-ŧõé˜[!˜ĮļĻqœ¸=ö‡īÎĩą'â6ķ­Oú,Ŧrĸ ʔ”w˛Ŧˇ¸7d;8å°%}Ú,=LņŅŽ3ŒYGN3^ŸZŽbcƒ} v¸û–Z”ÉŦ˜’ë3ʝPJ¤dmķZ‘–ëX5æãęl4ɘˇŌ–Z[t/ˆøÕU ŗėĄë×Ē÷ø§č4§(OĶÆ S”>1}:k¯ĩ=4€ÍpÉ 2īŌäôi#;çgÆš~,ö]\…‡>eMÂz)ô—Í^qËõ]NŸŽŧa…įøÁķFlĩŌ_¨¤uëJ—ú‹îh|ĸiîõŠ}û‡ČĨš4y> Úŗ}ËÔ|ŦĮ‰(žue!D†ķü#—RĢki"Bƒö>$i+ÃøŽˆÎlX‰iéR yzÜ1æĀY:´R€Oü ĄŽmÉúKj%$~  €acsq6=æâŠ'Hõ‚4Ķ Ą9 CŸ>9ųvzōMž§˙–×——(&Ė5Ôŕ™ /ņ‰Ũ>•ĸz`Â$*Øk„:֘‡ôéX4ięĶą\Ņ­%=TK/¨Ė_Ëõ@”Ol@ÂÖÕü ˙Íf31Iaڕ„mV(ÅksLyˆM Eą2|løÂ(Í2•y›Ĩ!Ž⑛/EsŒosß2#Ŗ6€Íā,žé‚}ë]ü‹ puΕ[Nrš‘‡DĨČĮhéä§øDE#i˙ąĪ`‹ŖlāŒ‘”öVį`ėĮgë^K”#Zú„)‘ØhęĖ˙é?ƒžXpöQrÉ=•; ë0}HnÚļéßōe#A+Pí’}8Ú- …iĩœŲ.ĢĻĨ6öfΜãa˙Ôiö=ZÕ,SÕ>UŠ! 4Â‚xyB•´ĀåɰØpĪ]KĐöæû…:ĸVNbMĨ ļ?HJÖŽĸžÖxY—&ŸKwmgY¤ĒŧAXōŌž“'ęŦafēÖjĨŸą îi„¯ō}”kĮ…pN1įC5ĢĄpŸô žÂ¸ÆW÷Bgt(/˙ũãSzJßĘ"€ļéŲR.unyĘĮšo؃ÅūŌԁ*ŠÃˤš†m>L=Đ)1›KÂY;#¨vŠa;v{”q˛ÉÄđÅ(mt•ĨĩBąü6 Āņ.cíC‘ '2ļb&įPãÍ^Ѕ™>|ÅFĢ„á|o\…Ųö\ú¨,…¯Œx.^Ѝ‹Ö҇C>.}ļō57 ŠyŽ–žÍ¨aT•ļÛOéæÃĨˆē„…˛ļ;ĘK/ä}Î*:>É wŗ7IíJœĸ.ž3𕍒¤zOÛ8$ĖDžgŦ\.šOež÷_ŲX#Č!ō#ĪGDĻrœÆœ $ņ¨D†„<ĢU†BYžtĢalE¨•Ģš8;lE5Mbŋdí%_ąíž4ÆĩÅŦĨ™Ōd‚…3ęĻĻĨ3MélŦĩ,ŖÖ!fLÅ3“]ĒV­ąĢlŠE8QD‰ŧE“1n…ˇæ[1.–xόa”Õ§ GŨúšÆr`˜œS˙SšhnYū°œî‹6 Ī~˙NĻŖŪĖ–= ß÷î¤: ßk|x"fyŽŊšų™B“Ļ>Í?ŋü@ō‘œņ•æÖĮšIĶ5O”âËÅ,E\ɟdŖĀųßÂÂŌÕ͕L4æ˛U rš¸¸0Ku“w퐄V8ŦŌ =7잞 œ?jĪÁÎ,žą%^&ėĶĄ>PʘQrf|xK6Đļ[+ƒģ˛|{= įŠŗJÎĄß# TĩLV”ûČhn<”sļNfÚpV–›ŅGe  ŨĸϏ"`K|KĖØo~é7á‚ŲŠΘL{lŪjPÉ憀cæ n×GæXĮŠéÖ´"žĨ0Z:bļggŅząũhÃôc¯fÚĐĒ3\š ĸ÷kíRå--š”Â߆4F%Wë !(õJņšƒ‹ˇ­1…Éį*u„–͇A‡t­Ü/ežÜ0î‚~Θz*ąŊ&Q áõĪúÅõN)'J.×QCūpmēuÔ #oäėŦTæÔ­šr™Ĩ˛™4ÚôS~æšÎ×Ņž´ÜĒÚ­ĮΌßĒægR·ŖåÃH4 BnGJS_ ģÉÚâF•;žoˆUįgR‰e }z4!čoÁČō,ÖĪߎ¨LWĖ­#ųGŌfgÄãĮįíí^)Īo{ûÔhŠæúūeüÆæįŊũ=ÎŅq<ž››=Ûü°šˇßģŧ`Ä~d8FŅ~¯#ŋtī/{ƒ¨I39õéyŨ _e(pčid Ī.,íîí_^ŨBķ~ÕVæÛ‹‹Ë˙›_Xøuss}čøūüülcuevv–ŗÅßŨ]˙Įå%Üō•:=œœ‹ąbpHTŅ7éSĮøNoލļS\˛ē:ú–ØnÜfĶĘbz–K# ‡š8ëTƒŲŖŽĖ„ö vN‘G*"߯VĐÖKđ åYĄL—Âtgœß#˜ŸF̐„\›NŠFölÔ­ŠôRy–ŸéâŌ;ōgEßQÄ*Û:Đ(‹ŖbNš¯z<íŋ‹zdÕj'¯=GM+ë Ëŗ–O´”0&ŲR1*=Æęœé FĨlåtY!ĄéŠ%žĖ–JŅ;Kiâ,ŽL„gsē¯dãbhÍB˙AæAÔÆ‡+hÛŲū`6?iÍŖ ąäV |žÜ*Š;˙šCwe5YœDĶÔŌ 6ģ[?æÖ0Ļ%‡Žésūgüõŋl]”ĩKG}Ø+Īøąv0ŗ(͆NUYԆ9 tÄŦyĻOGéĪąMđŊė'Č?YĮ°ĨnĪˉ&Oa­ prūŌŨVcĢbZZœ"ĐĐxģÄū>Ū…9χ˙ØŖüųđcßß×åãâŊ¤œFø‘5Fĩ0Ģ]T– ÄP^,FŦÚ4Ũē>ĒšÜ”č&KÃ$” ģ(iƒ,•”šR‚]ąÚ‰’°ŦgGilˇqëģøķō'kŖéņ~)dSN˜ŖˆĘ|ČhÖŌ.ÎÃÅ2ķ Ų\OųĩJxĄËŋČô ‹÷iĒÃaMĸs™Ö¯fåâ]õSƋ•X¯ō(ũ$ŽäŲ˛]ĩŅŧF3Ļ-õˇŖow@gƒí;Šj…SvŦŠÉdiJahĀGßwwwē–§ųEƒŊ=Ēļ[[ģ[{;*÷äm]<ßÎÎĩΎ6Öwžô^¨MЧ›$]ÍY˙“n˙ęŠū+‹Ō—įZ›ķŗYë2Ī›™Čá‡Âī–V>īļÔød\\^(õ]V)2äÅųŲÖúÚįΟą“š=hfâ vffæŋ˙ūûųķįû÷īŽĪ66×3”šģ=ęŦ|Ö¸~ōëdĩS”×VËLĮŠķkļ.Č>mœÄEÃčģquu´øaoõøæÛj'[uõ.>/o\ė_^m-Äؚš…å ęĮįõ÷ųÕŊŊ‹_ÛK™šāøRRÛ'ÁœV͌9!SkטliI}ˇøŒč`؈ĮŦŪĻ?Ųq›ČWēį‚î’TĒũgÛ-ū¤ĩ™ÅēŸų‰zCÔãŗ1Ö*qœGvŽē¯Žü<9õĢĖETœŧˆ]QĨU˛ “&QŗĪÉV“ץjÚŌ=Ļk€Ø_J%ŲÉɴ̍¸Fũ5k{D‚#v›­gfĀ4e n+LroY3ēVt+Ÿđ-‰›åSĐļ+uw.Œ3Í~ÜāŲV/EÔÖÆ]Xé9$ÃPІķ;:ūˆ:í3Ô$hÃĄC¤ršõpjBįF1ą[ũ6ãÉlŨHžÍ3ˆ'đŒ?ĪWFLyŽ\v÷DĢ6}ZHhüК˛”<”~ņhGĢS¨uÖCM1†ųAÔj‚Xk˜(5į0;Õ.ūõ"–l)\ōHGčÔ x†˙h%_U…­€ØČjM1¯ÃY>~$`ĸŧœ÷éËáēŠž5˙ÖÉv/įsÂ%Đ*Ŗa™&Ã)ĩÃl-×Ŧ#|ĒB|ē0Sņ—… ēuŪ¯­(ŋŒ%įôéB[‚^eZŧTę¤Ĩ܊÷ÔÚ×eŦÚÆ¯/÷1?ÖŪ&­Iá-áũ%Ēąj=Íe~ŠģŪ¨˙ˇl’Đ\¯ĪĪānũ;ÎŌÚY2ęcķsŗßOŽwļ?_w{âbåâä÷  ųÖöŪÚÖVÔŦJņ9f]œ÷ãÃōæÁŅí1Ôq›Ũ">5gMĨúŪæųÎgį/[sĮZ4K_iŨ¯öģ’^aQĸƒ™†&;‡AC šąŗÎÚÚîūūi÷ŪŖÔĢķíŋ—åxĸōĨŗú‡ŠRē\…ĸgÁ(Į}Z­I œÉŠúôũųÖĘįũË˙={ŋ6Ož_Gš@m2č”ņE …ĪŪpŨŌøô%ûBąį ē#ŋĸņ­h“į›ß¯Õ–œS÷ęø\Â:/ŌĨĄų…Õ˛øm’– ‹…õ›ŪÍÖÂw›ožŨ…ܐƒĖ‘ĖßjË:ģú07ĩRCŸPҁYafXŲ’Ę~1ŪĘöIb2Œ$|ō˙Ž6Ų9ŋԚ5žs4Q N5yeëÄî)_į"UP 4‡ž´öŲ`ąâ–ÂÁîVøFßã^†Ņ^DHĢiŨĢß;oyk&ô+U+aô‡„%FĶk>!Fū{âĸ nōRVŽūd2ß;™UŽæĻ< öģRl Æ`kŦ!1đ}&,ŖÃöËlĩæ“VŨâvXR2KĪˌ…Ą„&°ĶR•îŅSHß×.Õ°ņ‰ŧ5[yW˙ØGĻáIzŽĐę'ĪnnWĻPSsãˇÂÛ /ZnlQā[ĨŧR2Ŗ°rĩˇŖŊžš˙Ķŧ]cŧĒãEméXēZADîâč( •š‘ÅQÆxJ†0îT“ĻÄ:ûЉ(ŲDŸĨgŊhJcR+ØD‰.k™*6ŖT­Ō§‹c<ˆ'“3ŦƒhØ(ONtԃĪúŖÎēAų TÚeOWÕŲļĸÖG˛Ņĩ„įjĄŠ|Ā—úĢ ķež5aK3*ȡ|é:aÄ<ķ…U>MËžuųd§Tm΁ųË Ē2Ŋwßf„píĩúĒÉT í{DŒvÉ'&ĒníÚk}{ÍuPžŖŒo9ŗÖ§…°Ī?ŖFčã Ö!ķĄkŊ”€Ô˛,[ŋ‘ģŠs™eÄ;ŧÅ4ÔBĢ1Ԝüd<ĨY¨Oßq‹cXÅt„'û1!{ĨÅø°Ģ9߲-ĩáŒÎ™$Ņ‘™ŅßÂÚëųRØĩĸÔÃŦ?enŗœŲFß^Ö$PÅĩ]”ÜP°o}L1Kß6P•ÜÖ6æ{Ÿ2_ž}čĶŲ Bž)Ė?éˆŲõĩ…ÉQÚûyžŌŽ!=Ī/NŽĪŽ O“׌ÎüĘÍ8?y&*čįwEeG¤ŽÔV5'áĸ0T¤ūz„ÁŦü‰ũXM$ŊųĩĄ•Įhī$ ­Ö)Š}Ęĸf‹)=ä,0r6ßkof#7,}+ØiļũϊĨ '%3’ôÂiÖF“b[,…(Įš´1ÅžVš{ŸzŒ‰-ō­ qRHÖīŌjŪ˙ĸÄPZq+Ũ´KčŨ™œėHˆ=™Z.ĮÛh2GĢ#Eę§Žt)įL„*bWˆ:‰īĄ'+ڋ¨2fu:tVgöN˜×´å‘†Ę\œSŦ]ØÆ{˜‹‡ô ŠŸÁļÍ7pe†péĖŪÆĪzģ˛XX§y}NŠÄĶҁ?Ŧ/l$Ę(& ‹žcãÎæŽ€:ą *aBŦlŽŲÜâ9ą„FĮi?zG ö Ŗy}Xķ´ôYØĘâ¸ČÉĻ1zú” `ŸfÁF!kģûÖĘeé|æü„°nú¤2^r99U,ú‰éU jæ$7ŪM ’>ŌꨆV"­XˇHO“ŠAļdã7蠇‡{G‡{ûû{­%,øą0‡ļŨÉ ŗkug5öÕõTūŒôĸŧ(mŧxˇpŲîÄüwvTƒø‹Yņ G~°Rl^°ąĖŌ írw ˜wjö…ĄÔYf})ųÜŨŨˆĐ§ujkÌÌĄ&‘MŠK›'ëŸ˙Ųŧ†)znæ*ŽŌëöhæĮˇ­ÁõagnĻ3ķŋÎÖEá•ŨVk÷Ÿš™Íķģvëî|ķx5‡ˇ›į]”rą5ŗņũhŸüŨ™;ž´nŽø[>Öē&ÍĻí˙{˛ūõđüNã[ŨŸ_×Nūũb8x¸:ųÎ-Ęķ¨Ģēl÷ûÜßߎN$CÄánĩzį[sģ?{Ũ›+›g­ķūž;ē”{u,i:ø|yņøVôŋąs99>^D#PiĨéĶVYÃéXw%횴’ĐčI¤P>ā:/ĶĒ3MŌ›^åŌ„žĸ>*ōÎ(Æ!ík­†ė5é$Ķ&5˜õĩÕÖĐ֜ēĩĨ ée¸Åo…kc,=ŋ“ņĀ2ĩęÖk…pļĒVŧ“­™1ˇScÜøM[͆…ve44ī{ēŽÅ[ü¯”Rv€=ŖŽŖ9”teëŖYßĶMō$%-įĖĮ…§0JĪQ[ŋ2ÉęZašŊŨšMŪ´jã1pšFqŨėw bÜÕō[-=˛ŗ§™=UVŽČAĮ ZH+ÖYĩŸĐŗÂcNĘKéüų´=5Ī]!lĨRŪklÔS3Š‘Ė晎ÄĢž ´%JMD_iVD} ķžI'Įō;n´ÆžŌšÅõl(—ôŅvËĖ'WĖJX•)ÚLĢ6#õĪpA´Yö@ĖW [Âæ´Æq6Ö¤ŧˇžĘ­!ívû•¯HCĮiĨ03*¤ņÔ.PŪ§ˇJ¸<Ã:$æiĸ\äø? ëú$öfušēo ņÃëHæ8ĮīäwKH8'9ÃōÎËãdã'AĖîÕcOąŖ*䀗 NÂ89CĘdHŗđ‘›5tĪÍÍųRÉa‘Ĩœĩ§Ä2F=ũã)_ęSâå94ú`‡™oīė-ˇîˇˇ÷ø'âa ‚4L)-9Qgi)ĒŌ[K|Db"JMË@ÅA´Ĩ˙[Xzsˇ}cÂøŋ8=ÛÚ\ÛØØ@ĸĨ%‘íŗŗ××ļw~$~ØßŋæKšãØRŒŒD>kˇúį›s[įíŊ‹˜"C?ŋØėœlöŽ×Ū‰&}°psĐÚ[<Ų¸:† Šę=í–hÕ­˙îw–Û­ĢŖ˙ũĶēémÃîēuįtŗw´pĩŊøálíôęd­w4ķa_íũčmˇžÍ}˜š„yĘĩTíÚ?Žæ–O֑Ęøßg›7[7‹HųqörwųĶ_Ÿ—U“î|¸<š<^ë}›ÛøÚÚųqŋŊ|{ÜYš>š9xßēÜ]<Û›lhÕ˙œo]ŦÎÎô.ļ?¯ Ÿyw}ÜŲē>ųq°Ú?éčįŊų°â cŧÅš<Ķã(åˆ ˇ‰ęB Ûį%eÂĪß9”.)ŊÃ9B–§B~ķ)đGXķIδĨ&ã‡Ád:“Îß:dÕۈŧĢĢdKĘ"ßÛHp †$ņ#Ķõ×$ŅųyF“ļ ˛š°…ú„VHÁVHXƒJ+ũ}“Fí@1%ŗä–­Yëē›Ÿ#Ą­ŠÖāŖ•ŦžM¨›jLöŒuĐÜ‘m`5;Ŧ֖:ͧÅN:ž‰ĖÆ öļâddeGæeEÖû9N üėö‚¤)Šgg\š¨- |¤AœwMOU‰ĖŽ&+YB•ŗĄyĖCz1ĶËT”WlŠ'ƒŨŧ†åģvŊäo:w´ŸFYyTnäēR¤DíĮ0×ęĖĪ&b ocĢE<ˇįÄī‡iŪv1ŒĮĀ‚Ží"ˇD™đ”s‹<_s]j%ú°ˇy`—”y¨•›UrƒcM{VW åŅc|úááĒēël-Íđ×ĸ|ËÚØ.īԈÎĉĀŧ˛°ŗ}Ę+}fdĻ ėoÂ=ö—Č˙¸Š:š(üo–úšœ¨€™Vd`E`Vĸ”kVLwK¨ëpTÆÕžäf ˛!<ʡ˛Ÿ&Loų6fãĢ~qĸŅs՟ ?eæú4ĐÁÆLsß ä1nŊņgÛpCSaqķlŧŊŧÕ^ė´ļˇ>Ģ-ĩ¸jUŨ@ųQÏÛRĪĪ/­Ŧææžv{~ŽũũäûÎöÖe7ä¤÷íõe÷æļF.HvæįVW *éüüėҕÍo°Ĩ>Ž’_¤›ëœE˂¨Q\<ˊŪŦ­ûéV˙bĻsÉËp3#1Ûž„ėčDĸŠ$ŒũĶY]šjlŠŗiĸĐWb%#ļ­Ŋn÷áō–Ķ=x\^dŒu§,\p<ŅFēÜb֚];ų?¸.i­ü¨3NI.lޟÜ Į›ËŨĩÍåNgíčjķdņč&Ēa°×?°^T”瀸ėļĮ…Ę—ĩYØpŋß§>=˙0˜[\oÉĸ đĀādûËŲ§ķÛVīúė|ë}G¨9˛ûũlõß Ņ§!æWOÖÎOQ-é‰/˙m-Ŗ”…•ũÖ9ĸdkV›%P‘rbz×įįëßVįš=ŧôádåüäļ¯Sí—˙¨OSŖåháäĪš\™ĘôBíŸāķNe–r€rˆčĶļ+ôi•Dē×ä3”+ôYucœ|Ĩú„^Q,ŧ¸’Rrihv)‘aDGḠRgËĸ>͡–O–Ōöņž”%Ņmi>aīIĮŧjEĸų‘>F™į$wš3“>Ą&Y}ĸ•%⠞Ũb8%˙Đφöz=ĖĒžŗĩ0Ëë%ŋé‚ÔíiÔķt %UcȈĮp“IÖŊ:ĀC_Û$ÅI23bđ˜tŸv>FÜ ˇų,đa´6Vëë)Ļ)ô5×0äĨ˜ÎŽNãövØsd_ŗØĀų‘÷¨˜õ­2”“C‰äOĪĢAÂøŨöŦ<ķ°gXUō-ã™&<Õō1´Œ7C$ÉĐđŊb,ŌÖ|rÃ$\æ%z×ÎųØļ:PŸŽ%ōB$93 ’Ú]‰ŗ •RMä‰Č„Xî$˜Ž¯Ŧ@j˜ˇ9ͧŨW–ÎKōĄa˘š›¤ĘhČ!aÔĢ [ūZ ëũŠéŨXËŸˇaeâĘuyēž3YjTÍŌ(õŦž„seåj˜Õ?_ķŦ/ ą„‹KΏŽŌ—aMe•"‡“Fađ9Ŧ:ßwÖŠúAĮ>b{c„ĩ—fžįåXŠœÉ TâYX<;č͚ŌTlápU+‚ ^PŊU1“&e=âÎÄÂN,-_‰Ąpg)ÔAâ=§›LС,%P# g´e'?1kŠ%ÍY…GÂYMldeãŒŊúēÎŊÍFTĞĩu‡ŽaŠUĀĒ}8ë…â>0‘i~PjâÁŅä&ŽyNÃ+këĢ›[k[ÛĢ[ÛxæÃˆ>m7Uņ[‚ęTĪ“?Œq~\XZÉōŲÜ á-äĖ0õéōÜj3ËÜŦČDØŅaļĐpknV4Z։?%yĘ\+‡ĀVĖŽÎ öįz{sŊũÎ=ˆ_kß#ŧ‹˜šŪZ{€41=rc2q!g͟eÉ 6ƒ'Ϟ9.^‚=ã Äües!>ę‡%˚íÖė,ĖĶq׆|s{;XYz-ÜEɞųáWö­›ˆ[K€vĪ7Nq‰dge{íėčööâhw}kéôęüÚņeëCgĻ÷†ģHÄ`ö¯ū¯÷đ÷ōėÁ[×:VTŠÕ”AågÖŖ2$ŧüHøå÷ķķ/ĢK ¯P„[ö­Îsš\ĻtÉNŪZ˜ ŗRbŨXC):ÂĒŦÎjŧøÉ"k ĩaņWũƒģ ˛Ë4]í+ņš,Ũˆlįtw‹.Ŧ˜z1+K+4 Éš <'Ķ7ū•J OĖŠDAŋ„Š$,VĒ2¯K%¤ÕČSÍ<í ûÍI_ëˇ`€˜^rĐ,ĨÉņŠŨ%uŗô,‘ßúđŦrĒhV(€ö†'4Ĩžšĩ…Qˇg¤›ÖMjõ9Ą[¨•Ä3Ė>bJö‘“ÁŽ–t`"Ĩ°6Whˆ**iįŒļ¤°ôöé&]Ÿ1^1Ú lļŨž 9ķWúgi4,ĩ’2ŋԜųÔ˛´ß%cááØûŽvˇš´˜Fu\Ĩd ›ö5<%•”,EiKcģŒŅČnŲŗ\!™ō ËV[¯aë_zē∠å*ŸŗxÔ\JGä§ņäCÖ0ëAĶÎuõ%?§OS{ Ú°„m&Rdf6úî^C‰ŅˇĒIKų‚vkÖ*!EôHyB؋2bš‰ŪRLčšŲ,=X[´nĪuf XsĢÔ&Iģ${ЏŦąETã/ԜĸČ­Ž5žtĐŒ&¤A e/K¯ҘEø¸2lešąãĮQ!­ą–ËÚįĪ6¸Veå2}lL1,­ˆí-†æ,-Đŋ:lĩB?FĘZ=I8GÆkĘl>*ׁ%fO[į°ÕŕslaÖZքŋŦŦĐa6}:žrõ tÂ%Ŧ“mFļ<#6iژđ0높¯áĸ›|"í•՝­Ž˛ÃClb8ĶžÕaXVÂ<)$mŅû€m'*œŌjŌ‹(?-ˆã”ĩ˛bYVöd|ˆ"íĩudäsļ"væØáØšąãú(ōļįsv}ą•ižŨráȄ…@ŪūŪøVÉO~ļgÆˇÛ%†ÚšŪ…}–›DĢt™íČT‚:cjzבėßußæņxvđœ“'fŸæa~Ëį;”âōĨ$fnn^pS(­ŌFCĮŗÎÆí‰ĄˇĘt[į‘Í[=¸îÂĩ Đé;`ÖũËËûnJ6~XŦęu0úŗEŦ†åKXNŪ2ÜŊÜm­ĪŖüÁėęŪÁŲÆō?gû+h)K\Úé^ŽŸ_ m_'~exųđ&ԍ•ct‡Kŋæ(âG:ŅĄÚ,Mz˜_?YBŋūī&4rũŖēŗ´žvöi˙âNGøíÉÆųúæĸč*:ãRVZ˜qĨ€ƒį–Ö×Ī>í]ô•›ģĐךÃvEĩpŠH°a(˛pBŦŪkeĩ­VÍ?Ãųdļ•z| ›xs” ö­æiW™ļgz5<ÕḎ š4‰Å§‚ŌQtkÍVCĶö˛ˇYšŦ>’AĢ`ëÜSj;„ū­ßräđYü6ĶŽ"õŒžämœ%Ŗ„3jЉšW5â˛Rĸ§ŒjĪBmÍX{„Oö‚­¸Ē‰11>ęßAķĻū5cjįQ%Í i2Đr‰ßį(™ë}WĪČ\MÂŅÂ$‡K’÷ ĪŦŊņ­ęžļƈ:ˇö,zÍÖlĄ…ļÂDŽū~ˆI>´6’%ņ¸4F‚v oφIKâ¨Cëjŋīc¤×mž7´Lc⍌tZCŸ$9œ[ņ?|ÚWÆŅíKEŦĩ"Âz˙mĮP[͍7:žáEˇž‘•¤ņūCœ>;i ŧPÂÁ0^ãŲ–ú'ęSųļOFË5}Hƒ>Ė|Lûa‰öUömˆáܑĢ[)ƧažĨļ”c"m+ÛU“>€Z)Z6ŅÕŋÎÛRg4ÉZg¤fĮ0ĩUæŦá[ØgĪ|h]ũÚZhĩoį–9˙ĩNÕdG&åíö´.XrkxöįÖß'ŋž­ÁlúįņÜĘW`į—˙÷qū'Œ­ÖēíÃúGFQŨ—7ģīMÎĒÆO ̘–ņą!FÎ@ô`ƒp[Į§ŗĻ0M(ôEHä—öŲꈃ;tjœé…Ú.:b:Ōĸ-Ŗ”›™ØöF´Š ķģ4ȃēô1>î’pÁĩ[.FĖÆßå’Ĩ Â&ãC–lúq@-#Ö3^j䃚2g[ĖÄÚ^Ņžđ49’œT2ē‘üކ5ņY߅ą“õräÔØŋ–ŠŽŦrémÜŲJŒ ^¤CžĢsõ émtf(ZčëŒļŽ´O‹-˛ ãŋĀš4HöØ …~`qč.É';ĮňåÃķyĸ=ĮšY5`ũš–ĶĄĢÅ\öh~eũUÄ!Whæ[”Ú‰qŽõ¸N*ĮÍL~H‰rŠ%ôåi~čC5}Q]Yל‘&äÉ„ą57ë„:s}K.ÕSÅļžeq&9NÆ,:å­3Ļīú.|뀕Č<‹Xc>ƧÉķOäéÕ}§9jr‹=Õ­ĻąÜüčrÎK;?‚H-¯(€‹RÛņyV!åy‘öŒvķ%˛ZJ¨Ä˜Låž~89d—rNÔ]œ˜SŪЉlaîiįĨVm1ÚōD­Z?Õņ[aĖƗq˛Ą6ėBŌ-öa8=Ŧh:1uâeņū„"ÍĪ9•įOx(īyūlĀÛ#%És‹įœ|¸Ff§Īá¤\k°ģŗˇ´ŧđamí]g>rEāgŽbJ’r¸zÔãöÄŊmu…ëFÁÂgk+['§÷ˇˇ|a§%4N¤ˇûĒŗlovØ ú#‡Ë oģŊĶãÃķËžNĘQŅ _jܞ¸ôë×*z !áŗĶīۛ뙎Uuâ!Jv¨Ôkë™âį+­JNVŊ^\>ė@EÍKޞžb8,ÂØČ:7Æ˛DYÖq{VĮ$50î”E•!ëėmųŪ“šÚ‚&a›ˆ ãYŋu§ëë --HUO'•ŗyîtcÅËA“ĒĻ F­ˇ<ûTāRŋûQT˜ËũœÂĻÅšÜâ]ayÍ9îā­=ō°mÕgú1ĩ(ˇ;īîÂ0Ņí4~ áÂP—˛z]ÔĢ ˛F%ē<—æfj2¨ōs`VX˛žssXE|ÔDmH˜äōē{NŊ͏¯ŦßŊčĘql”†ša§r&ļ_YøåķŅf•„"9<ˇ3Vv6ģTû"'Øĸîn|å92ÔMņã€ą!kÆ}:[ŗÁēč#:CK`ûŖZÂfmbŨbš¨jejԉuæĐ×(J´y&5KÄxė…9C÷ƒY-1r‚ŽÜ䎕ō7L×anŌĒ? ;ęĀÖėÔŗ1{ųŠ&D‹ę°yËølõ5û˛T1žŧ˛zJLN$ą­Afáü=M‹Ōør¸F7-­ ÂúAK4ŊĒÎåīk[ŲęČáúļn­R­+{îe'åsĢĀ8­ŠķĪ*čŲĪ';frrāO2ĩŲoĄ"˙šVmō9Ŧņ e_ɡn~´é7đgđį˜ĶqŨčČéĶÔĀĸĖV­ĸ˜ŖĶ’CĸL{V ŋ¤#XDÔĨ8ō3:×ņŪ“ãŊÆ?œoŲ›cĻ(“U&<Ē~;˛fĄW­ŅúųkšŋŸžwoo{=õ=Lōķ7^8Ü^GU—’í)áÎí…LŅą†3>Īa^ŲÜ]ŦŋW5íŠsYœøķŗsķËKëĮwZ•€ŠVšÄ Éáņãę —ŸģA¤CđââŧĶnņ&—áüŅ… ČŨāÃúZÆßdr¯;ØũķšĶ­ûãÕŲØMåqSĶ<ŠįũĘf#•;•8¨Íķš5ą_gƓļbvųÄ>*TĐD1€ âd‚Î+N¸l]S=;”ÃŲWyũ&§fĩ-a]FļĮzŧŽ‚āĘ´´ ‰†TÛ3uu˛°r“”ŽsZ~\Ũ˛(soeąEQxÄyČįP!`‚^<ÃĨĄf™>mR ĻÖۜF:Ė4ך“ūå&gPlNžį{+ˡŅḭ̃žô\QāąœÖnī*úŊĖW.õxh0´ 'ĩ܈(ĩ ĒhËaz­ÎßbBËĸ ­:)ŠOœõMļ›/ĩíx‰4báÂrĘ*Œkĩؔǿn9čD”­8öÃũ”‘‡L(PSą‘ÖĖnž Ę :GŗČiÕĒ3ûü¸cūÅø†ŗ~yŽtFđZE]¸aé~\‡rët\?Oų4šųëŅú4`WĪršEMe¸lqķtq¤Į5r”āÖ_š9TyÕÅȈ. ō6āĶÂoĖ1 2åLáö¨UįĻįLW/é)u0Fí‰L)ˇ‹VĄļŅáXŖÖ0CŠIĸŽķɝ·JLĢ^…D”×–9ŪŽYĄ5áÉ&iFâÛÜÜQ#įčÍÃuk.Z•”9B Ë›¨›˛`Ŋ8?+}ęyėŲ„ÉŊ*KkTũâčËÄ^^dØ(Į ˇ[Ũ;[°n…*uJ ģĢ˟į}8UyŒ¨8ö´úaMf•ŧ>Zha[đéĪ­õ˙ŽNā,Ŗ¸õ¤:…×+ëæ ¯_š#§ÛÆŦ¤ÎЏuéŖ1RĄW mqõŒKũLÆUęĶļ§P´|`;Âr‘aę_¨LŦŲU(åRŒđBŦb3I­ųûđ(ZxN>ZĄēe=ohV•:wŧĻŽ2e˜nęGôØĩoôay-ņČguÂ2|æyŠ–¯UÍxˆZ<ĨŠįpŨēn•5TŸĐ8Ôû8CŒõß,ÃLâŨTĶÅKÅláˇ+mŊ/Ÿg2Z|úš§yÕa|ęQĮ•ôŠŧz0 ZĩáĶÁwaЧíÖw­&u—lūĘiQW“^ wņHųæõ%"XĨĩYĐB´gÜúGõ›“ri Užhę3yjĒáõyjîUßÚ;FÕR7ŗČéŽ?Ô`“*om™C÷]3]3‚ų‚āYĀ€ †s †pSĢÎņ?Õa5T s5ãLÎS*rŦÉ)^y„“KQĻ5vYŸfY:ž Áĩ|bfŅa'&ũ2ëŖßfĘTĻĶ{ä‚Éęæ…IĮ“>ēÎíOj5mŽ`ØÖÉÃÂE%˚RŖ;fôÔ—R9Š’[Ģŗ&ái{\ÂTÉšĻb¸¨˜[_˜0ôia…–ë¯Į vŸĨƒ9ēą9zrí—í#Eh"ËÃŲäŧėEŗPo"š…3}ĖŲ5TY.ė–:íÅeEŠ3ŋÔĄ2Ŗ˛ĮHØĒpœĨęô霜ōų ÕLJIķ2 ~šŪTŲĖ*̤gŧŲx[‹ûĘ…ŅžŅ'," ą—¤ Ė*Ėʆ 1Ļ€g€|Qī¯ÕšƒÆSę2ũøÖ‹ē\ÔuQ.>7ŌĘ:z-Î46NPˇŌ(°OĖŋ’­ĸņȐ^x;iĖKFŪZĘ­ŸālîÉø'ŧõŗß3 [Đ;Š}f^íđWcĶģéģ‘KU;^1ƒUF°Ļp†r”oA?6]D§åp=ňS‰Ē*­CA*'ā´hâÁ&jí*ũí´ež‰”i4Ÿü]Ešy7_I¯ĒģéĒįõŽoĩžTŽ|>ž.”[’Éur¸JfĄN×Ir|“r‡×ĄIYžž…ëšPŸŸãrëķNi]DYįôcīuÄĮp ›˜ŊV{ĐÂvŽm1Ĩ˜ˇ#bÉ;IČĖ˙ âv sú´ų}wœ&šD/īʆíŽÃ&rÖ#$Ēõ˜Ų“&Ô8˙zŒ<Đ*Ŗg#ũĩ‰Ž;­46ã#čŲķ[ÆĐaŒ“jŲ´œiÆAK:he/ä9­B{.éĶÍŧ?ä˙2F>VLūYŊaL”ŗšFB&‡eãĶGc8^˜g;Öyƒ÷ķŗ°ĨU:ė)ŨįŒŠ†„ãWēé¤+æ€æG–’§šAZū:GY‰¯1Ūgˆ÷ßķĪ—eįâĩŦÆ$i{IĨē•Ķŗ,Æû0)ãkb´Ō†Dē!ÂgœqsņFá ˛´˜ ‚æ$ŸŖęd’™ËÍįœ’U¸˛­JÃ鐰ōļi9J:§Oŗ÷Ā#üeü˙ŽÃ?æŋ œĻŖÎ$B”qŨÉ1YĢ÷įę\oåsĐq§„¤rāįĨFäŌĐRí´`˜;YŌ)GíÁáégYz~9[ĶW†sĢüĀ{T8sútčßl08RšTąņbŊc=•àgˌâ0´ėVŠĸŒ&ņˆĩö erĻWÜ™‡Ë{7įMĸħyŗiÆžĘ۔ĻNĮąl–öō­åLĄÉôZ–‘#„zÍę $ԝX#XOūa_‰>­ųT2Š#“qo:´¯“ípÎO…Ž‘ØŪfa•ō"/_ΐs9ŊĢ ļË3ĖPŽn”96seĨ(:ęSöÂڅcÛ=˛RĘõņ1ĘÕ,Ũj5<Ŋö”Jڌz,W{ßĩ"äã4“ąĨv#˜`Ī'9p•¨į­õF]ЧņžÖ‘ÕdčđFU[aĘxTū OŌ“§5?%ÛÍœhĩDiá}Z=T‹‘ŦŊa?YëVęŠ0ĶģBJ†­ē~´QOĖõ™Ä“CXŸr¸Ā?ĩ#%S2Œß|J—sƍ‘ģ c<–Ødx{¸Ŧ`ēr›„mŒg_¯&ãÃN:NNJš gįTL›GŽLŒÚšššX9Ė÷tëOgëoëIM§?Ėēáæ€‘p´iSläåąj÷A60=ū_Ā­=NĖ!îOÛ į0æĐü!ų,=ŠÛ*Ō0Ģ3§›ē6OdÉĢ$–ũ›MažG¤Bú ŦšôáQ.6:4ĐxloްęĘëYZSúũ” Ŋ–ôČáŲ6kƒ}8ŅBK5|¸H!ŗëø3-Ų¯ĒÖ CÖ֕qɊ ‰†hŊžĻnØī“Õ㟞[i\äxéQpÃ+U9wxoZĩHĢžÎI~!ÉeĨéÄĸ$s‡:‡RÛx7* l‹§æŨWĨ/°ž<ü]†–!{ŧHmV*3…CÍsˆŲžꙭĨŠĮ”žĘ€}ĪöŲY=7\Ëŧ]F›lœęWÅô+įķ6?Ī#ô‚ŋŒ;MĢŪ/„—3ÃÃAē?"Ęí¯2Ž ŠIôsi‚ÖÂ9"œŋ×?ôü_0LĻžŗ—(OiÂqņÆPúeĪīAEëHã@å™g;ŦT ˇŠŪxÜŗMšŠlQėÎf?ø}ØÖŸėMęũÁVĄ45A^§›†IĻdÃw.qרœårÖõ>=lßŖ”žœÉˇÕŠš‹7ū´tüǤķ”õ@¯+ú&šŧz´Ø*ËĢt0FŅ0íéYņ*=Đ<@ÄQ1:8Ŏ5ŽŖÖSÎXøŊzü~Š}Ŗ´3ãhņጾLSzR7ĩ­fQ{šAXåĨFũķ’{KDšWVļš÷­YŒoQx[ĘÅaų‰Üü˙c‰ŒeårH^†ęˇ]įËĐP‡¸*Ãŗ…&Ĩ<‰L;€L%ląŋ"ėņef( Ã>Æ:ذ%[ŽiĘ(Q@nČ𯏋a~Åēš˜ú°p`\ßGôėĮöLÃz Ŧōĩü=Ęa`_č/¯qōm<ķ7ņpäøéđ¨9xėÜč“įäá=˞Ōīėëđ”954nė˛ųĻX—jˇŌCŧw]…”āmĒU(ãGîĨŽĖ^ļ‰LûZīpąxĻŅyZj¨7šˆĮ)@ĶŸfÎE”ZęÁ:&ČEf5ŗ§Ú‡¨ĩ´a„) ĢNŸļļx+jWqß&žĨļu¸ }:ĸwŅĒ2œKs4÷ô¯ › {*ĖÁŋ)q¯¨Û9Ŋ™Ŋ![åē)r9T†ŧŦ¨ f ė„IáßBnåãöŲ´¤)*‡Ô#j ęcq,Đī­Ũá%WnВžVCİŨ3č|´ļˇSjØî]Ō… qkÕeŖÎ@BĢËBšz’AyÛnnb#–Ö$´BeČ&ĸ]Ņ6ŽŲ§Ö›nŸŽ~\Տ”õé(n؛!žŖ&kELä[åļTy5tČøœi<ĪĮ9WsķĘYM8äĀÔĖ­¤‰{ĢŠ˜2fÉԟūÃp˜ømVŽĪŌØŲW,9h،˙ÃY‚œVmkKKÉâyQxfnBÆÕ§ŗģG‚'7ÍS뗷Ĩޤ`ÕĸšÖ.%)*ŋáI}š§…+úžžđŦząë•騭%!–‹a‹âĐČ˜Ž†z’i-ĶėŖ;ėuZŽĮröĶlķ¯˙Å6’ŋË8–QŽĸ‘ž;‡wX 'Ĩ$iëÃA˛—hf Õkĩn¤v9\BŨšiŌeŦˆ3„˙eēH=GÕAßNúÍ2ŨÅí7áO×ī}°AvŸ ØĄŸ TžŊ žqM+ cdü§ķ7˜/l?2Æãß`f×ŲE9/F›Gĸ´ĪŠ:!wũ Õí¨mhgS“(ãĘĨ,(31äAŊ,Ũ°>Ū;īúņ[GĨĄ_Uʖ ūĪí”ûžúĶĐÉ3ČCĶQ‚ŦĻfš„ä`á!|× ų–å¤ĩ“ÜÂá•#ē&Ÿ*zŲ:4Ÿ ģs}[ą&4ú°Ŋœ_žW¸0ãṎ́ĩņ”ZřĶĶ3‡ŨV)%Bč°nTíŧžī¨ú°gWƒ,4Ņd¯öi9\D‘CšĒøŅN¸e<Ģ*ŖKĢš ļÔņDJ _ĩbƝ|ÃĶû1ēô.ī—š_NJe]\Žčn ¨ZŲpR‹:ë¸á˛FûˆâH' C'Ã8 øQŅZ–.֖xīkŒr:tØ@Ģ8:ČrS€Š]ŽnaˆetÎiA{~Ŧõ[p4;´ ŋŪxšžvî ŌŲøĐlģ9ĘX?Úk äH)q$™x‰ņ~4,áM'ķČ\n6hļū‰Ÿˆ<āŌ ëðä’>M9c÷˜ĘøĸŽkk3ŗ˛¨ę’ĖŠ$â=:ôômōXŽŗpåY.“‘#ŠA6­šV§ÔžŨLöČú°¤=ü.}: ŧ€å´ęœļôš´í) ÍÜ\QđÔh6NG RĻFGôö6ųqäųĮ4ĘĪJ­´~ zvķæ0 8§ĢųyļB=ĢĘĮ¯1H–P§C?U—­AF։'“ņhnØŠb}ˆ;äôČáö!e_~ ô V.ŪYQyRæČkŌ#kÆKŨ1´íœŽ+Él7#w›uĀ7U*såŠ!jŌŖ…sĨ´ÚķīZËôøQ´ĨÆÕę‚ë4^¨<˔9Í;*ÍŗZĻÖęĐ~URÖ9XŠĮŒËÕŗU53bØlĶõĢr¸ČÁUĻw™^-Č^žGˆÜn7$!xĶaMؚGk?ŌuÔđc=“›éĒōĪÕ͖HŋuŊ0j_ŋ…ô%Ze\Wæˇl[ĨĖ'š‘.‚mëOY“6T~€t÷lŽW‰IÁ„Ŋ-dÃĄD)Ū… Î}XJ”‡Ļy;čˆLg÷F-ĭ܋؜c‡Žē1UŽĪ­ŪũJžއ†úį˙‰âÚÛ\F5§ŗåų5ōĻÎO×*†¯ĄūŦŠûæ%3lo’Ũ´žÃᔿõ7%§‘áķāËM“Ķ GÔ žø­G0]Øđ5‚ĻC÷–ë0…&{Ņ>_—ēx‡ÕVëv>6SĒķ:a“xÜĖŨΉđ…w-w{â]ö7ŧœŸŸ˙¸¸tĄšbø”…ÔÆ)ōƒråīÃ^ĒKķfâM* ׊JÚÃÕ4žUßruŽU‹ŠdŽ:2ËWŠQ˛kVQĢ*Õã˛uąęĶq˙´¸0§Ĩ‡yq׹T"@ %[ŧ……ÚDƒ'*Cí)š¯sÆOYŪøc‹R|ĸCâįĪZÃՕåõõnŸv22|įgJŨ…ēÔ$ŧØúŧw´ŋ[ļ­ņv6)œč3)s‰jáė ˙å§-aĒÃĩé떨uy>ž¤mšÂ/q›~S•ŽÎâĨ_k!g;°wlx$nIíđá ŽĘv{åžö”ņ}÷ʎūm]ŸæāŒ­•ęlO›÷ûđvŒšĪ¤øvtęŽđ…īucŋ"MÉNtˆ5đŖ>ūˇ~<֍ÍōžF_Å}ëĮˇ]G đ„“Ž*oqa… i2¸žö=•æëIÍ×)đŌîūˇƒƒũ^†ē´f[¸=1xüā–*öĖlÕi‰•íĢĻp°NK4™oģĸŽÃ>ž.õ†h/Č|jŸÁ$mxzæÄx6<,Ū§ Ã& Ŗ0âā†V6ˆN_ĨM´PĢÚöēô…KôŅÍ:Xmft˅KŊcéËŪš ‡ûZĪēRr}íkëĶä[‘ë¯RŋÔQ2ŗŽ}íú1§Ė—zĘčYĸpŽ[|9^*§É•Uæēš~ŽũTpéĐ|Üėđ˜ZåÛâ)æGAŠžKëúkØČõē—ž¨ōwģũŋ……ĨĢ›Ģî]&CadM”:­EŌēöđ@”ûs~_ ûzæ°í:Ũ`Tėä1czī‡ã |›{–0-{[F 'aû1W˜CpžFc*jaÂúw(äēāĪáŲĶã„ˇ’ŗÃ)ëöŠfĻhŋk8GÛđķ͍˜JYÉũ1M0Ēîå¯2níįĄI`ÕĶĀųņ›įĄ|ŌhīâOôės/ŗÉxŠkƒûļbŸĄ4N›cĪŖîLƒ?%Īēąãã]˜tĘŦM nÜ8õõc˙ųrK>i°˙PU—Æuy.öũÛHnŧd|´éy•ÔÆiâ÷Ûûߎöq™`Ü%”zļÔŧ§ĮüRi8ŽĄßęú#Zk%ŧų¯õŊŊls|ēŒmįķŠÃTĒ1l˙­ĀGPRĀŧ á8‚ĒŌ4Á=.hĨ¸šÔãÖųÅB†dåĐk‡€V`HŽļņmoĢİŊu †WFs6! ĖaÛÍ1’šØÄū^ģ%ëģI…ëøad\ŧޝž?jĒqÄQé6üŒDiœ6ĮžŊ\jnÍ~¯M߇.rN\Ņ(ãĮ”—åx?ŽũNEÔ# ˆui#'+Ęš5ŪOŽI†Cįå^‘—FŨ{Ša—mĖūđœÕ\v%ũę%Ķ*Œˆ¨pgÉŖÔÁßuDŠ.›ÖģĪrXcsĖïׇãítßí÷ˆ|šáIõędōŠãÃáü×ßMe™\“˙s›ƒR}F–ˇ#Žũ‘˙#ļÔŧœƒļÔaØj~øšoiaa~~~aIž 3ũÖÖą^ÄLë-”ÅrËų#ž|ŋąąÁú nccĪ÷÷÷ûûûĢkkhßÚÚÚŅÁÁāA.qĢk—/ˇ#M†|;AZÅö–ÛéSI“§ĐjbõwČÄ#vuM0’ŤÂ(UŗĮÂ=ÖâĪ­žuxR§lŽũā—ÍP%tŲ§)ÛVZEJķgŲōō ;hBžJQSîm€Vú4Â%D3‡[?Īnn5;|?¤„™ÕúĢi@ĨVcÃ5õrf`|ÜŊ\ÖĐņU+—ö F– ÃądöŖOS°b‚C—ž­Ĩėģ¸*°Ņļē×Q°ĨļųQlŠ—–ŽŽŽnīdŒķ~őlŠĄŅvģŨ!ëļĨĨĨÛëëXŋ'Ž?PÛëëëGm‰X+_ÖŖõ’'ôōoßžÍÍÍ!Í}¯w}{{yyšˇˇW×_ÖSĘ}"­"ÍËu(ĶgR´šT›įS‡>˙qk¯K—ąjžÍaÛuøYÔ<†ø%(Ĩ)ZtD›Ņ2ÚũübšķÛ#UÔĀÃŋë0Ũ1ųųÉåN{ūS\áqčáu¨Ã•kÆKŨxŦ›Ĩĩđounxú6ŲhoĮíŠL–''6Æ_#æú¨N•úĸĀ?´Ĩ.úĨ[jšč7ͧõæ^ũÛņ͐0ī—‚ĮoĶB'ŪÜÜD¸aūŽb>Ā’Ąû.¯Ŧœžž*0rE˛[ÄŅĮ(ˇ×ë‰.ĸrsŽĶYYY>Í| aooo/,,lmoŖHãË*”ąağœžâ:øÕÕU\TÉ6ÆúĮz^\\Ŧ¯¯ :žž.ík]ŅGĄËõ×Ū´>E=Ī/.–—ĐeB+<›p#ÛŲėÍclŧ*Ã`ęqëjœģ ˙C*ŲAZ>aˆÆ)—5ņ8–ũíã9Ž=æQđR|ļíĐĩWÄĄŖ†WcŸšC7ëŌøø|ąlIŲÛ.aœãí:Œļqü˜üÜ8˙Gpå ëZ{å2Æ\Ssßŗ›9ķųÔđOaD×"÷qž{tWÁ‘ãĨb<–Æ — >}9ėĮ¸…+÷Ų´Ųąíĩûf•­ŽÛ‹ËĮ×ÉÆ"&]âĨ:ŪÎÉáąæ¸‰ß ņĒĪxēĘ ĸ[šŲÖŗũ—´\õEųKQj*ÕQĸáËĄaũļ"M1ūđđ$0f(ÖGG‡ķ—Ō̃¤AūČųōŋ˙5Ī×_qôžˇˇˇc”쎺ƒœĄéBëk5æsxtÄFA›?::B=}YĨr3šôē]TęøĖH”z¨˙ĮãĪXčîø]_ßîîîūüųŗ šôEĖŋ\mWVOÔįōōzmmÕ+´ŊaŋŒ[įĻũ^Ο1äaļú—â-įaũØrȧ/ŪėS]ž>ÆįYĪ96Ú#æÃl¯ũęÂN pŠ]ŦbØ.Ę“Y¸*Ÿ(Cd.ĪäI}ØõQ–ŪËĸB˜ŊãĮu î㛄]ŋGt BSŸ4†ú;ĮÅīlWŽŦ&ôoЧ6EžŠ}Áo}ŧãŊÜøuiĒãË|nsĸō˙¨cĮ*?sŖÍčã׏q —äI“øL†TÉÉ&ōMŌÔČX/KGÃ5cđwŽ‘TÖK™ßŸI=g8fM[ÁÁ Ūž(QęũCî+ŸņČÁđu[KĐ߲-uĉŖ„ā ícĻ ĮøQ×%ž,o|yyņūũûöÜÔß!¸ėåŽml@ĄL ŊxíųéiŦķÅų9ŦĢA:7įÚîņ~Ū…ŨČĖ 2„ÚÍųõ'}|ũņ'ôuW°į•V–žÔG‘>Ögg­[_˙ˆ.c=ĮŦôŋu´ )}em,ãUŖbÕuée˜9 IgÕ ĖŠ.žî[NÕCm¯m6÷vœ 0lU[ØŠŒ …g0l•-YKsáĐ"5"…ųôüĘëcrņŽG|/—ÖÃP<õIiĻ=JųÛ ÁcķS““kiëzĒļ_JûqtÔbĖnDÄ4’āˇĻ|UĮŸŒ/ķüđąãŌ—GGŨ^SÅø-áÍuû]Ãeˆ§^5ÆÜ Ÿ.âĐ^~–%pyo°Ä!&ĢkÖĢĪtŽųíã7Ņáę9[ęhQŲÎPę O‚Āaņ$d ”BĀŖ]uOR9{/z!ôoN@Gl˜yíčËB8ææÎH§ĮYMʏ€ģWāŖ0íN Ā*æéų9k–{˙đû , bØžíuáō >Ö_šĮčrrēAÉŪØXģššo}VÕG–˙ŖõŸ›ÁbŦ=33[gŊŽÚŋxIđXYÄŗķXuįŽų4ÁrüˇeúĖIįáG0'Ÿ†ķļÃēfā^9ĖÛ)u˜\.Ūåo4tõaĘ\ŧĪŋümk,~[Ø1pؤ¯NYīqņĄá&ãˇv\7Áe˙.ū”v5ų–Ŋæ÷‚ráR¯U¤/áВÆķR'”ͰļŒ÷á˜Ō†K Ī—ôáLjáØsé ?Ūëöš||ŨدÛīĒH_ [āųqd—“–u{}Mö+öŲ¯˜‹3Kߖû%ŅäwŅ$Ô6ÔEÎHĐĄÔēšĪ$AC<˛6Šę ™›ë@%UũüäDãd×^ČZ)ō}ŨcÃ5õoŨƒõL5`ãÁúCãÄB-bģÂhĪÚîéSG+Ō!Ö_l˛Cũą<ØÛۇŊõÖÖįΟ>Wį!˜ũŖõ‡Îē=pšōbíĖ*pk‡4Č8ܘkcSƕGŰëąXBåđė&x•ß2B}nx3iLŸCŋœBPĪšR˜°&íķ,ĄéĩdČŋklŠAFlÛcÕ­ ×aÛ5˜Ģį´áX8éVN5†øvŧ4>ŸgEšCé@ {:įÂCé“ņF–ėßë_ĪcuųÔĨ)ķgŪŽŌ kâōSϟ>›†‡îYU`Ō~ôÕ|û8&íåO=>]ĤŊü/ÉFĪĨĩōļ4/ŋÜ9eŧš8ĩ÷ÕĐ-ŒMČøp(5âdpÉ?ōēņZ°Ī.ÆĶ4Ŗôææˆoãü›ŽSĄėŌžČŽĮT¸2†qаk kfäĖCаūūũ;`iÖ§QâąZØß …˛ōåÃūQŌxŦ?‹FūŗŗŗÚ=6 &1B¸XnĖŋ˛ūãņVëûŲēo<Œ|”z6íëQķôډGJČįv]|ŲŪzT{DŸŪÛwæ1¤&XTEŨ{+ŲbÖáÖ#(Ŧƒû Ž>å4dã°F‹5/bŪūĩÛˆ;'‡c䍱đŠ<‡ĸ×>}AÛÎdi Nœã˙ršÆoeÕáʕXrŸ.ėQÔĄËuXrŠ üPäsĪ-%(soŽ÷<¯Ö„kųŋf/č‘q˜˜ÃĩzĖ2g÷ŦG5ßz™gX ķ)ĻŠéS/Gŧ+æ ŋ‡3Âü5­yaÔy$Ĩš:ĀÄû.ŒMNN6[>ΖZT }•ŲRgč‘ΰÆráÜēsXmT_ĶÔ¤¯ÃšáŲ9œIךŒņ¨Ã>~„°Nę–Ū–ØÄŖ‹ĨT”K… gKZIJÃß55ĖJ2]%āÜ.ŊKŖwžÆÚf5ĪaÛŽE֒!Øj|ē~'!WîcøkŦų0Ŧ×cŪôˇcvÆ]ĮĩņëŌXúíōõŠ ÛČmL\Ũ uđũŌcŽIoLg\ņ8ŸD(ķšryÆį&~ĖÂÆįn\”ĮK°ÜÎčYÃąÆėˆãŊ“ÎĘQæäņfĮlŖ}úaú@Y&?ŸųĸBĪKWIųŧâ>͏e›;! á—úũÕÕeˇtíAĢ3ÛÚŪŨ;ÚßU[tDŽhSø÷ĐļPŦaOōĮi>YŸâœ Xĩ߃s8Ÿ.9•ÛsO.Ekâũą4QīĖp,.fũ3čĻ’Ļô–eUäcqAWiâb9Ũ>eÔIbúōÛæ1Ŧí?ļ?öYÔÆ+áũˇž^īĶt HĶĒ,wiMŒJ€—‡•yMJi|ú&ášsZ)Éb&™3Ž5˙ēļTäSÁø•jĘ ũ"õŦëģGúą4*Ę}]Į?ŖōØŦüÔ¤~ėԅkƸɄ&˛ĸNÎ8™ãįôĄō-Č4'}z '=áĪ×ŋG?ųãķø3Ąsæ—Zä›Č›…wíŋÅ/5‡ã¤G¤ÁY!§đ´×[bãÏV †‹‹‹‰ūA{˜æqʼnz>7ÎĪøŸœĪ9ÃÂ9;ėLŗ o3œ&|Ë—ĩs7Ę oYVÎäp)Ķ˜ŋ[7Ķ ėR)ßęŋi?˙ÎámqļÎ Ž Åą×Rņîņ2BS¸+q‚ë˜ē6ĸMîq|”†q͇›\ LŖ!Чaã"ÍŋŌŪ§Ôŋá]•Šį3_û>‚ĶD 8āÍF“ö⃎ŲŸ–šŗÉÎaET"˜røĪ§i~,ŋĻī›Ô-—WÔ %øøē°˙¤žvåŨ)aŪĖ­œÅ‡=ņbŽÖ ĮuX{/§äôØsųOę[WŸLī ˜w™Ãš`˙ĩ|Y¤íp6¯O›˛^.ŨČ|XSʨc§IúōH÷˛bDšáåR@ԖZK)Ŋe|…ėĒ“c>ĪNûųo‰vöŽēũ8ȃÛé‡ÖŌŧ Ô˙ƒOé_7W×wœ- ũÛ0üØŨŨÛ߃‰j)ŸâŽw`­†i/†gü.v-˙gĢĮŠũ÷ÚŊû2}jķŠ[#UŽ`‡ŦQ}úÚuWŨží¨ûšĩķqIā\^;¯×é %ŨĸÂÖÅk$å5ƨēTT§Č5ÉŋIŸ˙¨uõÛēúŒZnMzëë:Ŋ°ŽŠũXĩ­áĢŌúļn=ėuĮ ãĨąP7rËņ~œ6ųĒ&ŊŸ&%¯Ō÷*渤×eûŌãëo[ģĮßn{}Ŋq\äÕB§Ŋˆ ÉE' *ÎwCPŅ/ĩŠĻ8S˜‰¯šš3éu}mED8ev؍ÂYžūۜî+PgĊ 2ĨŪ\xfzF>}Ô˛ôš§>äIÖŠWīŋ-äķ÷—JE Õp^ĢI8&ęjh*ĄŲ‘ë[ģGûōB|æ$ŸžŅÍwu*2KˇT&ō)|=ëŋĖŪÔĨŠËŗIūŖ~[—g~¯#Ÿk5ĩjZãûĢ.\ŅGÅ~ŧJvpey>‰ŧĨqܕãI74ž‚˙ëøŧīŋÍķ_qœf˛ÂÉ™Â¸ÎÆĻī>}NFå|Ņ:YTemjîåC9œ×ãĶ|÷Ēįģ¤ĪØŦ=:ŸĶânÆôiüEņ“ŲRËõøŨŋl\ĨpÜŊ:SĸIĸ‰ŗ\Ôąķ$?ë6: ZÆp$VØp—|øôÎŲ|ĶęŅ=kÎīįėŋGMS´Î—Xõ– Ŧöņ ÂÅÖ՗e&%[ØĖ´B+QŦO¨ĢŸ–ŽlYëŌûēÅFJ6>M)\AGŸáßV´ĨŽÎ5yæ¨äûĨ†ž]åđđļxČŅĒŽßđI~¨.+ũpÜeuĢ ~RbÅØt3HŨčÎÅûô û9¨´3×äyũø1•æĩ4¯%hČĸCˇaE-ļRî,GĨ6Û‘X>ÂÔ¤šđ@#ŦēÛ.aW…1•ãų?.áÜeä›c˙IOWˇˆ{ŧŧ;WU)à G 7ŠŗĄ›e,ĶĮPt8h>Z—~hüc8ë°rũˇŖæSŅęąÚ8†æ_Ņ–~+îĢdđã|RĮoÃø°r¯ĻĢŽÜ#ĒÆĄk0fãöšQ9KÎÍŅ#ƜZŠŌDžĨ4iL<į8AŠ[ŠUۜQ´ĨÆ ØRīėî–ÚەĻpîË'šŧXšpŌ—[Ö˙ÄO3دIØ×1—~Uīđ×ÅįÛQmC}Ũš|KâDũÐEbŠ“đZWŸēēåęãę–ËĮך.ܤūMōiĻ ͟DĪRųū*‡›đįÕmīčĸ+æNŦ¯ŸÔÆ&ũ•Ō¤3|‰ū4ĖwZÛ+ŗËķsN:ē.ˇĩ'ļÔŨž^ãĸ'áDoĮÍ/õ]<į>˜ŸmĶ/uš–Ō$ ŧ8 Péy÷îŨŸP§'\æŨŨ×Ēs:A]ĒN˙/žĨ[ 뾓J?aĸN4;߯Q3•>Ŗæ˙ÜŌ7i¯ĻšęövOķ3ƒåųļ¸haųH؎æĮI0Öw1Mō™læV›į`f&ļ҇‡ļ×ōÔô)üč@Kt:\uŊ‡öūF{yž3ļö˛ŗ˙íč`ŋ×ë3ãŅAˇžáÛ7N+Nëé훉c=•īŖûû{ø(ôZãØãĒ`‰ņûķA+āxÍĖÕ¤nÍũˆ7Ũ{ųŊišāĐMj5Š|F-̌kÚēâŲcQ#ÔĶ­ÍšĐį7¤Ų9é‚ĀĮÛÅ:s™.žÂ%_:‰>‰7”x`ëčāĪáæüØļ[ģû‚R÷a­ ļԘ7įg[¸mÚŖÔĻU{”š ĸ^‘§įŠäūŪZaÎč÷zPŊū‘h•TÜŲž[u„ĩŲėėėof$銞,$cOĄntp.ĢËdŊã5]¯1ˇÛh  E­ú1Íx8ž]7ĮĶZaŸŨëdú™ĶÃʘw.˙QqëIáßOÉį÷ œ?_Zcü¸ĸĒu8}“øš4ĢŨÍåŲ­Õš?O™TƒDD—Fã‹ŪÉÕābw~ČúĒŽ ŲÜÜŦŧÅPjhÍTœ[­…Ų–ųĨžššēíf!Ų6mŠ‘ˆxJůņHaĄ´Cčj<–hUā đ YjŽ ūT}úđī)” ÖęĨ>Ŋ¯hļĸH2*ļ5w—õ~jĮ —ķ–3‘žŪvM8îęDŗÖ/¯ķę[—Ģ9īŲ2­\+*x Ūˇh{åoëķ<}ŸßM¸´I>ų4žnY¸ŽnŪs}šęŅԀūžŧOt‹/qQLΚ+@$ÕJáD‡Ä‰šō@į1įīAxB“Ū?8āö/câÜꤐ+P‚íŋh"­w*†;ɃŲ4 X}ų NBDŅSÉ)Ŧtˆķą’H,d­"oäiBž“9īđ“2znōၖ'#Y’ĻîÎ9ßŪ˙ßŦĪßcĨOÍŌÔûÎü÷ ÷ķ=ŠīBšVO_įáeEŋÎҎ 6ØˇĢ˛ŪaÆ3õ´ĒĻĪĐ~ņm‰4Bg´ÂܕœÃ4䁨”æĩ?~@ÍĪw F¯­­á9¯›íˆ闗ųTšíBŒ˙ ÷ž(ƒs‹8ŊČÄķ` ĸúĐŊ>S˜tÍ,8÷H´*ōØÄô<Ō*"Š€\éÂØĨŽ@LK¨į3OCQjŋæĻļ]Ž?ĶTāŲ>ŪĄƒ/,¯C*0īĄųøŦ†eėĶc“CąęGpM˙­;¯™ŖL)ŪŖæåļķۊr‡rŽ}UFGĮ˜3<¸îÛØvŽŽŖmŽėiX×w“Š­3d:áĶ §O<x`,(ā͐lĀF¯¯"~sũŖ˜­öûxŽüˆÄã­Í_*ĐíaWŦj´Ĩ&>­Ú÷Œéāĸ]Z‰[?ŽēPÂŅkqčōŨ™Ŗ–UÆļ‡įY—~hÁ›ėäú7Ÿ>@C†UãäÆÁˇo8&ą°°„įŅņ1Žēü~ėvaiIĮģļņŅ:Cũ‰ČëZŨõû‡‡‡đÂ^ŲØÜ<::"9Íß§AmGJ?<˙:š4o{Ė<0¤,ģĀĀŦđE-ô…ī¯VVöö÷īēŨ˜>CövwA7đ‘ĮđÕõõuĄÜī''uíBOUė'8~6d×YJąa,ëÍø?_ĘĮ‹”hüāyuy‰]¤ĪŸ?“ЧÉ—××?~üˇúūũŪŪž}Ûnņ0„¯ÃÉÉie8[g(ĩŗĨŽëéL/qHĒĮŗ›„YŸ§`ÛĪÎaÕ5ößiJØšoc žršĪĶãĘu˜t Īömn]‘ĻŽŦŽ[­[]YäSWn k“~2=sû$ko´R@Š>‡‘…uqqq{}}zz öāāȐė߈á÷0´Q<œūĀBŗ9V4…UáHtP $:üÆąi2i^oK ‡`âæNuZJ$„ƒø8#O)v NÆÁ–Ún*įŠ"ņđRmˇÎ¤Cņ*r˙./!ŧ (P:}1ũo‹—Z]HŊPįļęÛzÃęFœ˛ÜŽųx[¨ßJ]OĢÍÍõ““T ú4:æøøhTZÅŠlKķ:ŗÜĘļ×GeŪ`>äEŅķÅ*ŠVZ¨?Ō°ŋđߏķsč—û_ŋ2M¯×ÅRmyeåüüÔÃ+ž`ũÁuū¸¸¨kŲš°Ÿé@^7|WÃč#–ˆĶģ¨ R?’ōœuģŋø„?ąrE˜åF„XÂĨ<% ÜŦŽ~fz04ä€?;sskvĻXĮ š …Į|@õRÂą™åo<ܖēd+f’bÄxk#ĨLŨˇ nŠŠÅš=Æ?æĘvåōogļŪČëãëŌø˛jĶ+ĮZŨš„Gnûˆųģ:<Ĩuũ؄ž9^ąŊa†*âž@ŠM´Û“lVŌņŸChËKK@š–QŖûNXĻtYY]Õa.)/¯ŽÖÖį^[]=׹xlmŲxߖ33š'Ō`T"ži˜'ÛÃsŽú ¸a”ˆ‚P‡rž÷€'˜4÷÷VOÄ#Œ×Ö×!sÖÖÖEú m×ųų ĩ„‹˙ū=ŠDķCķüŦíįDú%+ĖqˆĄ˙߯ģ$ĩA¯˛ÆPj^Ė*(ĩ(?*#Ē}#ˆĘ.3ŊÁŦĸ'„c2boPD0øš.Įe(ģģŸĄ (ŒŨĶ$PÛú;;;g>xģģģįRJRhZ››ĢĢk‚=œ3%å ü )myjC$ŸöxĨō—ÁkhÛ!ũųŲJüüsVˇØŽĘúË­9e[ꀠÄúôûwÁœ`´A+hc ĖÆÆ&šåŲīonl ūōōJ¸ēŠļC2"ŸUÅÎWĄ‹Ė­ĻŌ@ üđaT‰bm¯¤O™nX’Ą×” Ÿã–Čžĩĩ­•ŲDwÎ æ ”ú{:įķŒëāư¨ęūR5R˜įęĶ¤9>>Á¤¸žž†BA7L{ûû{ą-0¨¸ŊŊŽũŽz Ÿį\}Ž‹ĩO°Ĩ-‡#‡ųH[B,ŗ_žėp­HíO OHFUņ'8ŠÛĄ&äÃę<ņÇLsqqžžž^Ho#šÕ{@ÎõA‡‚fųĮš—lŠĩEą]$ĖŌ]ŒJēÍÚÃ>ŪtPĨgm8ôÚ°4ŌËÆĨ˟ģ .OĶw9rËņö•æci†æSNīčššÕĄ.ܨí ōÉå_Mķ ëŗEžV%ēY?ŽŸĻ.öHš_*úŨՁƒ6{ļĒ)*ĪÃ[+ûû‡bØÂŦ0G9JÄpžøī?äũũ}æ顡ˇM0ė˜q9xŒwˆĪx"Ėņ:CŊ6|ÄaŅL§ļßž}{˙~•ų€ēryÄI9Īšš™Î|ëm‘ 2¯ĩõŲBLg~~nffoo D0™Â =ķ¯k—\QqrbrČÕĮ(ö§pÁTnę‹gÆQōŒ1Wr4éŦDū[j*؆RCI´pÁ7‚&Ĩ#Cã`Ø_BÖDŦz!äŌ˙ŽĨūÆ~fe•ÁYąūßž ŧ ¤@˛§s.ļTĨÖUߐ: s4 Ķā(˜Ą.=^žūˆôŸ:\ÜãŲuXx†‚<ŌF_Ÿēđ#íĩzr=W5Đ'O‡:ûˆã%ßqÜåâ­wĘéķ#ŽC2_vvNNq X‰Äcm ‘…ˇ°‘ÄÁ˛—ęđœ% x2J6ÅG™Œņp ]Â&r"ŲúmŠ‘ 0vƒ-5Úo\uyŠŠ‰ŽĪąÍsA–ŽŦÛQ@$†°6Ā1ŧ]0csR"koŠ7AŸčķvų$Œâ§Ė•”cq¨)W)J-úZDŠŠwËĪáyCœ†smŠ7ũņãú.ļæUg—42ž5Œi~Yasʗo!Ũ N3ķÄ[H ü ZQGÁ'X‹Ŗē؉,:ĶL…éĐkĩQģ-äķ”ļ…ü!ā°‹§ČŚi]l ōË×M'VĢyš>…˛(ë+°Ī<­đ$/c Ē¨3Äũļä”JøÁĖaĐâ^0Kiõ>ǁVÃJ­f}üŗ’VŽŊŗŌ›öÕŦĒâšžķũčÃ÷Ŋ{ö tqôsJŠ) Õ&ÆÃô°žeJOį<}¤%[jãßĸ`K}C lnFŪ˛äøÄˇe}mũû÷ī(x9ĻĀēvY|i?ĢåsūdXČø_cCXŽĨ‡Z EY4e=wŅ)æS™g!iVWßÃđŠƧØēāDŖœnė÷ÃÉËbūŦbäCÕo¤Î䁗&å'T˙°:ĒČSøŲJ)cá–~hš°S7$ŸQņfßöQÂ*%š`įāÁŽōÕûAÛFŸBš"OÖõoķx76uÄŲ8•0耥ŖŖãCŦH56Í××ē˛Õ˙Û¨‰ŖÕÎ IJ*å8ߥ@#vņ‚3[įGÉāĘeY&1X. 𭏷Á–:Hãę<ÅĀcnĒ3ļvįæā)ĻŪ'ˆétpĶ„ø#Â< i ¸SbÃveŠTO“<)>aŌo“Âhs%Æ8ļåĄ‡ëÄzEöDHã1Ī/~ e@¸t<ˆķbKMŒGŸŨĄČ?‚-5ĶéÔgļž7„8ÆÅdŅŌgfú ĘTO‰‡„PëīܡH‰ēĸļĸŠĢ%ō„ŲŪÚBÃÖ77ƒ­$ËL)?ĀöŠu!ŸXOU˙ŦΑPjĄ‹ŸžŠí/ãĩRÆvl…vUÖ§PiZĀ>Ë´ōtÉQÆu€ĄđŲ™„ĪžŸ)œ/4…ŠĒ‰ÚjĨyĐ˙ŒĒeZųöօcũ}ÅžƒėV{w\–Ų›ëImīîí‚tXÛ[v!„ŧZËíâž,O%3×Ū–ZæĪKž]­Āĕũ˯0k÷…ƉĢæáĀ.Į{yž–ˇž {/: ”Ņ–ÚbëΈ91 čõ™aŦü0ÛĄĢËŧ+ųh­jķ,”…t[[ŸąOŽÆ‡Xà €uķ—Ēéęeum S;lZ|ŧ¯3ˍ|hc9˙qíĮy“á&¸¸õāpúDjDĪČÂ!#bę“Mo5•oūŠĨ§ŖO#š?…'ŗQÆŨ>đéķSzĸÂq¤† C,ĮWÅØ|ƒîHix6 ÃPqĀãXwíjĮ{A 2°cÄ.kyüVæ‰eļžĶô86‰°™p诪ėßęŽræŅvdE”u)ž,ĢMŪ MüœˆzØ˛‚Ѕ šĖČsf˙ÃS30ŦĀ[ŒÄ(<ŧÎ,rļÔĸĄč@5ŦšÚFđKÍtJnÉGP1öņšĐŌDA† ÎLĩÛPGDēAqģŋW•QŌ›Mę \öđˆŽÚĐ>ąYŽ*īœČ­ÃūūėˆĩĨŲžŦX7~ëņÛPDÎ{…Ļâ[:X€ŪģƒBģ*ëÃ4ąŊĄ ~$Ę´‚Xš/Újģ A…Žlj>4D‘„&ũÛhõūõehĩé¨>§vu´ōí­ ĮúWŌmåũ{,9@mÚcœĘ9Ĩ%(ĻŅθRNŽ~ø€aČĄmĮÚm˜aĀp >´°€GxČx'ĸ-õ‡˙āž,ōãáĘ<×Öäđką†0Īl =ä ęƒ:`lŪ./ßR8ĖŨ~ÎJáâ<øĻøÄË[(ĪЛ‰ŨōYyÎĘxļg߅0Ū‹3jHXâŠ}‡ĸ`Á~ėķ KŋnŽŽī”ß4e§Ũ‚§ƒÃ­îŨ„‹*sšƒéĩb˙ ø[įõ\<Ō„ķ=r˙ëÕÕÍōōĸ‡ĖJzxüę)g–[€c×VE‚Î1„Č4ÍÅP Kp8j(iœÕ[Z’ŗ tŠ{8l“ÕŖ _7†Ĩ†8ü§aØZ sõā-^p,ĨāC Mø Ũß˙B{ƒØŽĘú”Ú+ZbäŨąŽV  EĶz] ŋR.ĒM Pđéķķo°KJJOdZŨÁã?ċíĘŪšé\Y7ßŪēpŦ?ę`=§]ĒF{ĸ=ŖU‚ėƒ‰ÔĻ2 Ũ)˜=`>6Œ†ā\| ŗ§>AaôîŨ;häČĩR ã1ō’qKāĢB?âsß"Ė(0†û"Įc¨3*͞:­įŊČ̜˙°‹ŠÍ\4 X5jŽ ˜ā'ķu 8ˇŽą—ôDC0˛Œ9Î9”S8ŅáõōĀÚˇŪÆr{{UmŨܔ•ķrNÖČ۔&ņŌÛáã‹ŪÉÕāüsđĐjū8Å<á1Ւ kvÃĨMgÕps ßj[{û8æÔíA×ôXKĪĘæŌ˙–Ūã¸Cˇ¯Äě‡Á|§ŊŊģw¸÷9¯ … *ũK}EīۃÚLŽG+ęĘrŒ&ėįŊęfë ŊÖä=ÖfŊ^°Žø­­^ītp‚Ū÷}OÚ/끍Â÷"^F˜č8ųđÅ­^Öę%ÕöųP`õ ģš2ģĩ"[ąé—(((0Ž/īO.ûģķQĻá8ü˙p>՝mŨy.Ŧ?‰Ã"áÎŪÁáÁ>4jjƒXÚ/ŧk-..[ję͞…l(đ<SL;"_)Õ0ŦKĖ–z~üHËš,Ī×VۉœĮpkãāį]ũRŋL×ÛR“3EûōN4y•<`HDv6ƒ;ļAšĻpūŦ'­DŸD‡Ä™æJ UåDoĄ1™°jÆĢn-úūËÚRã.U¨5Åāq[j€ŠÁ[‚æ™~JĐ˙^0ëmŠ­Čā9š4!oK­Fųæ…[j CpЃŋ7/.ŒÚ'[jrå` ŋ:ЖúË:\ũ¤_ĸ@ĸ@ĸĀh8<ģÍŲRĢ7^qz!‰HĐGlŠ76ļļ÷ŽŽē÷<Ą(ZõüģJ[ęÖ`~6gK-ļ#q2=Z•_{jj8ÛG ]œŊÊJ}K„ĸJƒ‰8Ũ(îMԖį/īūĐÚ ã§-áD]îYĐŗ°8žH[ęįc:vMx”7,Ōg!÷LR8ŅáÅķ€G•ģI´Ĩ^iwm`j2w)l]žhBQ˜ččPÍ—ƒųĸ-õŏv{t[jš^Ų|åŅ–ē ĨžmíėîíīlŅ7‚ē$ėĩ+ÉŠ}o‚4ĸĸJũ:đ<ŽÍ°3õ&:15ō-PƒŽääÖœę æ˜JŊšÜŪI(õ[āƒÔÆDISāëŲíŠzüˆ6DŠãLâƒ7ŌpĢô\í 7[ģ{Gßē÷ũŅPjX5n¤ Ë~šģ䴏IõËtĀžËĢąĸöØv†į;°Äįo™Ī_AÛšVÄK¸kĄ0f‰RīŦÉá4~°č ŦGÎū)ߎ\Xú Q Q`t žw=J­Ū–RĪčÍĐŪ–Z=€ŋø—`îWŲĻĮEŠÕ^k (õr†R‹æ\ũRãn:øú…\\ũ:đĩ%Ņ!ņ@âÄΟč`Ãí4sŒXu˛Ĩ]‹H_$ $ &lKŠ3fKoO´ķŒ­ņ §z÷ d™ÜËJT/ųHž$H<xā7ōį ;ųāĘddšžüåöīqOųvÜ2Ķw‰‰ŖP xÄĸ$–Ģ7Ė+NĻņŌããUVÕXŊK‡S‰ąÚW īņK­Í>öā—ZÃ8ᄃwtŒŌ$ˇ¤UĸCĸCâÄ‰~p>*ČŪÜm ŖLĻMŌŽmõĖ›‹‹ •:ūáÃ\"ö÷ßúôÉßH…CŪ¸˜¯p•lî˛Ē&Mjĩh*ÚäWNc66>0Ÿa´…OV;ÛC ōßĸŊMĒÄ4˜|q9?ūÄ­ĖJĢĻŠĨÄŌ‘u›ô{k(č´ęķ×LŲėŋÔĒsgŌO4gîĄŅ/u ĻŪŦ[•íčkY“I”:bNáD‡Ä‰$øm< îy€Ÿƒ‚—åŠ(OAš›[Vįž˙žŗŊ í×ņŌÂõl4T¨øÁ†7õîîîŽÚōæ¸~9eŒ9=ũÁr}„{=%ŊĮķׯ_đj€[†TĪûã‡eؤ9PÖņCJŦ../¯đí*q,䃌Mę™Ōym~ow€1 @MgJP áEÄW1’ā7œD’–icFãC`ÈQ`Zū,¤GUcÍcõ–——-—ŋ˜12A 5ņäEë`ķz” õˇ ˜#aM؞ŸonlDŋĸô9­šV‹ŗųhzü–rūŨpÖÂŊ†RëIDÁ›Ã$oO4ŅŖē5wĖÜŊ‰ō’ˇ<ĨÖÔnšFķbT„O'œ>ņ@âÄ‰~+Pƒ”=YõŖą%ÆOo~ŠšÕüÛák‚ŖŖŖhÕ0"օVãÕÎÎĐkŽ4ŠĀīëׯ0žĘ\]]1üû×/$‹) ø \y?Ļ,”ŠÍ:dZ€iqyPč T)Ž+bõųΎ@×ußĸ&ø 5Á3VĪWãøøJŗäp¯Ĩkņ‘†ČŸŸ¯ŦŽbÂĪĪΞ(qö÷÷˙žÅŸČŲN‘RÎĪ”9[ę°nWV&?›´yĖ–ZtkčĖj‡­æz{âÍÍÕuˇ˜ā­Ö|§-~ŠŋlßŨu—–Vĸ-5ŋ‰œÂ&}M(¯$˜(¯÷°F9ķĖũRĶ^Ũ3*ĀJĄ–u@­˜vÂžŸ_Љ-b >ĒyĨŽ ;cÜ[¸M"žE‚b D™~€sS˙ô—ŽÃĖ#§„™sŦs!ˇ˜~fŽũņãtâōš'ļĸŽ&(_cFM°(´ ;tē9‹9Äú !ü "^ūŧ˛ôē˛%uJđĸ)PōKŨ‚EĶ(~Š?líît{ęeúĪLká]{yy寸Ōôõę%ÃŊՖ:ĸ ›ų­ØŒĮcR˜KĮD‡D‡Äo’^ž-51ÚēgÔEęLW =+l \Õ,’1ŲםG„™52ŽŦ,CeŒZ,4õōíl^ĮaØ?ãŽ[ZAôz]Éĸm´âĐĕų*Öš›ŲRß߯¯oöŽo‹ūü :.âhRWÆŖæTß ?l¸SŸÖQPŦ> ›íø9Jq†”^W֋ÖSåĨ@ÜīŠh´ÖP€Ō^CŸīlŠåreDũJUgũ;xüxčËųÅûÁ=žĀĒåU˛ĨV2$Ûņd/žx ņ@â?”ĀoĮ–:Ē˜ČŊE2P[ÜîVŠ(Ŧ¯¯Ŋŋtvv-<ŗxîtĘ*¸ˇxŽaxé€ŪŠz3~ĻČąĨŽ\en¨!Œ.ž};@žŦ- ĩ–ØãØâđoQÖĪJį$t^Æl3í<e.ŧÅĢø9JĮYI+=īk^WÖŖ:YJđ˛)0![jēųĐWžĒØ(û;<˙ņ€ d q'[ęâ:&aĨÉßKâÄ‰~#Čõ–lŠŠŌjŸØR+ڍ ‚ĪÎΨq"đųĶ'žÂŸĢĢk@[Ŋ­M„‘ 0lēČ %ŖÃ8éČlo¯¯+Sž–Ÿ€~ŖŪ\°ņ8::ŪŨ57¨ĀÚÚ2y÷đo‘ŒfÍxō“Âīũû÷×áŧf†R‡Dkëë ūÂ3~ž+=īk~{{ ûek‡ŠöcP`bļÔ2XÅG5QjøĨ&&{Ëņ__ÂP¸%FũŲZđāĒß Š>”čx ņ@â7ËôčZ˜ 2„1&ÔĮ>iîĩŖœSķo1­‹/gõ›A˙ĘđéÕųīN–ÁŨîíˇprn~aáDâŲcyj÷~8ˇŋˇˆÄ'@j ôÂ[0gÄ 2@¸YÉJ\§ú›8ĘØÚŠ5†Rĩ.G€ķe!=ôûũ}qQ‚W­˙YÁ‰ÁU– úĢŽü5U7Ō\^^㒞ŧČđÔ9*á̏LŠ"|~O7Jé˙T•÷ÚZs`˙uį>ã‘ôūS€į‘GķK-žŋøŅ†ŦÆú‚įrWG_ÛSWŽÔ‡×VWˇ÷öģęrvÕøįũ|{qyųīæįáVįüōÚÖ|3í…ųÎîÎŪŅūN÷î{"ôøÁ2,M +!M?$H<x`Š<€iM<~\Îŋ›oĢ=ūāņccšũe}áeMäŠļO¤T¸ÃūÄ|ø9 ŦV&’sĘä™Sāđėöäjpūšc˛ĢŨÆ~…š9—hŧôŖÕiųcЇ%Įāūá†U[Û{GG7Ŋ^Lmķø1#_Ú}årSų:G$ŋÔfm“|MË$ŸÉļ>ņ@âßĖ*y^­_ęgŽv<Ãę&ĨOŖuŧ“>ũ {ųwTiBļÔĸOČvļÔĨž…Ú~Ĩ>Øßšëv—Vr~ŠGSS‰‰‰‰‰ĒO+J}Ŋ~ŪIÎß3÷Kē.Q Qā9S úĨ6”Võ?FņKũáÃ֞øĨžéõƒö(5˙žãsV_IXN„d'ģõ­ũöĨpĸIâÄ‰L—ĨÆYyҧũūšĘÄŨüÄrņOųv*I™& $ ä)PĐcaã1’_ją Qi$Ú˛ZSķöq@Ö˙›Ÿ_¸ų…ã‰]ŗČnÍāöD\lzđe‡sŖ-uę‘DDDDDßICŠ//q槀R'[ęßŲŠŦD×D ØRīîåPęė;ôöDÉÎĩįžÆŗ53;+XĩˇĨVZ&|:áô‰$H<đ›y@¯IP”:ÜmÃSšæŸ‚4?åÛ)5'e›((PĀŠŊ<ąû¤4ŠņÁãGđæîîPä•CĨﺌRūRãj)Ëė=đ‡úȓŊļāo’įŦ͓_ ĶŖaĸCĸCâÄ‰ĻĪŪ/u”Ŋ¯Ā/5}QûÔq8LÆŨŨ˙ŨÁ=ŪūDx|ƒh¸mÆ;ÜÉRw?ųÍŠōJÂĘôHųķįĪÂĢããŖ˜:˙žú™^hķÖ5¯6Ę-ģčnūyJųl)@¯ŌQ§… <š_jŅ%šÅE@éā¯ŨūËÖũbK ƒjhÛōS‡ɖZøá7ã1Rĸ˛a*7Ņ!ņ@âÄęņãuÚR‡Ûĩ3ÅãûņņÖö6n„øõ̇ RųF˜P(ņÃ.ŧ/bwwwT}Ĩšw ¤äE†ū÷ũûYĖáĮŖ–>vzŦx­ãđB›ˇŽyMbŅÍ?I)_&oKmšÛā/U˛åöņøA_ËęŨšR,Ū1#Ŋ’*aՉ‰$H<đ›x`0Ā~ŠŨž˜›ƒØSų}Ī jĶü[Žũīߓ\ĻÍų7)F܈KūƒxŧÅ-⸀žŸŸãöDšUqeÅۜėíîĪÆ9ßŋgJô‹‰¯b$Áo¸Úˆ,pą‚ËēcŨíŠ "žŽQ.ūē›ã„[#ĨJ1߲ ÍÍŦ ŸōüÜ>÷Aū¸ë‡wģD÷MBãG+Đ@6@ūŧ‡’--—\˙Ã?˙ā|ëŸĨtÍG>h/ę‰ryĶĐT˜,eú)ß[ĶuģŒ-î}ęLÚTdՎõ†Č"ĩ ÉPjÆĢ}ZYY†rgshęe%57×ŗÃZ­ûûtGä???ßëu˛úøöíPŦwÔĒ;CŠCĐX(ßëëk0>Á<Œ†ą4QX°|ø€H;æˆd¨!~(‹ę¸¤Sfiáķ#=Í bxc,ūt°Ô¸-Ŗļ=ĨO´M<x ņĀx<ÁL/f˜D—RLōõ ˇš<ģŗ>?Ĩy>ÚC‘˙SžŖ¸W˙ ,ÅĄ ûãOl2´5ü¸'0~čÜŅ_áËMŸ? žwO.ûŸ;Q.]\üŸwj¸ä ÍđT5˜KVY îėwīU9×EØûųöâōrŪ–úđˇCŠ'dK ƒSŊ_ŋ`/ã*ëĀ ¯ ģcƒ #‡WŖ.ûáO ',j‘Oēš‰¸r†"ŪëiÎ;;Œ÷Ÿ#b˙ÂwÖņ# 3D}˜aÂ™Ļ3ŲZĐ÷] ”áũÔōĀëĩĨ~>ÆsŽ 4„oß ’J=adBĀp°i’•mĐ{“ô)͋ĄĀo˛Ĩ†>=[jp0Î#ƒÖ˜aĸímąæFėĮëņŧ­Ŧtc†ģ3€“ąĄƒ4xŌB‹ņH#9//ąˇœCŧOƒQ¤ģW38A0žĩ˜™î˛%›ÅHOOˇ.đaâ“Ä'‰ūŧZ[ęŖüҊÂPXØĶĢdņëWبt`°$ŽI†@Üŧz“ORšB‘d°Ĩ~ŠM_ǞĨv?&jKŨųûočÄXķUâ1pķNû*lķÁŠŖĶ™Gbœ4F ,F0 €aāÉķ<ÖQ–ŗ~Žoņ[[_‡K õ°Aßâɓų%FŗK8YĸCâÄ‰žŧZ[ęĸ|ŧ’jÂ"üŋ˙.ą‰ũß˙Ey¯¤mŠ#R`bļÔĐE—T[jÕ)åöÄXņK-^>$bâ~Šá,k>9•\’aÔŧ™ ĮnĻҎđîχ5‡V_Ā9%‚á6G‡a#>ËšŨ¡ŊnNy  Ã„Zډ}CæÔûÕÕ0ƎwŌAΟĀ]¤:ŠÜ„ų%H<xāx~ŠGœßSōDDßJIúĨVU•äz{⯛Ģë;mÚRwÚ-ģ=ņŽģ´´Ĩ͟~Jģîīaž=Ũ0+\ŨU[Ž :‡;R—‡ĀĪXSâT"vjxI\uV:„‡Ņˆ÷ĄŖŸß/.JYūsXlŖ8úød†ŒŪ(œŗÆŠd¤Á!˘2Å$j'$NHœđ”QęáXĨļÛ//ßÍoOÜXnyގ'>e–Lß& $ L›‡gˇ'WƒķĪæÍĢúĶĶâúBÔcAbqĸhÉŲŅDųC¯˛Ü?=Š<C>/ˆcRUR`ëŦĩwҚmĩVŪĩp¯ÄãĐč~ëėļu˙w[Ŋž>ØŲA“î āęģÖáĪ>\8j}:¯ļV %‹(Ī)Äsx{ĸŸƒĻē[īŖkš[^‹A‹]][Ŗ9n@Œį|`ŖĶGčĶ:}ˇIE}ē5”]šUqe…~—ųÛÛŨž [˜VŨ„5_ÅH‚ßš"t†ŽVÁY˙˜ŌŖ]1AÄ×Q"Ęşxōēoü€pk¤T)Fâ[´š™äSÂ"´LmäËŪķqhėhãđZ˛É¨ō÷MV–\˙Ã?˙ā=¸e€}–Ō5ų Ŋ¨'ĘĨŨŅl>yÎČŨ$ĒZĩ ĩô¯¨ŗ…„kJQ4˛Hĩp•KúŅCæåZ=~š‘_Á/ĩyÛ0Ęøņ˙ÖÂĪ™9RŨÆĻĀŅuëL‰ūmĄ›Būúá"mT jČÕād^pJBĀŽt+›ėíY_+)Ũ-Ļ2-ß-P*ËBd†˛‡>đ)Ŗ];ōwÁû9ŖŖ)ņ[Ęö7S 7´l0†C‹’ĖQü~„ôĐ­ãŽ™ĸÔnÚPy4ĸĄÚPlķKĘ'UįßÜ늸DDéQø4 ρ1Žfø´ųTR‘Ņ0ŒWãƒėsĶIĻWëWžsYVŋ8[ęæ=4dM@Ķ‹hKÍģ*s†:ûūũŌŲŲ9´pgëÜ)Ģāš=į –ÂŅkh“ĒƛĻ˔„ÛQ#Á‚ĸļNĩē@q”~yyã“x> x6āpf`U3&åYL)CŸŒÔ”ûšr{+¸+đÍäulĻ{„@%–į[WY" ÍÉ ŠLéķũrŽyįϔΙŗĨv“„˛i´ĨĻI˛Ĩv\0E ä9ķZĒ[ĸĀ ÜáÁcüۚø÷ŸæŦLbuĄĩ§z9Ė?āe/ũÆ @ížįKŗĨnŪö2§ĐĢ,•WąĨV´ŋ/;;°KĻe”<>úÄWøsuu Ē­ÜLTEXà ™Ã†š3SVĸÔ°`ļˇ××å”tĖ͸+mŠYaš&#t!ōĄ>} B• 4šĐøų”•ã!×á.ļ2Æ,„ äöwŊU–M;@ɈsWĻô5ÁŠbõũûæ}šRž UŖĖAkKŨƉf­ã6øĨÖÄę#ėǘŊˆąmGø{‹áÁ%Š’‰‰ )D?œ/„Á´yDΌâšâĶQZ"˜Vãâ>Ÿĩāâ:ũFĨ@íüRaK­sĐ4?ŖēyÃĄƒBĨŸ ú €•3>\ĶwŨîíˇp0n~aw)ā'n4–—ĄÅîëĨ øíīíÃ葸dnf†ā4@b ÁˆAC€p3e%J Œš‰#Ž­­Øę˜R•ém }QĄk?T(õÁX‰āˇļļŠ?‘' gĒŅø!ôlD"ĪX%š2ƒ)ˇ˛”ž€H|꜓đ•ˇAa¸@öĪ•eĄĨ''Įđø—X˙˜ˆĩōųcsācÍÁĐæR>7 PÚx™3ļ-5ũR[neŋÔĐĒënO4nvëB™QŠ^q8ųĨ~nã!Õ'Qā)€õü‘ˆAø÷€˙i=Ė=§Ėå­ãkŠÚæRk{Ĩĩ4û”jžĄoĮōKŨŪ™Ú…äĐ2Vũ†úxÜĻ6ôKŨ<{,xøōŅO’_ęGIôBLĖ/õŊSÔEXŪ/ĩ€×EĐžÍöČČöņ鴋ûBĮLĒvĸ@Nn[ĐĒáÍú4!7FVđ˛­ģÄPŦWŽZËĮ@Yé׀ÕķKŪ–:K3Uĸ&}ēAũĻ$8"ųí›AōO)F&´QE ŦDšdĨEôŪ$}JķR(öaLž@æė—Zufŗúp~Š˙ˇ°ôūęę7đW‰RŗøWŒCĮÚég>ũ^VžˇŽģrĻp}i2øtįîŪĩŽŽZPÜÍ6AEįlģ53čŋk fZ÷ëYrÁ,ô\š)õęßt0+ÆuņvĻą{4ÜV+ÖÎ;ŧŖũޏÃ108āŊŋ‡Đ7ËšŋĀžÕģ âî7ĢBŦ…gŪgfäe{˙ŠßžiÍĖâĒĻ ßÖÁÅ>Hs4)[Ģt͌ķEķāĀĘųŅ#lü´†ÛWfw`j3_BЧC×?™+LƏŽnnēā%(ĘҧĮŸŦS*ûQāđėöäjpņšu?Ŋ="ͤ™Ęą°}ä-* Ã*|"K{‡ûŨŪ@üE”zqųķ K77WTŠÕ/5›öÎŪŪūÎÎųÂēĸnnčĶZ’Û ŋ{7-Áũ‡Ø)›(đĻ)đáŦ߸ÛE”ÄŠY¯Aō_ÜŪ_ŪˇÖ8ųʂV}uS-ŠhŋĶę‡ôŦa“4NGļv͡ú]Qæ9'0ŸrÎOŽŅŠĮrŽ „ĖÛ]Ô°ĄËë#|Ģ}õe6~æNr8 ön~Ū9^xXûÖßXn™šáĮ›Šņ‰¯_ĪnO¯ŧœ2įĮŧž3zŽ&Č@ŨZąA@Dž€ÉūöŪūŅÁÁM¯O}i—æÛËË+PjŽaz3;ņæŸlŠ_ûøJí{C¸ƒ!õĄÜ+Ž{'eE]—ĪíŲáĢng0XYŖģ{‘ĪA(Ŗõ‰{Tŗ„‡ĪSJAIƒ˜‡0ÅQL ) o–Ü(žë?´Xîßĩ8ŊŽƒčŗĀ–î–|d“RŌhYēaŠęķAJWOä„ÁÌbåpa†ŨÃCfÎā‚*’ÛŦ–…:ČĨ?´fĩÎŌ"ÅŦqVšvö úvŽßž´’-õŠŠ‰šĶŗĨ”ú×ÍļAƒ”lސ(õ]ˇģd(ĩJõˇ‡OO†š˙Sų‰ŖÜqŦˇ–ŪÉҔkQcx¸{ôšsuÛĖnlsĐ4Î×´ Gôô.éhÉ"aŦ'RŪˇUD†ž|ÜŪQ•ēb˙ĢüāžįÉĨļ40üH(õĆDĘ(QāQ  ÔXđ‹ŽyuĒ(ĩδSŒ#oúŅÖĢė÷>líė‹QeĨūKbø=â—:Ú{ o+üÆø-57QāÕRā^X˜SbR„N)LKŊÜ1MķdEp_esڅ˙ĪÎŊkÍÍÎvæÛ°îî,Ėuæį—æ—:‹ KËī–ņ\š×įŌ˛]7];§h'Ņę#—Æö[§Â¨Oŧ=q*uJ™& $ LˆfĢôXČé‘ũRĢ<×M>;>"ɖúqģđdK=!6NŲ$ üy ]ˇvĪårxį >1EŦzHøĖų<Îĸ´õp¸!ÆÁVOė@ĖĻĸZí4ÄŦ§öû ŨL”°`ĀĒŠĶn' % m!V]^rPĘ}w>Ũ7ģHŖ ˕Špž1›zGąĶ“ĐQJjéöŖ<Û&JLšĨuŪ_Ą–Ô^]ÛzŅļÔ8t$î/.Ú33ĢkkđĖËĀ zqĩ633oÍûû|_ÔņRnR įJyy!9ĸC‘'îôūķ&Õ QāÕQ`rļԊR+J˛đŽĘ–BwžĶŪŪŨ;Üû|ssƒƒâ:ŪųˆŸî 4ínŦü“-õĢPŠAo—{?[‡ĸOCĢžĒ-õíõĪ…Ŗã~Ģuß͊ją zđŒZEˆš˛„ÕAĪb¸2¤¤ė _cTÛ*熭,ĩ×f¸6jåÃĶøŌ™uy†íۚú4ü*WÖÃŌËįfKŨ|x ækĢĢģ{{¸Ĩ„>׎Q-šãų†û6Ū+Î{°})Œ^Ž›M ”c‹÷âŌDččÍ+“R& $ 4ĄĀ$mŠĨ2ÖōsĶ„š ø1~úĀáJŗįiE˙ŒĨÂZŖRŸæėķųûīŋĄRÃđƒŠĘœ˙ņ°åūūáîî.>ġũ=<äŒC&ÜԔ]ĸĀ[Ĩ€iŧNž¨€17Ė rūDu`žrüŅ)&‹$:#•‡šÜo‹Į8Âå:6ëtlÛ k{ûļü{ԝ=Ģė—ڝ(đz( ūÜZķŗĒŲLÍ×G¯×mõ La˙qך…cjېžđŧU–Ŋ ›ž,ÎAe­t"<Š3‚øéãžįņãũäū˂æ-ĮĒ~¨yĖ`3nėĢSžu2—Įû÷ī777Q&.--A̟HŖR&‰‰99ãüQ樀ŅS×Ņ*N§Â!ŒWŨ:z’uûĄÃW…=ū”š6sŒ—âĨFÃæ‘īZ,Ár~­iC& $ ŧx ô-ü_Ž´ķv*õz°ŨšI…ûß%Ī9šUeįSJ]OĀUÕsį "C~ 3ŅæI\0ˇåŸ;Õ_|9ŋ°p{{cF ˙„V K’ĢĢĢåe¸\úÃÍKÅ' ŧF ė/ ĢUĀđT‰ Ņ:#”ÚĻÕ­UŠÎPj=â\Á/ĩmp‰›ĨˆRŖˆ ›“¨N0Rguõ ˰ōžšēōßĸÛÛÛ ú;>ūŽ2€ Ŧ¯¯Ã‘)ž<}?Иyņ‚t9Cĸ!gäĪ]9„;ŋõ)?”¨"ŧžž‰0Ōā6ÂĖPOd7i×kdĢÔĻD7Fn_i\8=|šōķ]ŋ'ĸĒ3sߞ™›Møô Ŧ–܃Jíąę)áÔŖ ‰/_žžÉL< đéĶ'f‘Ÿ=sŲÂē?xßC,ôi„qa|ûķįOFâČãŪîîhĩIŠP Č“'ÕãûĨVz†R"JmšjĨόęB 5= e÷āā;_PŠ?}ūlíÕ| Œ LcQŽ4đŲ‡ø‚–Œ0žWÆÄ:ā+ėĻ!gčåræZķ„–,ŋ_ŋčĀh˙ā`eyiđDiPa_ŸG ē*%IHxļ€-DÚ<îyQÁ6)Lēœ Š‘÷ážß‰ž1’-u5rÜRš_ō(u6‰]ã3øšáĄC 6ËˋœėX¯!(5ĐLXP aŽ ›i„邚ņČjkk ĶßBBПA§*ŧ> äd‡ĘœņmŠe'2 ÔmĩĨFîAgW[˙đļÔAWÂ:›ļ†aœÉ€ōŠ !&2?šĐÆÆ†é@ŊF Öč0&Cb<š^/ĮÄr™3Ō ŠpUŨPÄÖöļd¸ĩ%HöˌÄhیá¯Iģ^gĨ% ŧ% J ėžčhû1[ę^ŋßîkūŨÁ}VɊzr X’Ãy”:“ÕSŨGē=æØ/ÅÔvs“3˜.xž.Œ3L˙÷ß0ׯ“VÚøAąĀ„ŦđŒ‘oi€Ļļ& ü `ȊQ™3Ž-ĩęĖĸUlŠŗė3[jÄy‹“€ ķ^ŽĖ˛­yGže-îžÅöÖôXčC|Āz c/L 6:Yî‹iĖ,ŠDđ”“%MÚō;z9•‘((0] āŧš| öĶ1ĪîÕéRÖtw÷­wķÉ×Įp X—×ČáJ]˛¨žģ$uvJ„MŲ& < Ø—“'#ŲR‹nlļԂHëŨÜ5‹(uϞWĄÔ´66ü`Ė0Ŧ¨qÕĒXŒš| ėÂõ&l0 ÞLj„ķŨhÜĖ vú ą(đˆÁ`ŦhŽ0ęĀ™ÚĸÍ@G‡Ú í|eeā4ōQ+ŽB$Œ!Ŗŗ=ĀYÎŗØŪįĀŠ‰‰OŖmŠß)J-˛p:Xõ\÷V°‹Yüfˇr'”z(ŦKkäpĨösG…'ē§1‡ûz$”zbĨό~ Láuōd$[jŅTÕŲ‡>ĖZ|åIpnOz+?™e€īėîíīlA7…Í1P܉Ĩz§Ū&¨úî¨aZ˜ÁNøą˙[f¸#ŠĮš6cà 1ŗK÷z}@Úxk1ŗŗØNeLšØbƒHÖØĄnĶ…Mö?§0t›Ÿ‡[ĢÍË˙ū[ZY9??ÕWŊn—ž^^ū7ŧ]'ggŧ‹Ļ)üĨ˜DÄ /nĩļēƒŲ˙>ļ.žŠÔ{ q]ypˆŊŗ°3Ķ;û3NšĪ­n—ŧ,ˇŽ{ũŲUlå_#gJׅů¯ŽÖ˛,æ-§Y[—Ģ^*į”öÜTjXA`âPÄæ ĩãūÆrûËēyÉø-ŗp*$Q Qā•P w{ĸę´¸ĻöÃĸ+įüčé~Ļ vDÕá‡>līíÜôD†Ģ†ŨZx×^^^ÉŨž¨žžÄcëöîŪáŪg€ĮØ˙ę÷īx”gėėĖp†Ŧą˙Z^[]]Y]å (͘¤[Ãę*xŋ×ÃŦæc s{Ms!~ķķŖŖcˆ]”b1ÎŅņ1ôxfˆS#›ëĄŅķsføņãG¨ø… +ۘnOģë͇‰ΊĩzũÖÕVkūeåtžģŸÚŊ‡ëųÁÍĖÜÜüRČ”ËéY¤oOŦä {äØ>ÁÚˇŪær{gj*5JÛöã)ß>ĢÁ’*“(đZ)pxÖ=šęŸÎüž_\ühmnΊ~LüŖ0=¨4ĐC8ėėėķâ0čĶXė/áöÄeŊ=‘ßãg^¨õw{"Ūo?oqĸ™2ŅÛŠŅ đo)AĸĀļĖ™<Ī/ĩĸÔâAOÔiü—ųĨæųwÕŗŠŋ;ŋÔÍ|b4Áz_pš1z-}’((đŒ(€{^đ“{^ĻæëųwôŠ×Ų™ÁlōõÁ“=<‹Tû’Ǐ=QįÙĻÃ_OAš›ģšžįTu÷™O§eČļ”ÜdŽõrOd]yņ€ėBņ›@ĩR‰)üRg2g<ŋÔv–QŗQ‰”Ũž(ŗ×ũáÂD™tųøT[j)čāĐCņōÆ]•& $ Ō@==9cbd (ĘŅâś Į°]QŦ>ĩ" ˇ_ž|Áįp ĨœyžžžĀ~Ú6pb¸Ŧeæz ąũ.›dā^ˆ…sîëĪoPĸŲ„lnÆŌ‘ÜšÜjĄ]¸‹)ãˇãę‰ E¯Ķ/Qā÷P`BļÔę5ũYO!Ē-ĩ6Aüę靊rgĄ ņdKĮÔOG§RĻEzЛīLזZ$Ģü_mæX§4Ąŋ_-5õãēgäo7ë%hō‚Koŗ°‹čá’ĩI´X¸%|_‰Rõ†Ę‹o‘!n7cÚʔ@Į 4>Á/Ž`Rž“ zünj—Ŗt–…4hK‡Ķqxļ… kâ/ģ@$!ŧôKø=˜-ĩÜ3 ŅĨē5mŠåß{üƒka8HĖũRKA/ “.á%ŋ§ŸS)‰‰SŖ@ˇ'YĪjūS˛Ĩžīuuí֝z#mbIœŌX‡ŋ1[j´ēo†6ŦÖ P§åIâ@ŋĻM3š…ŋJ”öɰņāˇČcHJ(¸;i$Æ,÷9´ZxōVŅÂļ"ņĢrũQ:ßâÍģNŋ˙ 5*ëŋebBxé—(đ{(0A[jа(Œ\ëļ˙’q-pŠ"+ŠOsėPj¤{ą6Гō1ō{ú9•’((05 ČT”úJ>Š=Ø'îw¯Û98?6ûîQO Ÿ&ė÷ÆlŠŅęJŧ (ė•ņ&ájŗÖęÚ*#qûo¤e˜;CIŨŨũŅ_|ͤŧŊžŽf•(5,˜‘'>ŠSĒ%´Üķ€xØc”Į""ŖņFšū0đ`=ņ¤ą~ũģ;d ÜÖÕą&‹ ņĘLd¸úūũÔÆ}Ę8Q H[Ņ9™cAŪ.Ž:°đĒz+ “„aŌĻ›/jM/aEŠá—‡ŽåPĸØ˙ ôÉĩ/ –čņãåcĖĀČS& $ ŧ` ÜÉ6ÜtīMÁŲ7”đ`BŠR pÕëąĨn8N*ņfčp‹€1 aī0戸.N⃊ĩĩõˆū")ÅaČÖV´ĪŽÄŗa] ØÖH BY[[€§åŋĘV VŅë_šū(W­Ąt\~Gîíī˙ķĪ?+đ÷ą¸u„0öŋ''ĐšyŌ~T¯|é—(đ{(VƒOōK­šˇž?„Õt´ĨVõšŽ>ÆSšôüRƒ ›˙=JIH˜ p6€Üķ2|š˜w金W´ĶŊ‰íČCŋ[ę† 3ĸŲ1>Ä<ũīŋ˙BŲ…žSÄī?āņ1†J øA'ö9”?÷o}§‘'rĀąÅXgä6?ŋ1æB[đ :lK_Ž?*ũXŊ—üˆ€42G)WW7ë )6 vúäV %ģųũ8 i›’% ĄĀDlŠÅ K”fQéMOlŠõ¨bkĐMšú´āÖɖZI ]’ŽM¤ą™(đâ)ŗR6šįEU’)ŲRĪéQ‘ąíš†m˛Ĩ6Ūz{ļÔã *ī+cŧ†|íÖ&`ŽL EœÖųÁˆw32‘‘.ķø!šxħgŪf8qdĸ@ĸĀ‹Ļîy¨ÃņÄŠÚR ^A—I´IĪ _éŒS1ŋx”Ú§qÛĢ“g˧ ĩOųvō-I9& $ ”(@鑗9ĸųrzāYCCĻŗc7ōVaEЉ›ˆ¯ĸÔâwOũRĢö-GiRQ‹Cô=u펆IۛX2Q QāeSāî^mŠgĻkKŨę̜ĨgĶ„R7ŖgˇšũĪč ļ&ģ'e|ų¤ų)ßNŖ-)ĪDDˆ9;™ŽÎ˜2ü1ŋԂRKJ•FōlŠŖŋųp^GŅląĨ–"BđFņétQj‰¯€‚RCĨžĸ-u~ũW;ƒå™ält6Ĩ!8ģÕėĒŖš_ōi˛{RĻÁœOAš›‹KĮYy\4¨Īáį͏xÅßku›››w:‹ĨWží |Z#n­áúÇ1˙æ$ŠW"+R>ũ^ŒŗŸfK-ČtDŠÕ7ĢØR2-'Ûŗúôļkǧ›÷7Uŋ>žJ-Jx3č <КŸ•OÉ×ä).ūyĶë^tīa@—PęÆ06|{ļÔQeˇčä÷xĀs3ÜnāF’n›7 øđ žĒÕuŧRą0|ĄˆķDčߗ—Wđc=†Jo uŊą‘új)ōŲųĀ1lŠÅ„ž>ˆg ĩ™ĄˆØOĢ=ˆÚhg§yp=ŲRŋZÖJ KxũčŪIĄQĢ…œJ>;Ķ=Épŋ{+žķ8Ąˆ“-uS ˙Ū -ĩÍëa78ŽC¨ÔđŧÁ?öq"¯bÁĖĢ^p“KyÄÂÚW¨ŧ{÷87TķųyŊcūĀŧqOĄ\ЏąņīķķsxÍC$î_°<ņ ø–x9ŽbAqɈåõ Į7ŲBĸÔO´ĨOҚƒ(5lЉŲĖĒ-ĩ>“-uŲvüM2]jtĸĀkĄŽWAQU4ρUãÆŦ9ؗ–­ždKŨ”œŨŪ -u´ĢˆØ0Ųh=r~~ë!Ãw¯đroāÜķŽųÃ÷32ÄĨ†+Ģ̏¨…ųœŸ}ŲŲAäūūŪŽ^?îŋşČp‚WēŧY’Úņ(0![jbĐĻ3›-uDkx>^BđLļÔ9?ܯ‹RŪ(ē=iø,wæĻ€O3·~wļ/GÁgíK?R@™ōUŲRÜ­{ÆAėČeæõļÔPvŽŽ˜ 3ođūĖŧ´´Ā4Ņ:æ@šz9žgggLöíßW×ÖX[[īöt„äņō………ëëëĮJNī^&dK aoÃFö?ĖlŠĢ–ĩ˛Ũ.ænOLļÔ/aR2 ô58m[깇ÖåÚŌí’^ҘP毰~zEļÔĘ랑/ëlŠž}‹j4l0üQÅēQ-l˜ņmÄŧcūĐËŖ;¯xS N=~úô V8é´đh-ŌBĻw­LO‰“ąĨ–ÛmĉūŦkRØR‹nYT›­á$ũRcXbãŠĶųģl“}uuĩ犃Éœ¨ĀŲ `‡Ž8wP7ėL­¯o" 32„=žqzzž˛˛Ģ/>D¨!Œ4(áųųEÄ#\gÃįã˙`ß§ĸžHĸÔs°Č˜Ļ-ukpŋ|)%5Dg’í<~$[ęjĮÔvzzú(˙Ãl&Ēř~>› oņ*úôœ촴("=^˂*?Z” QāQ`2ļÔÔĸ;īÔ\Ί-5įy2N˙Ņķ“ņKu°ĒÂū­áŠCúũûĨÃCÛęęõp¸Ų~ØM=::€XAš••e„}>8Ģqssķå˗-XƒiĨãˇČ‚f˙@>G`#ÍūūˆA< îƒ|AŦ’Ēš((P¤€ŲRÃŪ4mŠÍfŽ1:›lR@ɖēzØbj;<<¤ĩfn-—“ž˙ū:œhĖPęnm}–$xNb4|‰0üõë׈gc˛z͡ˇˇĐš“4Ix}˜”-ĩP&ģ[QũR{?;nŒ™ -5†č‘‰(ãÁ0ÕâYæÍÍ-8ũ =—ŗĢûņãjwH#6j1ĀĪüŠ˜tŦzLĩ{ssz3ž08CŧÆlJĖÆ†˜ģ=îĪäõqTjQĸĀĸn#ĮoÚļÔTåæ­toâ(PF|UļÔ ‡V%6\ø܉ūÔ5Į"&ŦxžĐ§„‘ôéų9cĘļԀŽāB9āIßÕø!ŸūÁyÅUWа ˆ Ķ"|ö! tŧÉÉȆMÉž&eK).@ÉīlŠŲTXÛ=æ“ôKíīĒ1L ž˙ŽĸąøV‹ąØo!r~QŦ2ú}äˆG$ęƒ'ĶpeúƇm Ķ,,/CF`)/Ģí™HŠNGÔn<ƒ4“’‡Îüŧȑ&wC>^H5IH‘}]kĪëÅĶđõÁ<ÕŽ˙4õt‘Pj‡R×ŪŅëīIđ˛:ŗų‘š$Šķ¸æßÂ5+3ÜdP1´[ėžŪÜüúæĖŦ}C cr¤Zs‹ųcZdxF+m˰ŋ~õ`{ T {ļpĀ'cd~ūōJ|ZcŽ„ĸĐüښ&TMiž &dKMÔĀP$ŋœ-ĩĩSU÷€Rë=æ“ôKmСĮĒąP†›žÅÅE iÕôgn¯Ž°ßtuy‰¨­-ņėƒxąįx€&ÍõwĪ€MČÎÎTj¤‘oõŊ|{{[m˜]8ÃHbfÜÛGî†|&|Ē‘((0č—õtmŠD€bĶ+ĄÔ#Q€úũRÁÉC>ļũí›9Č{zΘ—+áđ§įœrHøã˜-ĩiČîžŅö˙––nŽŽ¸1ĘîėŨúŧw¸ˇusĶåÚW?€†­æÕ*ũÆV‹ęęoņ Æa}/ipŠpQėē`ņüÖÖüž2ƒü\>˜Æ —Ģ˜ÅC@ŊÆ'XâÅP§3Ûëõa:S“#2fxģ°é•i`ĶŠ”b5Č ‰ž9'@xÍaāZ{íŖ\Úüeō 6cã’CŝŖĮ´Ûc§Ų~čļē÷§‹3wAKËī!åÛQËJ鯠ĀáŲíÉÕāüŗ8ĀāīââGģ=ãp*ŲA7Tz+Ø,ú°:ķŲŲ;8úvp;ŋ€RÃŖÔââō˙æ–~Ũ\uīäKžÃKÛģ{û;[ĐPq€‘ØH[ č­3‚ėŽîüũ7ā䡒që)ļĸ°&FA0ķB ~P Ųm^čĄ[cs ŗ ”`Ėßąøx{kk~aGLC÷EnÛÛ[ķķ HåûüôtgoâxëãÖūá!} č߸‘j_bomW4×ŖįŌ'‰‰–mÃÖÂlëŌîxVŦ:Ôi’áŨO­û‡“ÅÖüÄ&ąęô|œkëPŠī*įš°Ũ†$‡ösĮÆqcšũej*õŸåØTzĸ@ĸĀT)đõėö*õÖlÔũ`ėD˙6ҊCôh“áԍE{V}ø‡ ļ÷öŋ}ëõiõ!iF//¯˜Į~Ęwj2 ŋÔ9[j€€Ę‹.<Ą++Ú÷õŸÎ2ĄOķ24` ÔŒŲÛÛõļÔøđĶįĪ˙Ŋ}:4dŽÄ|BĖ߈÷ 33P˛qĀÆŲˆAi4æœč1&ŲRO•wS扖đœ)`§yܟĸ-5ĄévîĖxf%=QĀØãųĨūŗ ŸJOHNIÚRĢÔˇ˙œ_jAmėÜĸ8ôž1A[jąúž5āgšN< OÃîæĪaīxfcm ˜ĹOĪ끉öė,ÂHƒ§zŗĪėž‘Éå˙!gāĶ‚%?< æ?9“˜E´€7Â@ÜyhšžĢF ⃟dKÆ`ĸĀë¤@īAĐâšîu˙įĪŠÚRô*-ė ŽdIņ7ûŲ.ŲRŋÎá—Z•(đü(0I[ji]ļëų—Ü@îNÁGĶÚŦĻ­VęgCl?ž>;;ī<ÍsRuĢČįų1DĒQĸ@ĸ@C Ĩî îÛ§Į”u*ŲĻāŖĢũÁfl Ÿ‰T¨Ģį—`§N¨‡9(ÜĸАFMÖÜkG9į§|;j=SúDD1(@”:“9á&N˛ĮHí—Õ6a˜gķ†7c)ÅĻÃîtQŋÔ8‹ŖČĄÔÅ W—K΂Ж§ß`xŒKŸ$ $ < Ā ě\ķŧZ[OÅīĮ}˙nŲ8Š`{÷fąįæč;'ˇęų%ø|•ųȧq¸Đ4¸ė)ÎãšÛé¨[G÷ƒ:§Đ0\üûīÎįΟüUäØVÅ,ļUé4ļÉ-å…Ė›_…XNcⴚŲú4Ŗ-|˛ztįW÷ķßŌ´áސWLĀK7ž-ĶđŅŦbéČĘųjxôģ”āõPĀîc‰:mū&–0GČTĄĸž§EÁ–§YpĀFôСØĩsĶíö_ræ]~jE-ˆĩjØbK­ą:!LŸž"–MąĻÂÎOAš›kķēkÁ÷ãc\ ģGxŒ^YYÅI}ž„† õ?õN-7Ë) ņVÅGŋ+§Œ1§§v#›Oƒ0|`ã‡'œ§VŦyeYū[wÅÛŖõjAYĮéÄæķRüg—iøh.ątl•AÆGķO ž?LíÍl.xõvæa:‡Rg÷#Ę CŠÕžƒ¨6PjbĐČAPjhŠRķŠv!Ųí‰æ×ŲđiZŧĢ~3áįĪ!І‰‰Õ°‰k Ø:ˇ{¸ĀB%ߤąęÁCOPęl“ĐNˆ'”úQ hˇÕÍ)ļƒĒķ‘Oc÷'L‰é›#Íå 4˙–h–˙ũ{rŋŽÔųāä*jįp ũ7 ã-Î ášC~ˆ#F˙üķĐkÍ÷ÚüŪî.cøļâejĘķ„5_ÅH‚ßpP!Û2vcFãC`ÈQ`Zū,¤GUƒ¯ÛŦzp0hšümȑ ęP¨‰'ZĢ*\IƒrĄžãÎGqRļįį¸ũ1Bø‘8YésZs­:Î_˜Í—@Sbļ”íī§€NŠ7›K×<å˛Ld:›*ԇGø„Ÿã?GéŧæĸŽôē˛Ū†h|ģ­$;{y2ž-5}}ˆâlKÎ`K |Zu<›\?Û]‹oÆfē֞īí˛^jyĸĀ §dÄ'Š|Ē ĘNĢÖ3áæË˙Qëa¤Mi"”ŋ’-ĩ 3 ̀iQ<æ2î;Ģˆëëkīß/Ãŧ!ŗxîtĘ*xWfnĀ ĄwĒیŸ)˛ClŠŖ.\™2(ūíÛADÁŅ–……%ö8ļ8ü[ԄuÆŗŌ9  ˛ĩwĖs.ŧ•Í(ĩđÆĨãŦ¤•’úš×•õÂe^Ēū# üt[jĖ'’ODŠÕ–ZvjåO+j.õ’-ĩˇŗIš((đB)Đkmöî?Í´;ęáîáž+›ãˇĨ†J š :ĄÔŖR€“[˛Ĩ`Ŋ3bXM@ôà Á¸ ö ĐøüéāĪÕÕ5 ­ø0"ž4†î ›.2âĖ^Ã\„ŲŪ^_WĻ,¤70Ž…ģ——ŖŪ\°Ĩ>::ŪŨ57¨×0īū-’Ņ\Oŋœˆtf}yYWŸĩõu^ˇŒgüvbJö)0[j5‘æDhĐn˙›277Ww}ú‘}ŅÎl{ķĶįŖũ››.FBđŲŽGyĪ 3ž™0NxŧDĻIuNHø~qąõsuëápuąŊÚŊnŨ<ÜŽĖ/líĢÕoŸĖŗ÷õSįæĄģØę.ŦĒV-ØDz6ĄĀúĮíģ;XņVĖ)øútžÅÅyefKŗö­ˇšÜŪYˇÃjįs”ØÜ^Ąô§|;ņ†ŧ˛ a m{R32Pv`˙8@ųʨ”šķ(ĪnOŽįŸ;AÅaâí6Œ6feFtš0=čʑKV‡ŊƒãoōG°úx×n-./˙e6%AŸ†V­Č ŧ|¨ō­ŗB˛Ĩ~´‡R‚DDgHžĸbDŽīÕÖš­2qâø4ä <ĄÔŖR€ēr_Š×ė—úŽ—g^%¨ŋ0ԞT%aaŨĄL*Δ΋ ĀDlŠí^*āök˙e'3īz2÷ˆ„Ķj#Ĩ?’_ęÁ)Š’‰‰ž"ÎāōCõ]‘j“löK žj(*1ĸ׋ˇœ^:äMÚR§‘:*0L`R2ęWuéxŦĀ'•sĘį™S`2ļÔ´—Ö}NûÁ–Úüøģ)$ųĨö>PŸ9¤ę% $ TRāž¯Ņ¸7QčŠTģ›ę‰ÛR‹?\ĪņQ‘Úˇœ^:åMÚR§1›((đG(0)[jnwrĪS~đK\9ōOÆĘ”üR{Ÿ…¤×SĄ‰‰OĨ€šĒŠw¯(5ŧ~: Ä›TXąŠ6øˇŒ7×víâWå—úŠL›žOH˜&&◚2?Î#Z_AŠÅ $ŠųG˛ĨÎŲöMŗoSŪ‰‰SŖ@ŋ¯ēŗÚRÛåi÷ôm†p #Sq›|NDíüdK=ĩ12NHČS`RļÔ"íÃlĸ%Pę`ú!Žī“-5áúôKHx´åŌÜü ŽA߉LÈā&ʼná͆së?úHļÔŖQ@Ø*ŲRŋĀÁ•Ēœ(đB)0[jJ{įÉQüR›GIUÚ#V-"NI•üR'ŋÔ/tˤj' €÷”l‚ĪĖĖĩ [?< ē¸gŽōmBXu_=ų‹-5}į%ŋ#P@zâMÚRw:vÉ_ĒpĀÉđ͌ ´á0Î߀ođÍW¸“Ĩî~ō!ŖžōJÂĘôå”1ų‰O#^´ÕŊ6žŦ^đŊ[]˙-ũC7üapŅįôΟ?á‰[iU¤áŖYÅŌ‘UŲ÷ŖŸ§¯€“ąĨŽ3H¤H´ĨfLfJlŠsļ}¯€…RŪ"ô3*7°ĀīĮŦŪ;ĶŊŊœŦ-õŊÜrū wĐļgJ=*”/ßĸ-ĩųpãōû÷ī;ÛÛОonnp7Jŧ…*ÔGüp ^Á-.n,u<7÷nQNcNO˜ļš9Ŧ—™ë­æ÷¸x÷nooŠžĪ˙Į˰Is ŦķŠGŦ../¯đm™†æKyĮ ãŖų§Ο“ŗĨÎ4gmĩŗĨ–ŋW´Ĩ֛ĐÍÖím†Ÿ?‡¤& $ TP ‡ ŦôGäXPj@É÷Ũ‰á͊s÷{¸ŠDäįL˛ĨĄ×ūĄ-uy~)ûĨfšâ}“åūąīyA5šk^\Õq­āęÚt>üp"ŽäK¸O†ū÷ņãGåä6nGJžÂæ¸=QnU\YČ3ÛÛŨ`Œ ĄĻ3üđĸFâĢIđw˜GČvČí‰Ŗņ!°a¤áŊåž@UcÍcõp 8 åōˇ3F†¨CĄ&ž[Ņ:Üûƒ{^P.ÔwÜÆˆo# Ûķķ͍áW”>§5×ĒcŅbzĸM–‹RnĪ–ΖÚä‰ uŪ!’œŌôæˇ2åěüy0_”üáW˛ĨÖ+o3[ęŪ„N[7š<ņ텟-_¤Š% $ Ĩ°iyoˇ$šŨÛ,-NԖzp¯Gyø;ŲRH!]ŨüĸwŲ|äĶØ9Ÿiq˙SÔŦæß癏ĸ!GGGŅ(ĒaŨŊŲxĩŗŗô€kŧˆûëׯ0žĘ\]]1wPRđī_ŋ,ĻD€ā7påx ø”šu-Č´Ķ÷ĖŲ7UŠëŠX=džŗ#ĐuŨˇ¨ žBMđŒÕķŲâf(͒ÃŊ–ŽÅG"~ŽûĮã.įgg_”8ûû{€˙ ßâOdˆl§ÅF)ßįJĖ–š:­˜žŲ !?ķTLôéAHÆd‘&ÅM‹š2~ļ´`Km‘úī.W ]oO$~@‹ˇÃ÷ƒÁÁū>LĀĘߞœœŦč {õÑD33āūu]4ã‰0Ę*Ådu8=?ĮŌ|~qĪ˟?‘?JÁˇņ‰˜û~mm iÖ×ÖFL¯ßG1ˆGøņv=WļHõJHNķōPę{ę֐nĩĨ~čK9"…g’-õVÔ´;—_Ũœĸ÷ēÛ|äĶ䐥É‚æHsšl|K­ēî?É+ĸYN˜Á€ĄEē#ÚĘvâ:ALdxčúöö–iNOO ‚CĻõKPČ7Ūí†n͔Pyų9æP]›m´âĐĤ™>ÖŲ#Ų÷EŊ1ĸŽzjŦJévm8ž5ᲨyŦ‰o2@îĨ%ģ‚ž\Ÿ¸ę@&gggüđ›ĸūŦ­­wĄNčĪ—ž°°p}}]IØųŠ)`R%ę´˛¨Īx#ĶĨÖ2‚ˈŠPę¨!{2eļÔΌZƒØ.5Čy=ŸÆŽššœÆ†Ŧ¯°DÆR•=:8ĀØFĖĘō2ÂPí!VV–%feY¤ƒÃ* ŗūũ÷Öâ_žėlmosĩŧ÷럈Ų?8€ŒC,^FŒd(1WˆGøqÜũsVjZĸĀĢĻU6¨# ´áROÎ)MnRX5\ˆ¨ļĄčňmJ/ Xˇ˙‰XCŠ1•š=ŌpōgJĖÛi.WßR#¯{ÆOęė€eōظĒY$C¯­;3kdÄäE<ĸÅ@pË*¸Į’cöĪP+iŅSû%ō°ŲF+M\9čŖVũBn1ũúú&`īøēûįΟ Ā ÅéÁĒíkÂ0jNđģđøÕÖŖŦaĄ>ø„‘xÆĪQ:ˆ3¤ôē˛ĻÄW)ÛgB o6_CDŠMڈՇGŠ>8ŒYę™r*'øĨvĘ{ü IˆŲRk*ĸã¸Cqk°5Ęå|ŋŧŒu§´D*:ĶúqqE*onm!ŒęCBmnrņēĨHv–ĪŅŅÁü<—­mŦiãRĀÂåķ dŽ-„ņ6Ä´…īã¸û3á‚TDDQ(Q&§ ˜Ŗ-õŊ¨ĶėęM֖ēĨ€œPŨ’˜2ö-?ĨKëö?‰Ņ•OķJmŠ#wcBôɀ™NOO+y}}íũûĨŗŗsháQg…z]VÁ+ąa`И UoÆĪYŸ2hŽĨ•¨Ė /1§ûvQpĖæ K˜sŠE=ēŽ&Ŧ3ž•ÎI0‰Į3í<Ôg.ŧÅĢø9JĮYI+=īK¯+k1“Ōž< ›Ũų ĸÔŖÛRk>΂KlŠŠ‘fDҘ¸Ë#š8XeGŒy˰ĸԕßâŧ–’€Ģ!8ÁâX8"%ž#}!†X…·^{ˆ@ã+,Iįį%CŒjÄ`ßy÷éįįß!l1ķīâ™Ļœ§Ī˙åņKĒqĸ@ĸ@ĢuÛë &ũП3”›e‚OÜ?B…ß“íĒ͏W´ØVô‚¸xz>JÎm•rXW69d˛GI'ÎėODŠÖĮÍÄöĻ-†‹-ĩĸŨøÁ øđđö ˜øüé_áĪÕÕ5L|°?Ž4Ą‰02Á”GĘįØ0ĖE˜ííõueĘ¡ąÎ˛ųŦG }qttŧģknūP–Āŧ{øˇHFs<ųIá÷ūũûëp^3CŠCĸĩõuP á?Ī•Š÷5‡Š tî†ũ•’Ŋ ˜šY<8ļ-ĩŦ@˜˛_jĖ:úø¯Zˆ-ĩJ4[ę1Ã&7+ōQˍ/r"!,4ã™nš–(E<åmút–Ν_70ŲŌÜŪâ0†ü°™…]3Ũ*ąZg‘)ŖQx0†øG]ģ^ Ĩ†$ ŧ) `ĘW”ú!bĀPĐhĶ6čŨŠ|›Œ_j~¨ŋŒ§ĨôĒJ‡{m­9°˙ēsŸ ;1%{‰Č´Jķˆ?ļ-5‡V@ĨÛí˙aSVÅđĒŗíÖæ§ŨÃ/Û7Ũ.F‚ųlĪôNUN3œ¸iCEŽÖ|‹'ŦĄ[C:āhE§3ßëv7ā{ōŋ˙p~ņĸD-Ŋ666˙ûī?Z?û:⏠nŸ?šŖRŠ‹5(.tfgq‹WœE` QpÆüŖē-dbįŨjAØE*Ĩ˜D 2Câ„gË Líũ­•ûÛÕā+{3­í‡ûVˇuą¸x+æpm¨Û{_v ļÜBxF•âŖJ÷ãi>ļģsW÷÷‹3×ķKˇ78"f"viy ˛[cLXZ~bRšéŗūqûxg՜‚Ū€Û4č|‹ķķBÁfí[osšŊŗn‡Õ&>ŠG{č1r~ʡc÷Ļ>Šmg.'Ōj¨]˛=ž~oŒ‡gˇ'WƒķĪĶũä0ņXĶX?ėaÚļ#gŽšd…bŧŗwpü÷õh΋ËË˙ƒ-2PŪģžė¯q— oˇ>īî‡ķXāũû{Ų• 8n´ģ ZÛ4æ°Ļ*¤Œ´mœnž¸¸Ļ-û×0N›ˇ* &p@āÍ?’’ΧjA4‘zĻL&N¯gˇ§WƒĶ-Ø›‹kƒĖÍšjĪ€[ķ’œĮũ>8đūá[Û;û''ßü cUŠWūĸSœ¨OÛÎ]hpÛtlŠ?|ø2Ę>äâ&$hÕØ:‹CF ÖúЏ‘1?~œrFŒlč8[jhü0˙> }ú1d.c1ôæL~ü&%13F`“lŠ'β)ÃD?NJ3˜N{›æÖŦœ×ží÷(1UúQ`ŽĻ¤Ėûu˛ĸ… ÜÛ´ĨūãäÅUyRú4Ú|-éĶ/Ž&RáIŲRŗ2Ų!ØRc¯Qy8&›°-5 įh¯ ŸĶëëą>€ŨØ{œ`ƒ 7ė1€UCũĨmŌā‰ãĖHƒø6|s:ģgØrÁŪō€OÆ@ūČXczˆ8@āÃÃ= Ņ4fVcē3›lŠ'ÂŖ)“DįFœž€x›ĶķŅĘš'g+Zp€¯roBļÔ¸I Å ™~뤟ŠOļ­t{Ûȓ—‘) ´ĢņUōKmã|6 žũ=?ĻQķ”gĸ@ĸĀŖāîDæ;Î<~˜!gK­~rjüRįö6͈PPj)!] ˆËbÅqĮô2ÔĪÆ3ĘķŅūI ž ß* Á–!Į]ŀ¸ë=´Å0ԟž=?ÜęķÛä—z„^hW7”üRÛŧđ*üR?ŋ“j”(đ&(@8ķķfˇ'Žč—ÚÍÁ5PjÍ:ŗ#$JM…:Ü3ŋÔ/“.ÕķM°XjdĸĀĢŖ€8đĐģŽ=J KŊųjfĐë=Å~:÷­’nPęąüp‡š­â~ž˜'cž>ŋÔ¯nĀĨ% ŧ LŪ–šJēØR[ îÖ)ZŖw3j‚ÉųĨ~)˜tŠž/ƒGR-ō[jt(ugfĄÕ™ÁM‡÷ŨÛIŲRŸFɸz.ųĨƒ ŨÛ´ĨîtėŽ Čļ08ÃdxĮúûīîņö7 âđÎá8¯6ĢģŸ|ˆ ¨ŧ’°2=Râ^áÂ+øˆ9ĐŠķīųađŌĪôđB›ˇŽyĩQnŲEwķĪSĘgKŠÚR‹žîíEˆROՖzÂļÎĶÆŋŸ-_¤Š% $ ÔS ¯[nPŠ=JMķj ÔũŪíälŠåäLÛîhLÕ#Q@:đMÚR{\äâīĮĮ[ÛÛ8‹˙ëW¤Ā,ãád %~8yˇđ”ŋUŖũæŪ-’úß÷īg1xĩôąĶcũĀk‡ÚŧuÍk‹nūIJų"(0E[jšVÍ]Ŧe¯T Ct'ŲRŋ.I•LHđ¸oÍé ’J­˛{p°ĨžŠũtüö6Ų°ĪžO"c`´ŖúĮx}éĨËŪ¤-uų„åŋ''đ‚Åų—3ÄS’p‹‹ƒxŧÅ-⸀Ŧ×X¸MnU\Yņ§*÷vwgÃWė÷īߙœƒđĸFâĢIđw˜GD\§‚ËēãhBzčņ1AÄ×Q"Ęşxōbsü€pk¤T)Fâ[´š™äSžŸÛį~#¸üâŨ.‡Æ}“ĐøŅ 4MF?īĄdKËe×˙đĪ?ø߯úg)]ķ‘ڋzĸ\”ū”ŗĒI ?O LЖšžĢ3[ęJŋԑ “ôK=m,yjų?OžHĩJHNĸÔâŠ#oãÛ×=8\Š8[ę~¯Û†áÔÂvg$tļPĢ7û­ôЃÜ&VÆĒ_ˇ-ĩ9 ¨abh´¸d<ęÍu÷fCųÃŽ@¯[Į‹¸ŋ~ũ:×éΆ:´Ėüëāŧℑü"¯GJ‡ëŌbÕ †"&"Á_Ž‹!zŊ{<ŖęÉoyS[ŒDæ,hs—Ŧí3į­­Í˜ōü\”ãÂåB׏õgtëßŨņę ĸõ¨ęÖÃ]n÷0ŋ‡Ķå˛ĐRėāž9Ŋ°Ãʉ)ņ*6_ōī÷ONN‘Ĩcë Ļ‹RôKĨĀmŠÍK^´ĨÎPęV› >Pb˛ĨļûÉ_*פz' ŧm ˆĮ‚ Č.Ļ_ÄĀãŨDlŠ÷=AŠUH'”z €n¯Ė–šzdŨ3Ęė†ˆŌ8kŠŠŧoĒ'4EĀĨ•Ŗ÷iØÆ+@×W>==Ą œz§iĸƒÁ—/_Ú Ũš‘ĐČų9e]㞨ŋ08Á+d ôYE”:âë(7K Ųû÷īãÍŪDv ‘€ĢQCä†âNO žmKJč¯ūsßLĀØKKví|Vzģĩķå ŅzT2ļ.~XY bKˇwvbũcJ`đPGŋ,-ŲŜ\”ĖĘßļX} ­Ÿˆ-uЖåFûÁ/ĩ %­V÷~Đíõīä9čë$”lŠŊĪÂ×ĀDŠ ‰oŒ÷ļWô”|[j|ąĨ~č÷`™ •zv.ŲR‹Ũā¨čģpeŨŖyüĐųȧ‰­Ķaé§ėõã[čg¨WŨ3VšlKÍWP1Ą6†ĸÉh´uįĄšâ╕eØTD¨mY÷ÖÆ1|ß[XX@ūĒC›žÍˇ¨ĩdÉ;j՝ĄÔĄ h,Ôũõõ5Ÿ\__3ē)T蝏#ųúڎ9"jˆĘœl)ĪĪqÁ3P9~îģÛíö,cĘ9"žŲŅT–…Črũ}J"ܚg 4‰…ŠákúŊ. LĖģ¸ƒˇÛÜ÷ņ4Ūųk4ąwƒ^ĐŊģĮρxŠž[ՖZwåÄē{so3üē˜)ĩ&QāP€õŒbŌ=…ķ<‰ä ûr˛+g€öČ÷)ļ)dŌ}}VÎŋ§EŌKuķK8á#ķ‘Oķ*üR—mŠA @°DMH•?@ȧ§b‡PūA}˙~  ¯uļΝ˛ ߒW™ÕÜ\ˆ5´I|EÜô} M3ęƒ ŠÚzœ˜)Ą¸Ã^âōō Æ'¨6#g‡aÆö6"ˇBAs,…O—r_Sno…Ī}lÅBË9RFúÖAk/—…Č0ŪÍâK›_‘Ō÷ Č(&Ié÷ē(@öĪtÚąüRCč÷z},ÄIßScnĩ˙’ķˆv‹˜Î X—Ę,ãtyši ģ}[á×ÅKŠ5‰o‚šŗ"ŗæ ¸)Đkœ&YG+ˇ§ŨĄØú/׊|ŅϝtnĢžST§§†Ķöi´×*5ŌÉđöQꆕˆÛÅ1=L/`ō âhxĀW_vv`—LË(y|ūô‰¯đįęęT[|i `dęÃĻģŒÅbJØN0ÛÛëërJäE9šqWÚR“VĐMc‡ úā&›†‚čEhttWâS–ЁÄĀé¯// õ÷)cxyy9ZŗT–M;@ɸ?P™ŌįÅjØ+hØ­)Ųķ§y5괐@ļnŌ´Ũn×=7žx‡ĸ~…Ģr-ŪR˛ÍđK-逎-rM‘ijÛæũCNĮŋ]|Zq‘įĪІ‰‰ tģw"4úŗ%”ē5ƒÉUČuaŗ1>>Íoû’}ŧ߃鞞R¤›jö?šˇĀųȧą jjLO›ņ~ÍŋE렉ŌO}Pā¸4TXĮxÖa~aáDâFcyiöƒ…ôūŪ>Œ. s33§ F *„;L÷™Ž1]`ĖČMqlmŚĮˇĒLh­4ˇâjŸ@•GåRˆ•~kkĢøyzŽÎø z6"‘gŦ>ˇ”[YJOv$>uÎIøĘ/Ļb.P ũĶwueYhéÉÉ1<~ĀÄ%Ö?ĻÜvĩōųcsāŖ!ũ^œ-5u8>ųŪDÁNĄJcwOK^~ü{Z$(uÍūgŲ/u”ÉņļŽiđjsßŌåŌ›K_ÔūUž•q„î“á<Î߀ˆ{Lāw™¯āŧĸî~ō!Ԁ¯ë†´BJŪÕâĮĮG1ēū=?0!<ŲĄŦá…6o]ķjŖÜčŠģųW)åķ§@´Ĩōd<ŋÔԓs~Š˙âZ_ŸíŲöŽͰšÜ%ŲR?I5LHđxPŖŗtŌ_öÅ1÷N­áfŨîSlŠMŸƒĨ/ I?ÆĄēémÚR—WßŋßŲŪ†ö|ssßÉņÚB8H†B‰N×á´öxaķQß×GĘxEKĖ˙û÷ŗ˜Ã?š—ûĔX?đZĮá…6o]ķúÄĸ›’Rž LƖ:ØX›­-˙Ë Õ w0š¯ëdKPę1@R%ЏS[j=YRá1zŽÕĄÛ`đpßŊ}Š-õ]÷–Ī´åÅʲRüp õjÎęŧn[jîûnY][“ÅYģ/Ã]ÜpúõúãĮHŒWp^īK8ܞ(ˇ*ŽŦx{•ŊŨ]āŲ¸@j:‹@/ ŧ¨‘ø*FüÆŨã‘EWĢā.îX7¤‡D|%ĸ\ü‰'/6ĮˇFJ•b$žeA››YA>åųš}î ‚üq×:īy‰84ƏV l2*€üy%[Z. ¸ū‡ūÁ'ø6Ö?K随|Đ^Ôåĸô§X%šü<)0[jŊO1î¤ņđPjũ—wÆā…Š• ‚U î2M[jŗŊģ ŋë ËíĀ*Āņēĩõˇ>ū†ŲÅ ß_ßÜÄøY_ßDØã§gg*MūÆōũâįO éū[„Ķë÷utũ'ÂĶ“˜ųÅE‰éIĖpņįÉŠV‰‰C) 'CęPjŧhÃē5č J=ļ-uë^¯Īš…Y6.>ŖM_~oĶ–š‹žēn$‰ĻĐđ"b]HW¸Ķč5pkHā÷õëW\q<ę nGf$8Mđī_ŋ,ĻD€ā÷öö6Ü0Į”(ˆeA ELD‚#ž÷đđ°×ģĮ3ĒžüyúHd΂67ˇ­ \ĀSžŸ‹r\øĄ\čúąV €nũģ;´ + ĸõ¨ęÖģ—ūŦ, -ŨÚŪūõë´ˆX˙˜¯bķ%˙~˙ääYĄôīĮĮC;*Ŋ|y˜˜-ĩXGÛí‰ĘšEŠÃxãŋâZīß`K!‘X‹C.ÜÜ\ņ(`\˙ëWO˙AÄūÁģ?x"ėņŒ‹ËKÜĢ„d+´{Žß"Ä`ÔAeG$žĮˆ›3܇ÉËã—TãD7O^7<¨ąĨ:õ BNĐ-•ãaÕ÷=Aō¨‘'4z< €€¯Ė–šĘeŨ3M¯JCP.ĐVØ.G%š"āŌĘ1ëũÖÖÖđ ĐuĕOOO¨g×š w€|#%ĐnčÖĖ 3/?ĮôęĄk^Ų ƒŧBŠ(uÄ×QĻo${˙ū}ŧhČn!p5jˆÜPÜé‰ŌŗmI ũÕî› {iÉŽĪJoˇvž|!Zī÷FĪ•eĄ ļēBŦL >ęč—ĨĨ%V uo^šž6LȖ:w{ĸШŨ2[jĖėK~Ŗ-5¸Ģ@×ÃÃ#ģ=‹cPa iđDØãĒ.¯°æ›éŖāŸEp}'Âx[ŽŠÃHâ^¯ĄR{^;pAĸJ†jŋԐPd[Ž/į-åč~?æôz,9Û=Žŋ‹„Rƒ€¯É–Íá Åēgyu',Ą&*l ŗęm&ÆôWwš+2ŽŦ,C(2PÛ˛ cøūžT ųĢmz6ßĸtjɒQ•¨! ņuLĻP÷×××°]|}}ÍÚB7…ĘũõÃDš>Šd¨!W €“-åųų`pBĀŽŸ{ÉŒ[čšŌŧīôėĖŽĻ˛,Df({(ç$Â-­´â]0ø$š(đ=ō:Â˙ĪŪ˙„ļ˛mkž`ė]įAîŠqÁjÜ-Č2Á‚ē`dà nà ^‚ŨH°¯@̧U`7 –›2ŧ†Ũ(Xn$ld‚ •`7ŦFÁrÂkȍ V#Áj\XzdČ ę€õā4ę7战ëŸe[˛%kÄY'v84˙ň3žøæ7ĮœŽ–Ú9ĨãSÜĖCgXęäč“(=Üâ‰ĸúpž%ņ@f‹šĪ_`13"'>vAÄtĮéUdĻãđZėü“Ā€IK=š‡&Ų,Í7¤r^LĸĨvl7Û÷Ŋ=D’ĒLäqđíëWũ‰?766ļŧ@= ËØ/*d EŌpúöômķĮpOZlŗŅLI9e/ãĒĨÖX}ņ”Ŗ1ø Æ|T¤QD`Ŗ}¸’dĘAkAH#žŖ9Č1‹Ąâ xã{5ËĐēŧ´Kz"qhĘdK$ëë“ßSKš˜š–:~ŋDWŨëũ–˯!bŽG>r>— x8+åŨVģÍĮ1ZūŽÆĨVÅÛT÷kÅĸ~ˆķđƒ­Eũ—Ī)f&s†4ˆĸfFÂæÖØ`eÅļö-ôg>.žž^äVVZwwô?ūÔiČŅ™­­ŸŽs]$æœFō(zwą3f uķ„9ôđíA¯œ ē••“t&Ũ¸aHšņ7ø‚0ŋš§?­×™\PVˇ‚ĒĶâÆŅņ1ŋē4 y‹Ú˛"gdĨqÎëü`švåkĻußZ j÷-yh]nΡāvK3hŸÍ­ō]Ģ5´ΤRa:Í;"—ÍĘŨ‰ßÛÕîöjđ}'ŧ.Ä[|°‘.NE4"Œƒ@āOyš`•ā~ Ÿ+•##AĸŽŽcĸ‚pŦŌŽ‹ŗ3Ļ%27–}åā ~sŖį­ŧšČÎÛ\C…(wĢÍHƒã‰Oü%Ĩʝũ¯¯3‘éļ/ę oPãíV3—ËĶ$ÕēđžĻåÜ\.‹žXŦ¯xt_Eqö÷÷ø$J™4Aá­rpß*ėæ uŦqģICCëâZЎ¹ĶNt,Ú~Ÿ’`@Ŋüdų˜ËäąÆÔ—­Ų‡gķÛāŧ”ް_*…Ûh¸¯âPž%f"6‡étîŅ)•˜Ų÷ŖŲę|Zi˜b.…ū–_[ŋšŠ7î”'`ß[͆{ûû8>Š'é…XûĄJe]“| ĮLLĻ 9ú…SFļÎĪņārŠ„xƒŒîƒ3<įĢĢšŌN‰÷˛ŅÚhl^uô|ūęŗĮŸĪœ“†ë¤Oņ3‹÷öĘĢĢÉ3•ņ×ĸ|l3 ˜Åæ]ąļ’ëÜÎ}ĸŒÂ9í˛ėģŊî&“ oî{_ÂŨ¸÷Ô>tŌ}Pų´:­BĐĘ}ÉÉZėøI líČģfč;…"¨Ņŗįō2GͧŲüŅŪ-„{[ŅÄĩŠģ%oCQSˇęË ņķԟâ[‡ÔɗOļđüđņ ŸLo ÅĮÍĶ›^í[:ž/wĪ#Ī”˜(ö]4ãüņkĀéžTOhŨ;8ŠUZm…Ķ2ūŗ‘ V …HKÍ 1DŦĨöĒ#ÅĶ:67ícā/hXBEžž VvķŽŋ~û? žæÛ”3îĶö }4göŋ}KļAũ bÆ fÚéÎėFgD]-Œ;¸ œ4āuŌkčņ™'ŽkQŧÄÚi0 ¨ZíXŠ9ÂĮĮÔûBėGÄÖÍ+´Ô2JĖËųÅJbUv.ķ^°ōˆ÷‹‚ˆ’iȒÔÎNŨí OOŨ¤/.)’ÄõzqvŸ Ą a’]Õ2AĶļfí=<ĻĨr8^ Ņ­).i\Ī/Ģ+pŪMWMõÜ¤ÃĄqŠ“ZjIiŠ]7/ŧ‹rIcÚĮĖ|/Ö/§üŨŨ-´|A:ԛã ”§§§LU„ÆÎæäŒoÃ4õúOôđĶ|ÅrŪi¸3į+šmfŒcMĪSä 1û$;ķÄu}0g˛Ë1 |x ´ī$ŽG&č:æx¤Ę™Õ^0…ÎZz™–ZzOÉlZęqv~â.øˆīe¸Å]ũ,šxæOR#ŸI‡‡@¯$y˛Lhž™ēŲ“ °3˛€ŸĄ§x˜Í'ÔØ>އ9ãa~•zĸ‰˛ž_ĸvē¸ÔZ›Ž,ĩ†iu#¤ŗá§gÁy΍ĖŨT+Ö,`˜‘ēĐĶÂRŗĮˇ”špQ;^—Z8Š“Ļ7.9Ķü–]ŪR#Æ?#ŽČŨždwbč|ļé8”ąÔĶąã<•ŨöķgļÎÎĮț§Z[ŪÎ1îôĀdUjčųø]ŗÔ˛ž‹;–ųîŽĨ&ʅeå%‡GäQ\jũ;Ú+ņ’@ëOō¸3â°į¤ŪˇģĪV“YĀ,0 ĐŨҁA&ŒįG‰ŖG_' y?“ĢnĩOîT¸ōd\‹ņ-Yæ_šŖÆ?åƒ'ļ'ĶDį§á*CË0–zfĻĩ‚Íīo˜ĨŽôô@ũFcbō%õi´×wÍ –ZĶ(~ßĢÕŽ<÷ÜמžB0ûØ|ƒŸ}ßÉŧK|Сĩĩĩfŗ9nm0 ,Žŧ–:ęO^—z"-ĩ‹Ã'$ũÖPū`¯đ!Ī/ŽëX˧oĀô—/,JÄ2\Ķ/ÜJœ†:ŽĨ‘ķSkēU‰{`:Øč-uŗÕ¸î^õŌí^Ļ×jWWƒöZûĸUmļZ ûd-OꉗšŊáCĮBŨqŗéŨ>™fF,5šæ¤ZÛĻ1õ’{ī¤č: ‡ú,-LjЁÔđ͚rđk ī!yuT‘DØJŌÛf0 ŧØī ĨÖgX8’üÁ(^áCžņŗŒĐ•Š ôđbģ¸Œ1šËÜuŧ8ųI–ˇ Iíf†…ŽōĶr|qvÔ)æĘavÛášZ­ÃnÖī˛ Q\k”:›{˛cŠĮX@=hčX¨ģ#Ņ:ž}ifÄRŖ{õsŦÖļ)ģœÜ{¯ûI‡}BŸ–šĨ­GoÉø˜ĻN^¸<‰°•ΡÍ,`xąLKũžzîß6Ëø-EŨnųŧ°Ô j›€?÷¸ëĐIx/¸úIö—€ĖP$}ˇ%JA~­fīöļzpšÍÜy%¯Ķî5ˆ—§’9÷=u}Ž {ƒ!ų•0=A-ÆRą€`åcĄî[%Zš2™ÆŨ…šĐR‹ÅķķķĄÁ˜æōų¤~ƒcÎ$ IæÍåsFCåĀVū›ŋ.ĮZ´`x#-ĩŽ/]>œXB]õ‚9‹5wĻ`HÍÛÛBŦ1ߨ۝i…3*üŽė_ģÁEkF5ŧ[ą‡Šs)ē˛Ė“üq*•#Žéīš­Ē™Ž]žkÖ­°Ũę„kÂaˇkõ;VÛ IILë\ŪčWZ@Œŋ°ZęīßŋķŽ Šō9øúõĢēū¨ l|‘‘ <Í1ķ“L2oŠTfz"\8ė{ˇGË*6 | LKKÍ4ų´÷˛­d\jÁĘBքnõDĮ˜–:æN>„ŊÅEĖq4Ķ uĒĘ`m-H§{ëëB¯ā¨fڊWŪęåZp|\4ƒíŗāËY ĖîĮؚŠ„÷PV`Aû1n]Ãt˜V–ē×ēšQˆ›_ē‘ĒŸ×›õV/'VÉÜ^G"¨qvč&{­ —Ie~ĨÔëæDKũÜG —Ë‚ƒ-›Í Ģ譏Žd>kôQAööößHˇ eÍņææf2m’Ĩ&1˛6’itÛĖf[`ZZjmČ &c…‰ÕÁĶĒŅr,ĩhåĩdZjŽMæļW­āĶI9 ÕāZ†Đ§ļQÚj5Č'ŅāįĢJ~vj×p‰bQžŽí/B¯)Č^œ đŧ}ԚAi-øąäŌ÷nãô…¨ļ{ˇdŽåßöEp5Õ[˙2ŖļÚ]2f p'´€°ã÷,'NúLO\#”0g÷A!UX ÚÅb'[hķgVīÔ3ÍAŨŨņÕLĢ=Qų“´a™Ķč]ž-ĩ6Fg"N¸…íGģŨžŊũ•LŖĮSøøįΟHˇŲûH >}_^‚ŠÜ¸mTt‘ ›jÉĖfyw;|›čsîUŖá^7ĩÁÍâx4—ƒsŧMÜd†{YœWįę¸ânÎãÕŖČ.Iio`žõƨ^NlvƒÆ]ߜ™‘–õ\n%øŦ: øK} ļJ=@æ ŪųV ā _ē7öÍWO^ĒvoŨßß­mė×ÃíZxĐ^ģËdŋh’"ĪŌ `(lņ[rʑ{’ū^ vķÁåvOËž…Ø?kãĻėՂ3îuOn0ũ3?'r÷šqīĩS¨:K=YŧdÍlŨްŲĩšįq9đeŗš|ŖŅ ō˛Âb/Žû‘k ŪęĨīīEõņ >a–šyˇÔRGãĨĘųØSb˙YjŠ1î{9ŗÕk0 LŨo¤Ĩv {˜–:ŌēÉīԝőA—äxęˇsŠÎˇÔß\8 ЧÂ:š¯ƒÖčšĀRģႯĘëAŗ|_€õíJ@öë7ô WÍ`w-8ßŧ~|%”ęs7ā]ņ,¨\EÁŠ‘=ėž ˇúlë5×Ųânļ|Ō ĮÍ0ˇúãpõ[ 8ė9UŖc†w§œƒē |Zõ‚Kãî€bSn|€OŽņXöŦœ6ĒÕí › äV‚ķŨ`#'_Dģ—Ī (ÕD:˛“nĘÁ¯Ŋā`CĐ9Úrî~ūä­?ŧõÜJ,Á 0ø)~ZšáN I4DC*8= AmYŒ۴™‰:œ ƒlį6hßĩ›ŨŦķ›v¨Ē§YpKķÄ(ÁčwŠŪAéPŖē;=ŖˆęEĪbŠ_đØZŗ€Yā-āYjÅ´|ŦGr„„:ãĨvŨ[{\šmI‡NΑÔRĢä÷$Z×ĨÆhŠŨĨ‹2Äõ}Kx<õ{f…›|Í‹\Ŧ äKɀû큿ĐÚy°˜„uš! Á|€QAØgÃYLpp°¸|5D°_ ސöäaa_ÃVĸ"ąáÆ{…`#+%ŗDģMį3@‰)āĄŋÖŒã7°>†ēn žūc3h•ƒōšüˇ }–Ũ÷…Ŗz/Ŋ›íŦ÷ōÎ@šOŊ2Vzֆ|™,[šā|KĸJ U ũĪj uĻsĮrwôã€+哃3|aˇA+a–’LŖ *ÁxÚåbŸ ƒĶ­ ÄŅ]°_˜dƒŠĻüä (ļeßŪZP/—;ÁfNŠåîca@üĐ Ÿáūōkĩ)8ž8ĖsiōĄ%ˇģNČ蝌ĨîŪË:ˆAû>¨ēld:|AĻו9Îdķ-f.ŽJU͓Ëöųi¯sΟØjÂō—™žäÚõ&}ŋšvĶü{ĘŋƒŒĨžä!ĩ4fŗĀ úû“h&Ą‚¯kč ~čDâ˛ëĒäØõKФé—TōģNUt2U”ÔRGJĪ ,?í¯węN „n˛úüM^ bØGĀ(Jõ ƒ@jōbIĀ Úä‚on„„Üy˜‰pxëž/j¤vpÕŅŲ;§rr‚`m%8šÖ^Ŧ˛_Jiûk¤â¯{ëA1'Įė78rž(ĀŲ@4÷ <ŗ^wĨĩ¨P¸ĸõ•ārW˛S`e3j_pÕnėųävÕč}Éí‘l7ßã›Ą´ÚģÚ ~lôō(îĨ$%Ä n­ĒˇŨ&]•‚Íŧ´œ61'áÎIÆ5 ˜‚­|PÛ n˂ÎÁ˛l@sžs˛ŽˇæĖ TEœƒ)°jūIß/ŽķÉÁFúĪOMXÄė¸ ā*ÜŊīZf!+QlŽåi ÆOhœ\­ŽķŠĘ¯ß.Įķ'‚”´™d¯ŲēĄ4(v'dŠYü,ŊVWYs1ØÍÂR7Ûí$Ũj…ÁzĒÉæÚÂ]kėä‚û‹õ1†^īõĐ1OįQ ~(ÁgĪ-Km ÷k^Ëkx D|ŗīOĸ™„kŠŨ’ģCĩÔ˙§?g2åršõŋwƒ?ũ)øë_‚āO“ú+ÂFá_w˙Ōc30üūū¯žøë_¸ę%9&˜˙Ôīņęß×˙üˇnđī˙1Hũ9(2˛<ņˇ÷o˙cđ?ūüß˙/ÁÉŋ ūáī‚˙)ūé.āæũ§FPũG‰ ŦyîŦüŋĄ…č˙ëFđŋü+ÉÍũ…ŋ¤Š˙đ/‚˙e5øË_ƒëū7°Ņ?Û˙*øęĶ^°ųƒŪ_ƒ˙õß˙&'éÅËū*yŗļ˙uđwÁ?ũāėŋ˙߲ü‹įRøË ˛‰%k{ū!'ˆšų?‚ĖŸƒŋĪ Rü|*íI˙)ø›Áņf°íØÄÆ? Âū˙Pī?Iŗ˙į\đ˙­\‹ļrrv˙uđw–ģđOŨā?ūWų÷ßūü]&ø[4û6ķīū3WėŪ_í˛ōÂ_Ũ•ūõ_g˙ēû§fáęėŸ2˙ō˙ëßü—Vđū1øģĸūŌ r+VęÛøúw˙Yėüūįā_˙môãŋũōXũĶ_äęhÉį^ēaîŋ‰ķs9˙Ĩ-å˙Đ”|ņ_ƒģŋ˙j%ø÷˙ü?ū>ø3Ņ“˙ä2ÁŋË˙KA¸5˙ŊđXCĻ‚PÉõīÁ_š_üŋ6ėéī—Ūģųˇĸá‰ģF]˙×OÁßũÍp?ÚūO’˙˙ü7îģ÷l‹…i _b˙­ügw71#ˇƒĶ’üß˙2ü+îc.øûL¯ô×Ėūˇ˙z—úÛæ_B˙—ūūī†ß‹'ûđŋüŨāoļ˙Tû›ŋũŗÜ)dãö~î}ú?˙}ģũ)ķw˙â/A;øßhĮßŌõEšR›m˙÷LuW˙öü=ä_sAķOŨßū\x˛äIjˇ4˙"˙÷˙G—%wĸg3ųŽųķŸéÛūôßä '•ú͟|š˙ÔčũËĖ_˙Íŋz~7÷¤÷¸Äņ˜,áT¯ÉûâJ-ŖYĀ,0šūK㟛˙=Ø*Č˞wÖ˙”JŨŪūS*õį?ņĮŸūôWæâũū'÷î`/ĐáOá_˙ōR˙˙îī˙åęęžŧú‡/˙p÷PøŸÜ¯bŰ˙ī˙Ãoųüķˆë1á +PČĨ÷ö÷+ßË­f“˜=wD•rK j”=Ī%,ĪņĘ+ē×Q÷X‚ ¸Ųi@(6HJP5„%d! ×ôh׀Ũ¨ ƒh„+†\i`bJņ@ŦÁē`nB{_Hi0Áč+V˛‡[E9@ãi$ĩ] ¨Ĩņ°’CĶSNõZæĘõŌĒtP*Š„c’†Áb2+Ž|;˙¨%7m‘ęBæ…M”zĄÃĢ_“ˇ™Ú@„¤„8E‡ LuE؇”Õ'e–'GĘáŋ.TqÄģˇ{‚ ŲJ­‹ĘfØ+•9‹ĸL†az˙[ˇÕžŲ9¸ČޟŖ¸PžŲ•†q°'|­ßD?ƒĩš}ũ­ū–/øÁåe?€Jå2ÉīÎ8Yа\čˇLß5ԌÛļ”ÖėˆšĐ5ėŽŠÛHĶFÜ_=yj¯ģKÆv ĸNnˆ4p]Î1ˆÁđÅøŌŧgÂåĶéP~žĪ8%ˇŪ¯Ö]xrÔC\‘Éö:ŨķĖF%ģå†D_¨ũI—Fü#ƒéoA&īTŧn´môžĒ6ŗŒĸņĢz+ĖnõĨīÜÖŋėĻŌ°gæú>›ßx˛äIjˇ4[;e"4}ŋđÖYYYšēĒår˛ŠOŗYíîÂ=hfŗ ŠäVL|Áöšŧ/¨Î˛˜ĖĪĩĀņEķôĻWûÆs§Ņ`h÷ú*të$ŪņkR_ŊN(IlxūŗąžžwxR9Ēô)Iãd.X]-üFWu{{{Õ`h“ SîmMTĘ%ĸą4T¯Ë¸ļ˛üôRĪ‚uPÔÅŨËéØĮ Jîč˜!zĶ‚<žä… TM>āiá†sAuëÁ‹4¯nE-o#8XwØäņÖerUWԁ„ p°8ˆŒĮ×-Yfō|YršNRpöbWŽGĨįNoŖ)Ԉİ\ˆa€•¨PŖCėLˉvxŲ ÂNģŲ~l8#X›ŊâŅ1÷čš×>4=BĐÕ:iRå4{ áĶ,qŲčLDHķ ›K^‚ĨTOązN „ž nęZũ0(ÖĶ…fj…ĒņvŦ×ë‡ûķI 5ŸWĮéoéėÚ¨/Ŋžķí•Ģín&ˆ­ÛÎ Č?Åã}_‰Ŋtį*“O§SAķŧÕ+ˆ_Ž˙–°_'´Āæ–@jėéųi 9Ļyŧꌗ&ĮjˇĢ]ÆŖžī¸ąŨޝ¯ ŠĮjˆŖÖ_œĪ+ãK’gØ|ûųŠ1pđBČ­‘yXã†mĻ2žų4Ŗĩę-pxÖ8ŋ ÎKtįQŧËË+Õ#<ŽŖ§oedDWÍÖéÜų˛Q>8>:úŅluŖ~) y9 EXęüÍÍm (+%¨ē‡Ĩ>–ēÕ$´ŧ°Ô‘ ;Ē{yøiåæWV^>8æÅŸ|ßãžõ¸OŅ§B'Xgæ˙žšA°ŧ°ÂcŪč¤AeĢaėō+B¸Šúŗ#*dH\ÄMøé‡BčzÔ“ŧ).9M*ģ)ƒæėŪúĶ폴l“9fĀȲŋé_NŨ÷ÆŽ€ČĄ¨ˆJÁ¤’%¤mÉ4|Оāŋ-Í$ˆXęøŨ–|ŋĐ K]ĢézŨūũˇųã#°ÔŸ˛Ų[yågĶĩi`š|ûų⁀{Ö_…0€ÔüņĮŗ˛[bŗĀk,ąÔĨt4_î^YęCÎ/ēžWš jaŠI0’Ĩ.K#@ũÕU#"úRa>—ÅÅ+{%æšÃRSD‹:ž‚A.A™ŅųöŨ]yoâõõõ“ããôƊ¯Ģvqq|rÂÃĖ#ÍG3 >}ú”4ô¯_ŋh?L‹žōņP­VĶéôā™ņíŸK-¸6nčPžė{×ô  Ū4vˆūĮp„pĨ"GæÆ>H¤œĐa2(Ŗđ¸O6ėĨ{]&fč¨Į$ŧ#Ãˇ7fÂÕ|6Õm“ä%hXŋ"\æŖÆ­EŠš°Ė§ëũ€Yu9ņ˜ fĶLāúá„G•œœôÎ/ÂŨŪövô i;•Ëä¸^ONä\ål—,‡d0ô(ā‰:Q} áę$ ’ĩŅ 0cŖ!_<úŖcÛzŒÂd{÷Yļ}Ú“ŨŖžr¸MƚŖ‘•ׄ´~U]ŨģŪéipã\hc3ØüĸĶĸŨϝVŊ‚ą>7ĒŲMĩv‰°Œ|í´[w-V Bĸ“7}ßÍ3i0+ÅTîrĨ žļr•Õ'úŪ{ö÷ĀX匕ö, ĀRs/Ŗ‘ĐĮī‘LF^r°Ôšl–#Ÿæ°Ô`ʨßsžÄú‹ŧž ĢyoķÂrę‘bĩzĒ€›wÖîö6˂ū8:*ɲ‹ŧŖŌäąĪÎģ›25;)ONN*Ö<YÜҝÔx°ŋvqAŪøœDë‚ 4ų- ´„—Ļ?ɟĩZBúÚ¯ø˜ÚY/û•Ŧôŧ¸onoš‰ŧ¯yM'ķr-ŦĮŽĩ'ĢļcŗĀė,āYj­‚™———ę–õ4ā™īĮôedCXę{Xę/åŊĘŅÉQŗíf4ē_ŗ…Ĩū]$DÎĘe‰žÚ\aũķ"ß#.õŅÅBpĖãÍą\Ėg\ÕëŦVE/đ}oI–ljiˇIûËí~Oxøĩ§`Ī1iôĖ­?ã˛ oŽįgąéŨŠĖ<ėXTčeׅníU„Ë›v0ēéz‹F—à •j âođīĻ$˙ˆ"×ÜhŪäjįíãĶúˇJm˙āō {´î ŽCâˆuËŨļVS°cåÆNâøVĢŊŊrHg}z6›OäÅŖÚ-ÖF)Ļē(Œëeš"š‡:ũ"8i€=ÎeĶmŒ}Æ_{ûë×´°W­Â xe$ Ũ‘–Ü܄W—`ģđč$ā*öŋõ°ĀÅÁÔB0Ÿˇ-í1§"ĩŽšļEäxŖØƒ%å Â2Ø!q_HĘ@m+øUvŧ;xēŅĢ'=†;Ə5r:ŧôBhéoû!ˆ|˙€Ō¤ĖzŊw°Ö. bũÛwˆM°*ŸÎÜĩåOÍnąáhũņF468¤āéąž7Îhg•`%•đÆÅõ(•žR´+‹:žO‹U72ß÷+kíúíˇûël§Ë'"Š  ō÷ģ•ĢTž–Ęu2i1D"r‘ĘØ0Õ +† *íī‘Ī­¸íŖbIÅΚÜČE‰K=aÄ0(Ŋ{0%^ 1¤ō¸!^Øüšģ[:>ŽčÛG īÖ&o4Pĩ Ū´˙ņī&LJÍÎ ‘ĸ|J¤5pgB–Ŧe;<<Ė@“#ûŧēâũnæĩxvöM‰cnO“ ÜŋŊŊÍA_û};)„ÚŲS—žRCŠqr˙āzģ//Rā}‚mfˇ˛@„lcė7ŸÔĘRķP5õŨ{Š,õÁŪŧhŠųŪå™vųînskĢQ¯đWĮĮ'|gĀR ’ŽãgsƒČ~qqÁč!Lģ[[?Ũ4gr++­ģģ­­->ŠĮķëŗ`ŠŊ–:î_ĮųMÂ+ˇ[Ŋë8˛w>ģĩ(O3H[D! }ëûčÕÕ0ŊŌ“Ÿ˛¸D˜vĢ[´n˙J•F, a–/=ŖK6TųŧđžŒÕ2>.cęˆZˇ|å!DŠzyÉĖû$M.fs2¸¯ī‰žkĄĄŦAŌu+áņ#Mä’ÛwaˇÍŧ1T=dŌōļqŠgŠ÷ō\Ô”CuĸŅdS‘w„÷žÜ%´ƒî@Û5kēk‘K(„…5æĖ‡ZU:ü!—ÔK‡éLīĪbTPō=:âŌŽÔÛéöîZÁ- ëFČÛTmHcP,ŦŠâÎDp†­]HËŅEh{Hƒ=sšČÎíļāūV[ž}ŌŦŠ=7āÆeW†Ø3ŽFƌ_Îōė'Ī÷˜kq×ëŨ†XížŨŊHŅfôĖ)¤ rÎ}ŲGí'ŌŗÜĶÄ8špqĩZxU“¸Î$‡”*n¸y Ä×. x| -w~ŅîŪׂ\;ĖR(?›ËŽōĒüAšiŨlŪm毎ęųRąÖč­=‹+5>ûŊ,đņ´Ô:CqÔŪï'†Ôę‹fuu•c}å8ĻĮp'yOņ䙅ëmģ^t(KLɛNYįĄ) …U•$bv+ĮŦ-ô¯NßādK\éĄ}ųžņĐędZ$ķ&[âķĘŖŪ둑w_bûĶ,0# ĖZK}kŠåÍZČgįJKÍÃv{{Ã4ÆÃŨņmßÜđlöƒ\ÅâZ>_ĐG”įšRųž[ÉeČÖā†Ã&ũŖ3ŋ~gōZę‰Ūũ\ ¸íŽļv *ĒÃ2 @§ƒWlt#gÜ ØĢŲ’ô2•”đ`ČZĘ “3~TGąÁ— =RBßē.;JåJÛԌp‡ØWĐ­Ŧ!$įCĻã¸īîzq×å~u%DyAēTæ´ ĶŠ%°+Ķ~iŒ°„û^÷.B[’<Ž‹”¤poˇŽģŌûđŪíĩĩėSncͰ˜í‚ž`šcv9âŊX3OEĶX’†ŨøÜ‘É›2ƒ@>€čĄk˜ēî}ĄRg CÚč&”Æ"ŊT@-'Сģ.D2vWA:JAü„<ē<÷ąŦ÷Îĩ0– G¯ŌFr!Öđ§ŖkŒP§”æŽÂ•guVUŊSš¸&šņ(ņ‡Ļ˙ĒåiUˆWčĩ¸Ģˆöō‰AĒ~˙”Âc^]WŖ–ãũÄ_iT2w–ųŧáFāPČtÖsŦ<čÚ#OĢ´NB ėãģŌŨ)UœĮˆŸ[Âí1°‡@T×ndMÁ [*uŪno䮉[XDŽÅP{/­–Ú#ҤY×:†KöĒQN&•+N )ååųP‰H_ųƒezā¤Fvĸß°Ôƒįl&Ŗ€žm°%I ė+Uĩ4ā^ëÕ}å÷ĩĐ7ÆĖ3˛ĀLĩÔ.âGŊĄ ¨§\zŽ´Ô}˜XŅpŲđB­žVkĩ+Îô<€ė¤zR¯ßpFPøÍčđø.¸X=Ŗ¯k=ŖˇmW= –ēˇûõe&įĮ%Š=NžÄ@u l$ˆD‹Häå°2GÎΧČƟ+))aÕÁ™ÉKÆģ{Yč™äVËĨ¤^PT„íÜ}cÉPQŌE?ĸČÆØNxHW‘ö \/wnE5:ņ"H´–”2į‘ĪČ*ŖĘLTÁÛČpR1V:ncåŸ>‘@Ē˙ž<@`đšØ!ëlHĢ´)ū]Ûŧ,ë#*bՖCķ)ÕK…ÄđCƒO)- @WßĨÁō-<=bUy"[õ0â ˛^: ;ÄƒMäLúüa¤ L.ũŠũ ø•Ŗ7_Ž ĀîXg6W~Oē‡îãō]É. >POˆšĨØ=ēānnÛÃ=fŸ4íGß.o[†Tē§ļÃXj˙Mōøņzß:×rjŠy\<>ö÷ö•gönꎍSNnâ=Õ+vĄ‚Ī|ļ߸˙(‚jUtĸ@ ‚ĶG˜{w@)!ãH‚÷„g‹Ą;úā™Gŋ+@b˙@æJƒôڕŊĨ͂ÃeĄHq °I{įĩë&Ō‚¸ĮW˛!Pč„ŊfĐiĻĀžđĘŠô=ŦPĸbaŠieß÷Ō˙@“ įCmÚčÜ ļ<Ũ…ĸMƒ …Üî ģėĢÂW1ŗŽ­TWČ\ĮjKÃÅĖ)îÔCRjJ+.–kéĨ€}üģ'đIA\\˜épcĶŠŽ '%Čĩv{…1ږ’‹ĸ…-ڛĸrá›3P:vK‰ļJÚŨqW‹\ŖÔE, 7(ЖK$ÜšQâ9´PūĻ]ŲâŽøĢsĶpA8…Ãve:ëa0ëŒ ˇí.HŽ0¤D4“íĩĶŽHt! E`[ZØ ˛Ö1L‰rˆäĖsr7Ņ͊Üp4=ÆDÚ”%Ŋ6VĢH1Ȓ•ûEDŊ Í „sĮÛÉW _a’K "ÖčR‡Ŗđš<Ī͂xq$§ũäz§ļ’§ÕbŌåŒK=”oČBâ€A/>fū`ŸũũoŲl—C͝ Č+‰}höOOžö—ėŧøvvvšÆIĩĘ\#NeŠ)Šg É5oFTך’­X¤Ø4Ķ)uáî^ÁDü`ŠĄtĄņüH_>íá˜vâ´GŋúéĶåΟB€bĩŸ™=é™{€†ūÁ|eÛĖobž¸ÔŧĶŽŸ—˙?—ēÎd~÷歇S´Ôq\jøņ&—9ŧžRú:ŽęÉ AHô)ՍŸčz耸°æĶœ(yM_Ûž‡â!§'âŠ<ķŽWgU›ĖfŗĀP 0j6—Z“}€ˆ\Å ™“|f‚Yŗ`P^Uŧø8)?vw!šxũ•K%•^0ň÷ aHeúëׯ}Ų‡˛ÔúeZšŠ@áj[P/`#>0HōÖøˆCÛOããˆPž*P>˛žƒ}ū' î)Q´™w7īh‹øa˙[`€ĨĻ—úwuzí՘|Žĩr"\ŅPĘy!¤bRōíyyęøtfäK'û60ĸDīŖS"@˜OžŅø$ Đ4ûßž‘†/ū‡3ûûJļžũuYŊfsķķķņ>āUÅ}ī čÍ5›Wņ„Q;†V>y^Õo°%×IáēP0CåōÂR<Í%#āL.ĩ_=Qđ1Xę—ŨĐäĀ—•0&—.ڀd ÆRëę‰SŠŨVOœŠ­gY`vZjKÉYUíęĸ¸Yö‰¸Ôœ1>Éø$ķķķķ7ķ7õcøxé`П|bÅ1‹iKĶãņ´bƒiáiJŖ¨™ŪĘYĘĘ\t ôé/d5CíÖ#Ŋ†›ûäYj=īņp*Ez&XÉi™_įâī,uNYę;7iĩԋ~ķŦũfŗ€YĀ,đ |l-õ bYĖf×[`öZj‡¯ôÃąÔ$+ãŽÃŽÍfŗ€YĀ,đ62^ŅINģ8›mr=ô`ũ¯É;›ĢąRÍfGx¤Ĩæ—éiŠ—[ é{ÂWŨŗĒLĩ[XOæGQĪâ°l.Ōlĸ͎5ŠŲÁė`>`>`>0#–GÆ[ŨûčqĢgf´ŊfļÜkōÎčrŦXŗ€Y iūū$ZĐ" ŽëôЁŖŊ šE›•x˛´šÃÉn4 ŠëúŖ-ĩ+?-K°Į×ĢIú´ÔNc3v,†SŨšŲÄė`>`>`>0učS4&˟Š÷5Lķkōî1 ˜ŪĀ1Ká7ÉĪĶRË:Į‚ ´ÔŋģÕ5 \ĩcb–z(7`|ˌøãŧm Ä|Ā|Ā| Ī’ãĨŦ[äíķXjbw(z n4Į˛Īd>}úDāW"ox`AÄ:‚=ĘfY3Ĩī§>âMƒĖ˛rJĄ°Ęj…žüÉ1 mĐÄ5S OŪ$Ki˜ēb–Z ĶĘōnØEĒ ]\ÎŗÔ‚›cí*“šŒŽƒ–ĩ\r–ܔĨaŠ•“vXÛ­fįĸ<ží8uîō­Lã´ĖĖĖĖÆû€×Rķö"ā1}LĒŠŋh}¯aš'ĪëĨ+\#1žŲØš™°D˛ĶÆļytüåË/"UģÔēŧËāÚfãŧ[lå†8Ö/ÆxĒhvfˇ’ÍocĮZjĮU?+.ĩÃĖĮZ‘´K–ZN¨"Dp7š' 1-ĩņņÆĮ›˜˜ŧŖ$ĩÔŧŊVYÚ×ŊŊ<Ÿ4ŖđkôГįuäՐkRŗ`¤ūttTŲ-•t)ŽĨ^XÉeđĒÁņ,ĄÂúŪđÜ@ķ\NHo_>œ7KÛ{w{Ûķß,žHԄ´âröúë?ūØØÜä`ssĢÕn÷ååĪ|>ßh4žĒŲ~7 ,žf¯Ĩ3˛Ę‹{…ĨŽâRĮg,Ž…ķ™wįlŦ Îv/,ΌųžøĀC\ę0DĸŸķ¯ĨVD>jīČ(-õŅFŖÁHNU^d9ˇ0#éĒûōÁåҤŠ0äX“1ëņëׯ˜īŋO’|9mđ‰4Y‹ÍŖ-0K-5ĩ „v3ĨĄÅĨ6>l9ų0ģīvßÍæĘ’Zj^Nn _įÃkŠ“H X,žŸŸ?‰Ž ī=,~ĀĮqļLü+?ų˜Ėb\[[ƒJgBd’/÷uå}â'` Ė dYjŠ1ƒã¤jÍq?3ŽK}×jīīcb„c#ŽåúúęÛˇ}ēÎė÷ēīûøČŗŗŗ/_ļ™`Ąj¯äŒųFgF35&Ëšžn|ûúõķgšQ<8¨Đ§ rĸ5æ_Ķ1šöĘ6pw4ãÖø’ŧ^goÎ_$˜‘ ¨ÔWׅXã rˇĢ+™ČōJîÖYC0ųrŪĢ ŸåŽ8?tžÁ€Ļģ#_œc¸;ŌĪĪ1”éîHéJîČCŪ—/´ā`˙@Ÿ#Ü{ZūųŦgdFmxî3Ōíö°vĀ\ėeūā¯]Ÿ‘įúg÷îNûÚīîČûø'ŽŗÆg ņJßĐūķY}ø ڐÔR'"~ˆ ?@\ęQZę$4ųūũûņņąĒ5°P˃Øe}}ŊĪhô–ņåonmŠ’„=s5;ĪŠz>Ԛ;Ģ šÍ&˜{°.;cXt ĖRK-S¯+ÁŠ#ĨÚ,´Ô<Æģ_ŋ‹—îËÛsBįį5o°ōååyąPÜw¯Ā$_ru}ÅŖ~zzzL¯āz‡×đI­ģ–ÃŦjL–°ĩŗEc./¯č\x÷q6 ŧ››’W*4æ5m od o ą<įÖ¸, ûî˜ŦëęúēéŦ)ĒUĩF”÷ĮöÛ7nJ!™÷ŊÚp)wäáZÎĪO™äގ*ž#ō8û4ŅÁ1*•Ēܑ—ہŧ‹îŸxl6—ģēĒáĸĖ؟–>ë™QâcŌgĀ—Īå°ZŲlîĪEŌ—ü3ō\˙ÄÜÚߑ÷ņOŦA3bkd§Õ>ĢYŧ–Z"~ŦŽēˆķŽĨžp å†ûōr×čįŲ\hŽU^X~~a2%"éķZMĪ$TQœ"”Ā^?/Ų(‡īMÔ)TÁOt}œ$œ¤ ĮxË$3#'ŧXKf˜ ĖNKũ[~míĻ~sÕ"€^ÔO­åĶô}Gß÷˜ ÂôyŌdFáx^ŋ?8<Č­ävJĨqĨõz<č<üÉ4 `Ļ-gĶiÚĐl4hökÚÃw9ņ†ž•JcވÛŖÃ§2 ß?~ĄZŖözŖQ\[{MäuģšÛŲyŸ76~ö[ãkĨr¤Ö` \Âl­ŅëŊÜã8âŽüp Ëiܑ÷ĪĩbQÃoa ÖÅȸˆc/ö'ũsčyã6ČÕ ķO\üĄÎÍyȋ­ņâcĘÖxŠBŽ˙ŧēbŌ} ų;[k ëÃ_ĐPŅQ3ČT9õjŊƒ›Õîn!Üۊ&äMũUŒšņÜÂ_“÷šu%ĶÃ^ķŠÄ\¯)ÄįåvüĮS)Í 1 Ė•Ž/š§7ŊZI”ŽBÚōŠĐ ĸ7‹Öf‘‡ž>ô[U'6lŦ¯īžTŽ*­vœ’“š`ĩPpqŠK-uŒĒŨ;HJéÃōœĪ{Ôî ‡Íüaķ°v¯t%Íõ|OŊŪØp_ĀcĘáaæģš/ Qđš(-'įēב|$+Ų…BĒX¸?<äJëĸ×ãC|üĩ0ė5ČķņqĄcdäĨ—ˉöÂîaļ[Hß2ŊÃ8å#Ž PžąĄÖÉ]ÁF¯DÖxHÃēÜam>ßoĢG×EœQžVWsĖė9#ĪiCđãēĩ{ŅÜ(2§ÕÔÉņ`pØĘŠ0ÍŖĘÁâ'Ԍo}i ËŅZđ’fƒÃŖGĖtĶA5čuðvĢ™Ūņ`]Lĩ‰­!×8ØNBôi9‰”@#5¤u-ŲããØĸîķ*]­ĻąF]û{&wē–סáøî¸ÚŽö¸%Ŋnĩ[=ŧ{vh+ĻøÆyÔ~Ŋ#î†ô*rGFŪĶÓVĩÆ­Ą~õĸ}rú>ūywzÚF)7¯ÛĢÕøsŠū‰O2\ ŒÕX­õîôĸ[c„ÁEûâôîôšū9ęyF‚a`§‹fû´ŠwäŅũ}ņ3R9¨œžŸ2ÎĻC^cž‘ģnŗŨšļe¨ŽŨkŨu¯ŸûŒŒņĪGր'íŸĀčVŦiŊétšQĘGūüâū“fTĢ‘5Ž"k īszYÖ)Đ¯šû^ēŧ~ŽŽęÃ'oƒˇĖ ‰=õ´ÔĶ…DßSŲxÉÎÔÂSi¤bx™fĒĨ~ĐĨ)_&øˇËķãRŸ7ˆÎã(\÷b8Ŋ–`=}ĩ*؆rĢ€Dđĸŗ†˛ō˛i{hØfŋ5†ķÜáŠÔëˇPŦņPŽ‹5†qĨÔ5•6œļ]ô–pC:ĪkYîČ:wäQûõŽ”åŽė2ė2†—=¯Ķ‚¸øg­ííéË|˙lĮ’GõÎcí¸ļ˙ÅūɋpÛ-ŊÆXųöîîÎūĒ#s|Ũ}‘kmbNîŸcž‘g´ĄÕҁ|&pHšzGŨß?#¸ím™C\¯_moË4Ŧ‘Ī;đ•ĸn#Ņ}§×zÖ32Ū?ą†[ œ“‰Įņ ē6°Įšn„qZũ'_›:ŖßĀCíėë‚pĮ÷Š€Đ÷ĀëgõŸcúđÉې´˙¨ņRcŠŖį×ūc0 <ĶŗĶR˙îV*wë•GjCwėXę(:uĖi›•?}ė~õĶ.ԃŒ¯ęņ:’åČ ũršŧįÃė÷Õx‚ĶijcÚ •k+ed Íåãuô] rđtšTŌ0øCķžžž_]ūĖåVĮŲÄ}“¨1Ēb7„ ~áŲžēZsˆ5†Ú|KHXcÔ}IŪ‹¤×%ĶĢ5¯eZmxÄv`†ÖNŌŌčaĸŒBÁv"lâŽā|kķO?­]ũAŖ°?ö7ņO÷ÄÅîĀŗ=E˙ĝvJģ=߄cüS‘Ўįŗüsü3ōŦ6 ^“68‘ėŗÚ@ĻņūYŋšÁØy͆>ËzųqR€ŲhÅ+w~*ū‰5p]ÚĀF{Æ÷Ÿ˛Ž‰ÃĶQß5=˙l8kĐŒĖT–ĄĪ‘o›âigŨ÷÷]ŖúĪņ}øŗÚāëMŽ—&ũyĻęä+ &;U=~MŪÁŌėŒYĀ,0u ôŊk„Ø‘ŽĖĻxØkŠ GįųEgYmĘád—^āŒ›ŖâzÉŪīîoņÔb€5#–ēOK­PHųƒĄĮÛkeŧô×ÍŧTŅ—žT*Ŗ§JA_ÎõÕ€*•c†2XūŪÁ>ķđĀČ7Uø1Ē=/ŧJßŪl3„ Z1ĻH̆܎“ŪŲ å3ũqhųP_„˜ā2ošM'\i“^¸Ĩ E/´(|”žD X#ųÍDi†XŖ’ÔI'ķėī7Ž¯ąĄˇÆ¨ûŌKĀPŠ^­ío3īé)7Eâ4ųrĻØ†Íđ Sųvv\0EōZ°€ŪS0p1ôš#Ä?áĸ0wdœnd#įU˙,ž†Åĸ܈øģ"ģą1E˙äk}c hˆ]ā­‘ūš‘Ũxhķ>CiÕ(˙|Ö32y6sY姕Ģ.fēMį!:Ī8l:…ž|°/ōuņâÚp¯\u6¤ ųŒ<×?‰Ō€„LĨbܑ1ū™§_vxZšęŦëܧÕæœ5D[c”o SS~ÚÕ~ötFãŖûâûĪäų'ûđÉÛāÛ&/ĨÄxi˛ÍÆRĮŊ™ũ×,`xžâŪ#ęįÁĶŽĶāX{ ĪŊ+ļŽFŖ‘UIé’Fũcū–ΝnhÄ0 C–(´‹×ŲÖē =ĻØãĢ;'˙Og*[+ū*“é)–wLŗuûķg̓dē,–ë=ŖŊŌˇ •ÂÅR/¯zrr×n¯ä˛D°†&Ķžāđ ŧt8u{÷~o/æ#ĩL œåÚ5χnˆP“mā˜œGæĘđ—xF:ųōéË´Iũ4zŽ<ŨK/šeŸ´!Ö2RāΟ?ũų5høkČ<÷ím û/Ö¨žĀŌũčÛžXcÔ}aP=}P  žnīíÅŗūŨG‚¨ĪĪīĻŽ)Z•Z¯Ļxzoe/ÊBž¤mĀā^ųJšÁ;Ōŧšá<‘žNĪĪi'ßEūŽ|ûVB2Î?OīÎŅ{„!xēR–iƒv›ĩRãŨÉI§^Į잛›+ģģSôĪn÷îččî2ã#ääČcôû'ķŅOŖ÷Ā?ÁĶå\y¨ŨÔ?O˙øcōgä9mNwč=¤ Ų°ŧ.wdđš~Ų3|äKŒīpē¯ĸ—™í7˜•Č”NúGđôJ:/;-˙ĶU¯wD§|ęģS7]¯ ž^O,CũúūsĐŖÚ@ãēiöō’H͎ĶCûí?é1|9OöᓷÁ—‰Ņ˜m‚hF§¤ûķÛņÃw^v`0 <ĶɈš•…8QBd<( ĸąÜOķ“°Aâ:ņcĩđ[.ŋv{{sÕčzNz-ē˛WjˇZĖėĄˆ{–0uP†á›Í&üÕææžũĘäĐ.2"įž–yđščšN0-aÂÆSú3­FoHÉ4ûPcŠT†æŅÔ1áü’y Ŋ…foÂK€†āÂŲ+fū ^ĶgÃ'‹ōĩĶHļ™*ˆžlŒ%x ž5Îoƒķ]Š`KxFÆÃu­ĐĮڏHĩëĮĘ8`ũV¸Ôōá1ėUŗEü Aᔰ–ƒ.ē¸Ôx%SÍUúŅëås—ēÜ—z(W1žˇÃĮ<Éy[^ŗ­ų€ų€ųĀ2û@_\j˙úqŠ‘ë×ŨŨ­Ũ]Ū͍•Āš ÁqՌsüĀ;šÁR6–eya=—vģ=I–Á”ãĪôũŠÄˆæYG}ō–ôĩÖĮŪūôéĶíí-Všü;ÁåkˇØÛ“8ÇLŖ,õy)íß/ŽĨž4.õH–:ŠKí¤ n:bDy?Ō‘ÄŗÎU/ĸŗŋíØė`>`>`>`>0Sˆ´ÔĒk|üŪųZj:Üū8=…âUŪ ôėg:ĸBgčÆå„EcäÖãiĀëįρ‰ēļ‘/ų6āNfčDg<™T°ęNúųčœDOv4–Ū¤ƒļõg˛ŽÉƒ_ŨxÖũŲ—žĻúi÷žyč‚dŌ@^-A Ą }-I‡Ģƒ˜‡üĻ^čvÖ|v`>`>0CG¤nėˇķLĮë_ŗ&ĪëžFnL!(ŦFAĨ€†ŖÖį§ŊŊ=čgƖũbãĖnĪdŗđ¸4F§é;^öö×/<čI^šōņŧ˙Gąw|^=Đ6ËZcČėļˆBã{䯄&ų°]žyžˇ'6FåĨ%äĸ%ĘĮÚōĐ,%t\íŽzoCĘ×ėhũ"5,öôŨ§R9Ø+ģÚyų“)vė}°? ŧZCŸ ™¯á÷ãŪ&ęy<NÄũˆ"~¸XUÄũpõXž Y=Qšg}\döĸ€o7Œ˜)'Ąmpíéį$ėŧŲÄ|Ā|Ā|`Ų|āaŪũã÷Â<ŗÔŠĒGí=BIąėG-\@ä(ļUvĨœf†(Ä6ųĄŽAáZĐųųŠ ŨÂéz|I™ßŋįWØn°ĩĻōjvHî$u­<ŽōĐĘIkúdôOßhĪû’ūͧ,ĩĮŠžyÔÂ|úh<|Mø’ũgƒPL ģÖ%LčoĪ΅ģ`G˛ũøã †Ë,¤V,zIzƘÁ0jŧtžYjåhGíHt8nAˇ.„6öĄœĀĩ^ĖЗ™5 ãbą€æÁÛw‚'-æYXŠ*ˆv;ÂŲžVZyåËFõ÷•Ļœ1ģ­­]ho˙Ģ„*˙öÕ*”ŽJs $:&ˇĶōĄĶ(2Äw×ęÛCZ|vjĮ8cjU×Į”vuą˙Dc_1Kã^íÄĐnwúɸÔĘ ;šÚíB'‰QšqÆÆ—›˜˜˜ŧ‡čHéĐņŌyfŠ'„,¸2‘ĻšŠZÉcŨ GÍöÛÚÚ\__쏍!oxP;ĩš2Ē=Ž>Yû¨ē&ŧƒ–lA-ų@ܧM_KąÔÎ<žĨ6-ĩ~;›7ov000xSˆ¸ĸ˜•LöÃķĖROˆ3ÔqÔ ČˆQM“… &<ŧętÍ o_ŋjūÜØØ„mMƃS‰0¸ûÛˇošr(K\D‹eY€Ą)ûōú6CũzÜÜw/XÍm˙›âc ßČģĮį%™ĘEØ'?'ŧāėYDbT{6ˇļ”ίė}öGĩĮÕ'[ÎLŸŠ'ŧw–lĄ-0{-ĩ,)Ē=K=”0-ã˛iízÍįÍĖŪËQK=!ڀ¨Ię•ÉU*íBŠĸöįĩ(Vē!š›Dö(HSqkąU*Düā$q<`jČĸ™†sæ ânM9”fVĨI ŒRÉĪ)ĮRĮ\/!Gā€Ņxô•ĖŸā{"jë|JHëĪEf n ,Ą0.'‡æĨ%ˆ°ICr?Q2iI da¯žkņÚnž(âėu?ģQj˙<ŦvÂkģ–Ãũš÷9áM´d‹hŲiЉKŊNXęÆ.Į+ûÕl°ˇŋ_Ųŗ¸Ôâ*?[ŗƒŲÁ|Ā|ā}ācĮĨ^D,ōžmöqŠ§Ō ‹K=3.b!3KiŠũœGĶRŋcõh>`>`>ā}āckŠ‹ŧo›‰‚P{Zm@aâÃĄLĢL+g!,đZj™ĪiŠ-.ĩé§Å „Ÿ6šŲÁ|Ā|ā=|āckŠ|ĖU#‘Ā")™V“—ĖT‘?­vZ9SˇĀ[iŠ5î‡Sņšgm’qHææææoãXK=u `šĖ“X`vZꇸÔĒĨvŦ¤ j‹Km­ņĶæææīë‹—z’—ēĨ1 ˜ŪËo—Ú´ÔJÎ Oo1hÍæææīíĻĨ~/Ėaõš>°LKmē^Ķ4›˜˜,—˜–úÃģ4ŗĀ{YĀ´ÔÆwn>`>`>°t>đĩÔŲlĻR"šxÉÄffųnÆ%W@$âq õ'Ödĩ>ųŒ2tIÂĄéSú3tÖ,É4E;ËNöÚ<?=jKæÕøĐn8ƒÆœžžž&ˇŗUŋ Ÿ,Ę×NQ3]ƒķɖX‚÷˛€iŠ—‹›ÁĪŪWŋhúQŗŋų€ųĀ<øĀÖR;YÍŖíėėl¯\=ßŪŪ˛6Š_…„ |dc~båV,.™<ēÅ`JæüüRëí[‹‘ÅĖŨĒæg=Ârš<ĻyÉŧ——Q“\`]—xäëĸ^ŋ!ī  Ÿ,Į׎y_`Æ'ˡķoĶR/7cznĶĩ›˜,š|l-uŧáaYÁÍMŪ÷lŦ€Čr€úá“Á;;;ŠeYEœ”úk˜ŗzĸŦĒX,Brû˛ö÷!ŒY@˜Ž'aæ8^u'ÉåO*ųÍæž˛ŗz"d4á†Iãˆiųŗ/=Mõ-÷Ícp¨åÁŧž3ĻÚĐג$8ãęX—qee…zīŦÆH^oCh{ÍžģŊí)ü!ĩg\Ë]ĶųhÁ˜IŖÍ?´NÅĻĨ–ž„˙odv000XøØZę’úZ899ņĸ á¨uŗųiooöÂÕ/Ä}xxˆ>ŧxssŖå…˙ūõ‹d>%J~Ã+ûeĀĮ°ÔÚfØhŌ8bēŖ%'›N“üÚæžyžˇ'Ôõ¨ŧ´„\´„Ŋo^˛XVf4K WģĢŪېō5;ëû\jߝq*•č˙žŧüI;”f…,LKmJãéÍĖĖ–ÎQK­Äį¨ŊGč j…C­VO<Æ;BĐ…,,'¸ššÉOP×ÍfS͜ŸŸÁÁ pē_bI˜o~…í[kJ ¯f‡äNRב6ÚņĐĘIkzßæ$3íy_ÚũéSŠÚãTß`>°<>°ˆZjåhGí=RĨ†Uu´1ŧj¤H׎šˆĖš‰ŒÅb îŲbÜAŪ§~Öf VĒ ĸŨŽpļပ‡V^9Æ˛Qķ‡kŠ;­­]ho˙+ØũÛˇ¯¨>PĄ$pųĢíSRĶr%ŋûļûûû0LëÉÁöEO˛÷ŲŠ㌊}T]MÚĨ%ü'ÂTņÚáŊȅøęSëé>:Ī9žŲT*EúP𘜗oÍ0tgØzŋGhu^ˆĮü°z"9),úš\r=Ÿ3”ÅĢŽÕÄææææoāËĻĨöp‡WuR‘ …|~~> mmmޝ¯]\Ô@áŗ¯!øPnÜép3[dĮhŠ=Z-„˙ņãČŗāPÎųü„=ĶĮįĨ%ÚföCƒ“eÕŪžĖLü+i|vjgŽdT{œ4ŲōQuîüØ0-ĩ|đ˙åáfėzí^›˜,š,›–JUå"ŧōEKíØn6ÁĮĮĮĒgrđíëWũ‰?766a[Ņ{îV%‡­!2ĖÜŖcä"ZlŗŅš˛/¯×.CũzÜܧĨ>9ŠîīGaūh€ KwĪK2•‹°×,}Ûúúz#ž¯ųĀRĮ‰6ˇļ°ą÷ŲÕWŸl9R0÷Į†vuƒ0-ĩņÄÆ ›˜˜,,ĸ–zBÐ˛(•cŊ2šP9?eŗåhĩš?â™sš|ūÔmŲŖPvWŽŽ´–ĘA…ˆœ$ L­ŊhĻáœ9ƒø†[S啑kSšĘ(•üœÂq,uĖõrX'PöĨßW*ĸ„Ÿ ­?™1¸°„Z4^õĐŧ´6ięõ+/"OZ’΁JbŦ%á‹"Î^÷ŗĨöĪÃj'ŧļk9Ü˙¨yŸŪDKļˆ˜–úˇüÚúÍMŊq'ĒTJöĢŲ`oŋ˛Wn5›Hûå@Ä›Mô!v _nvpn`vЧÁė`v0˜ēđNb‚3ĪĐûú77vŪŦvw áŪÖÃÉéž×A™_>ˇä×ä}n]˖´KLåÂaŲų€aåTJŗBČĮÍĶ›Ūy)íßŨ××WaȰ¨œŊY¤Ĩ1ĩhüDĻ/8\?Y098<ŠU˜pëGŌ6rÁjĄ`ZjãŊ–Ž÷z ¨t.Ļŋ—ŪÆė`Ī×Ë}āckŠ‚ĖISŋĩ§Õ&>Ę´Ę´rÂĻĨÖ/ĶR›ĖĖĖ–Å>ļ–z!ĀĮ\5 ,’’i5 Â{0÷´ ˇræŲĻĨ~9Īa<0^Ö8cķķEôŦĨžgĖam3 |` ĖNKũ{„֝Š-ĩc‚e°Ö‡x4nØ9–qä:-Åė`v00x;XĸÔ‹ØĨ™>€â҉¨ŗ¸ÔĻŅ ,"įdm6ŽÔ|Ā|`B0-õ€/v fyŗ€iŠ5ũ¨ų€ų€ųĀrų€iŠį ‹X{ĖĀĻĨ6-ĩqŌæææKįXKÍfúĐ ø˜ĖÚŨŸ>eYĮ;š"߈MØf~cM–Q듏;C—$šž”×××}?UĢ'ž ęü6 qĻĮW:ųÕMŪlę Ņ=yvK9ˇ0-õrq38ĸ­gēmķķķŦĨva6mgÕjŠ\žŊŊũõĢÍ)årYfu% ¸đ+1ŗ÷÷÷Ÿ‹W&nAJ]Č0š]ø.//Ÿ[û‹Ķķũ Ë:ޝtōĢ›ŧ%žęÉŗXʅ°€iŠ—Ž›™Pk(îkņ-ūąų€ųĀGô­ĨŽ"|!œž˛˜ļžīYIQ'g#|2‹üq†c~eq ԟX‡ÕeUÅbҧįüÁū>|6 (žiJ˜9ŽWŨIrų“J~ŗ†šgd9`9ëö-#=8Ū'đü:5R/˛×…ÍŲ`¸ŨIi’?I^­hw÷ĄĸdĘZ-ʞÄd”ĪZŒēļ‹įĄYoÄĪUpzÉ4€ōuJŊŌÁēāõŋ|ūLōúö?¤L\>åpŊ´“zŠ=iÕ…Ā‹ÖČ'-`ZjéIøŋq6fķķķ%ņ­Ĩ Š!-‹Œ{Ü=|ãĒ"W"č R„.š”å!ļų ęÚķĘį᧊€áYũ”ųũûwRÂvƒ­=X×ė0ĘIę:—ËœđÅÂūR”gŠ}Êĸ^¯“Œ%šũĘŪĘėö„ŽĻ…”Fuį§!%%ø5™=y™ĐØkkҞķĩ‡ÁŪ÷īĘÖĶČg'd4Cëĸ"ŊŌōŪžoŋO ī uîËÚښËÁՀŦ|ąāŖĩvĐĻĨ6~¯0žŪ⛘,—,ĸ–|F=jī_đƒZjũ ˆ †6hęí¨ųˆ ׯ_ŋ‹4žE†ĩ„āIĩą?îtÚų|žō†ŽpļūJ%C$ī9U÷K_8¸ŋĩĩ‰ø¤Ņhči°)üúå 'ŖiŽ$Ŗ…lԝĨŦÕzŊ†‚ĀöŲ“čūū> Ķzf#įdg?|  ­‹“ƒíOĻT†Û•`_)mH6Ɏ?€LKŊt܌iŠM#n>`>°ä>°lZjĀ ”0:`(jĨTuƒB>?Âāœ]__쏍Á×&´ÎŲAž ^á3™,Œ5h’ėņSŠ&¤ bĻ=$Č;Ö6ÉkJ€;z‰zũņ Í֓đŲĐá3ĘeN–ô$HZkŅ}"eÅĨ,—âėÉkLĨRI…ˇū”$÷ũqōę†ÖÅIMÃŪ M™,3Ō†Ą–ˇ“‹kĶRGĪŅäJæ`H$"7ƒ˙ßįå!9ŠV9kŧīäö4[™­ĖĖŪØ–MKôÉ/b/_ņXÁâ"HkyŸLKũl å/‰CÄvËĖh€ķ|_ü‘æė#Α×ąë˛øææÂQK=!‚ƒ‚D5N…Æ `ēõ°?¯EåōųSˇIB4•Ŗ#ũŠrPAtÁI"`dR)%§!‰a‚9Ãģ÷ĻĘRÃ1Sšâ(•ôE™LéĀtO5ĐÉķžĮ% PžÆÃR‰J„mssƒ?)ęŲã#8›“”é›Dö(eé!eŌz$>O'ҟ’tĖ‹ô¯ąĢ‡Öŕžž •†ÄŎߧ,'Z•,ŸÁ1‚mĘŗĶR˙–_[ŋšŠ7îôB o ZÍ{ûû•Ŋr‹īŗÍM™ iŨâXŅüGŋo3VûׯÅē–EąšĩĶüĘ|Ā|āõ>¤R¨ānŊļUËÜŦvw áŪV$xúÛ”éņås MŪįÖĩ éQŠƒ†5ŽŪT6ā”Nž|˛4Ā˜ÛĮ+|2Ŋ%X _4OozįĨ´īŖŽ¯¯ÂaH9#z3Ձ•đ{÷MǟŦ žžTŽ*­vœĄT.X-~ĐzGÆ:î•Ր}8ŠX,mcL\H4eøCp6‹eãËí~™˜ŧØ>ļ–zQđĮûļ“)’?~D”ükZ‚ČD5*ĖļD%2IQŽęˆzŸ$ŊĨY ĖRK zNgãq§DŋĒöęĢEcÎH™ēZÔö›vĶîų€ųĀŌúĀĮÖR/ ūxßvE*•)@j†ҍdôđĮ_ėø,e|_{ZíÂC;qīWQŒæ ēķ‚~#–:ÆĀ!GšR)ŌÃh;>[p˛J‘ä˙ŊŪoˆ´_ot=ŋË"EĒ”wÛ­Vqcĸûžā2gK.EŪ‹p|×%Ēüן?m3sŅ[Or-.–>æbZƒwG;cÖPg0O0OX,O88>é7+ÜØÜâāüĸ– ģ™ MĪūöūL;ÜIJ!ī—T&ÃÄģząoÎŊĪĸ4Û§ŊíÕāûN<ØP‚YĀ,`˜Ü‡gķÛāŧD—"˜l}yuĨKo*nŽĩ Eĩá@ĩlÎũ—ōá1“š­ŽGįkš P(ū–ΝÂĻŪėJo%™‚Õ\Z´Ôß^K}pxË­Ö~¨æ{r-õJ.7ų­˛”fŗ€Y`¨îz ģļŗÅ•GąĀfd1ēõ›ģ ŲZ ÉA]_ĀI…Ļ}ûÜJP.å÷€Š>VC__mZę9†kXf Ė\K đĻ{íuc-ĩÃãžģ3 8wžŨí^^^mšĐžSh˙2{Ÿ]ģYĀ,đR ŖŋœŸN‚߃Üq°zlŸŸĢAö88ˆÁxiŅŖķÁ_ÅjP8‘ēökōįU+huúx›åĶÁZZæĶlfƒŨĩ`+dĶ‚šIISĢMíņßvöŽ1-õÛŪĢÍ,°˜Ĩ–Ú @dĮ2E.ΎĶU+×ŊĀZj7\XgÔĐ9Č-5ö&×h.…—ŲEšĖĶļĀÁUPom'ŦKŗĐņJPĖ –Ĩw=Ž ęsŋ~ŖŒëģā¤|­Ų“ P ¨—8NÔKTŒŌZđc38Ũ nËA{/¸Ũ ŽJō3§;ÁŅFPŨ nܟš´čß.‚j°{!ÖZ}|ų•4¤l\;å>‹ÎŒęĢ?p\jEÜ€ãDVf ēD‚Į%W@DC@Xũ‰āŖÖ'ãH*d#ĨŽÕ’ÜĒÕ_‚†~›  ’u¯tōĢ›ŧŲԛ æ=yFK9į˜]\ęß]Í[Ôc7‘1Öh?žé~u?/] 9÷kžYĀ,0w@ģĖÆ´Ø_7ø­RÁq!ÔËA1'@J>¸;Ŧų€rôjUwŠ& T}|%ée-Z&Â*uEp3l4ô3HŊĩ&Ũt_ŊÉ6 =V,ŸM čĒâ0ëČBØ@áüƒųæŸ(ŗīDIĸМ4YĄÃiÎƒīš‹ÚIC#AÛĐįčÃkAØ\ßPÚõ–ŋđ!c‰ú&:^úXęAēæėėl¯\=3€ØÉ~ŲB$(؈ĻĖO¯đ Nîô“[Œ”~‰_ūŲŲ…/áōōrōz_™’ī]Öq|Ĩ“_ŨäíņUOžÅR.„bo‰úœ8âGĶ#šfë5|Ŧ]šų.â‡c$46”;#}äīt’’΍ėąį,ķW\īšØZęiņĶQ9 á&ÖHŗ€Y`ž,čd<=Œæ$t5°艈9{(ėoņLpį.ôđ…ü Å tÖrP<—׃Ŋõā`#8X *ė7C_n †Žíʙíŧˆ¤Ģ ōГŸ‡ĪS œ:Ė:˛(vjjķ2›W;rž?9OŽ~´œlĘąĪÄ?X—^07í â¨tŽL)/…8¸>|ÍW):^:SúđÅëŧp“įUv*šÉZŋ››ŧ§ŲXíĸūJĐ7ā5Sí9æ'–3ô돎ÕeUÅb1Š99Øß‡ĪfE`zlāĮĢî$šüI%ŋY{ܛ”–V‰ÖvéHĪuųž_§FęåOöē°9 ˇ;)Mō'ÉĢíî>T”LYĢEŲ“Ą|4œē΋įĄYoÄĪUpzÉ4€ōuJįJCę‚×˙ōų3YČëÛ˙2qų”ÃõŌNęĨvSō øéŸ˜Š–:ÂڂēS‚¸=KŊĐZęéōč īAvfŗĀ›[āÖąÔųĖ#~:É ķke3øcSh]6ØßFKˆÛZSäČü ÚūÜ šeQ<FƒÂ÷6ä<đ ZU6Ú᭑u=ÉOÉKÉLKĄĐ& ō+Ō0ØwūT{L^ĻBŌZ@ļĒP€éüĶ+į‘ŋ¸m ĩÔîÃg䯊$^ęÂķŒu_~"î!ė5ŧĩ $ØYâ>8HD/= ūû×/’ų”(ų].— ÃėSR đuC9ã™`ΝÃãCŗŨî°÷ĐSķRfō$…kEģģĨãã¨"`÷)kĩ!úuęëûVévëŪŨq]|i([Oh[ģ#­‹+-•ËĖĄęuģžũ>%?ų˗ōģŨĶĶsŠĸöŗjuė˛ĪĶŌR+oŒK–Zž59 ž–%tSžĨĘ ŧx,÷(HeSæß¤ĖÅskąYĀ,đŪŗ­ĻŖÎo(WMƄEČ`'ëķ9zžl E%2*ī§Š|9đ%Ā'W¤/ĪÔ~$-ĩ‚ËQ{&XBåÂļĸ]ö ¤]:ԝYŪoĶļ‚ēöŧōųšŦqn€gõ˚ĀĖÁ|“ļÛG-‘kvå$u­Kv#8á'ŠĨ=åYjר*d,ŅėTfˇī$t5-¤4Ē;?ét()Á¯ÉėÉ˄Ǝ?ö3ģ€Ųûū]ŲzŋŽxrÔbh]T¤WZŪÛķí÷)áā=ĄNųkkQ,IŽdåīŨ¯Xũ¯ĩĀ´´Ôĸ qŅ?”âZ§.I… š„[7-õ,į•ŋÖ/,ŋYĀ,0¯ ‡m9R‰Ø'ųcēføfā5ŗž(ĄU=IŪ…Nß@šÔAŽzá´Ô\…j?FíŊΚúLt´1˛ę˛&ŅŽšre"cąXˆ{Öv‚'ÕÆū¸ĶiįķyĘwÚ}Æ3Ѝ]Q2D2MņįBÄ×Nîomm">i4zl dŋ~ųÂÉhš#ÉhĄ~-@'G)k5–ÎĀPØ>{ōž'Y˜NļJjOĐû(?ņĻZ'Xö¸ŽdJe¸ĩ|lâ+Ĩ É&Ųņ°Ā,ĩԞ(pkSĄūˆ‹—zĘ\øp"ģŗ€Yā -@D y7¯8Œ2:ÎÆäúæ×hŖį9otO†7ęHéĐņŌšÕROîbž+ĖÂ+šŲkŠĄĪĪE‡0¸g×××..j đ„Ö9;Á“ķĮ™LÆ4é`ŧĮ”ZAē f(jäk›Ā¯QCîPéõú â¤Ōz>:a_Č<ô$HZkŅ}"eÅĨ,—âėÉkd*XRáÁÜD oÃäÕ ­‹“šF<*Î64eōž`FÚ0ų=ĩ” akŠ™Ã¨ƒJîsđcÄĨ6-õBxļ5Ō,đQ-ĀT<ėņ$?-]īl4Đĸč›{ž[āÃhŠ'÷įŲe‚oVš¯|ŅR;ļ›íûŪēdU&ō8øöõĢūğ›@[´Âž…EŒ ™Bā°5\†ķąŦčŅNhąÍFc0%å”ŊŒ{¨–Z 6õÅSŽÆāƒ“öÖ ""íÕ$SZƒÄBņÍAŽY WP(ŧšeh]^ځ%=Ī=4e˛%|QlŦ¯O~O-åBX`ZjšđžFü‰ōĄĢ‘Ë^ø]]ĶÜ•ž‰^yĘŧōTÛŧ^b4 ˜æĮļt¨kŲ)ÄߘgŽųõmĶ[öa´Ô“{ ¯\¨ÆŠĐ¨œ|Ÿ˛YÂY´ZÍņĸ\>ę6 ŖQ(€b+GGZQå ‚č‚“dɤRJNCÃs í)‡˛ÔpĖ”&8J%Ģħt`ē§čd ›“(OãaŠŽD%ÂļššÁŸ” õėƒņ‘ œÍIĘôM"{”˛ô2i@Ÿ'‚“čOI1Ļ?& č_'t­‹+==­ņ‰‹oŋOYN´*Y>ƒ;bÛ>”fĄĨV×ü-Ÿ_cØĨŽĄMĒ^ÍĨ÷ö÷+ßË-žĪ67e‚BBLĈ-ÉņJÎÍÆˇÍ,`0 LlB/ˇî$H‘1”…÷[€hzž_ė{§Ķæŧļķ“fŗÚŨ-„{Ŧ 9› ”9y,ŧŲ4ÁJ,sÖ8zSŲ€S:ųōÉŌ?`n¯đÉô–`Q,p|Ņ<Ŋ靗Ōڟ€i¯¯¯Bbt8Ĩ†°É:Ē“ė¸Ũ7Š~˛2xrpxR9Ē´āMâņ&ĀŦ ~õDÎCXKjĶRáKÅSŦfŗĀX€ž<ÍÆÜÄ×ķ¸[oŨŽåĶR΁Ÿ.@˜"ųãGDÉŋĻšˆLTŖ‚ĸ•Č$EšĒ#ę}’ô–fQ,0c-ĩꎝ€XmZęAöĸ8ŠĩĶ,`˜ čâ,˛žÉrë¤'҈ëũZB-õ<8ęüˇ˛°R™¤fØáđJ–AXpüåÎ'čķo+kᄘĨ–ZįĀŪ3Õ÷ž×~ÖŗÔĻĨvCgMLxã,™YĀ,°Œ`š6V9ąXډŽąƒúĮjŠ—ņÁxŋkFūķgŊĮΟ?“:ĸ÷k‘Õün˜–š¸ÔŌãIwß %VĖŊŦž(å‡>,?ŠKX‚ķīvī­bŗ€Y`á,pã‘ÉÜÄEˆš1 —<ģ4ņ͕7ÎĮˆKŊpîj 6 ,•f—šÕģųT-˜Z >:õbŽw8åø!KånvąfŗĀë,p=Ų牯a?˜Ų´Ô¯s9Ëm0 LhYjŠŨˆ\ÄIK8=xiÕ¨5ĢĻīy¨~n9ī ī“%3 ˜Ėm'uț–z‚xÛę0ĻĨļĮ,`x ĖRKíF&utR˜ęÔKmZjĨëßæ[-fŗĀĮ°ĀÃē‰c5Ä;ŽĮäqNôĻ/Ž–š€DD–`ԟ>VÂ/[˜Œ9M8äoßžúŸˆEŨįę—ÚŊpz„Ļ&¯O0xæc<&vf÷˛ĀLĩÔiŽ*ˀljĘĮcœō,ĩœwŠęEá’gŲÎ÷ēõV¯YĀ,°`¸uë&ŽÚ牓éČãģ;GZęÉŽĐÅÛŦ/ōå˯vûæö–°~i@Ūž˛úļŦŋŨųõë×Ök†D‹† ō4žēYÍåˆõ– 4ÁŠ-}g&ožĨ4 ˜-0k-ĩcĒS)åĒMKmqŠí!4 ˜^lfGˇō[7qĸ˜Ü‘įIK=ų­?:ǰî Ģ*EX ŋ2HrĩB~m×ÖÖî¯ÁÉ-eš_^^&ķĮûÎLŪÔÃĮđĘš|^ßAēqĖ™d!ÆR/U§aûÆ˜Ļ–:AR­Įī č´DÖĄŽđĸ‘Ēå0ŽÎĖsŒęĢz}w{;›N¯ŦŦqIž“žŊŊ­ūøáná¤×õÆ÷ÛĒ3 ˜Ôí{ixŪÖMœXGŽ7zAĩÔßŋ‡žš¸¸ˇf¯ĮÁׯ_õŠÆ¨ŸAe#úžâiŽ’o,õ‚>ūÖė…°Ā4ĩÔÉ'Ŋ×û]ôĶ1K­ČŨīZKM?uôãGnuõķįĪFƒëz'=Ā—,„—X#Ífwˇ@ŗ+M€Ĩv}ŠqÕOÛAo؜hПë?Œ…žē-›Í íN˙&Z AööößČŦ eÍņææf2ĨąÔĪŊ–Ū,0šĻŽĨŽĒ…ĨvZjŋyp/gîŖ/ėÅÕRĶōÕÕÜÍÍ Žb`RNz_2ųͲ”fŗĀōZ Ũ‘kĪš•:LK=IĖ“ØWæBK­Ņ™ˆnkkkh?Úíöíí¯¤`=ö˜Pf˙üųé6{ ħĖ;ž´ ›jÉĖf×3ëtā¨Īad1•âOŋޏJ7ĸy†P#>=,I*•"Ŋ*ûtbŦō~Ëå×nooj×öŠ×“1Ëĩ\æ`ī rPnˇ[Åâs/îīéäg—Sęš˙c$nÔ}Œ*ŸĐž}mū$'o'š–‹‹,–a€ĪģŖ1k¨3˜'˜'$=aõáüÜVpąļËå2?ķ•ÎVŅ*—÷čC+rFVāŧĨa*§H †Ŋ_2ŠL˜Nםޞšœ{ÃEī íjo{5øžŗføĀ,`0 <ׇgķÛā|×M!zŠ ŧŧēbĒą@;Ŧė×€Ŋa¯Ë|Ã^įūūËÆFųāøčä¨Ųö)Ãb. ÆoųüTîUŗë"č Ē^Īe÷÷ö*{h'bu¨û{Ú€x§”mūųô___?;;ƒB`ëksöĶ'ÁŲ\ C{ĪŊa–Ū,`XN Ī‚F+¸*y—ZA íĮX@!õĐw „“a˜cŽŗ÷|šíjwˇîm=šŌ7EƒĨdާXže0 ŧŖŽ/š§7ŊZ)í1íõ5K–f”Ž{ėę XžTŽ*Íļ\„æHŊZ€ĨÎ3]¯ÆâĘ=3ą&ĸ”¨ė•Û­VqÃąÔ `-?­<:ÍfÎ=c>Ÿ?ĒTōkđøõOŸVŨŪNr]}sąßŅŦjŗ€Y`Î-=&tRpSŽäÔ:ާ›ĩī7QoČøg*“!đ\Ŋ~•Í:–:Nŗ}ēĀ,õÉUˇÖė¸HúÎu×eĮfķû}Čf>SŪHG,u‰GOžAF •Ĩöø8ęk"P-q;Ü͑­Ķq,õáņŅŅfĢiBBĪR¯­ŨÔoŽZ@OÕkųô~YXęVŗšáXę@ijˇÍ??=õvŽK=į(ƚg˜ ¤P‡õ‚ÎwcĻ'ĩ@$üö~AˇKM T˜‡ymÎ1K=žáOÃ_åri˙ Ÿ ļ7 ˜fmPnĢÕ][ zŨ6,õy)í1­cЁÖr&ÆĶ‡ŒXƒĐ{–ēՎU×Ä­ĪĢaŠEK}ÕčÆ$u°– ‰čSŲ+-´–zŠšī\nVËsŦ!fŗĀ,p× `ŠYŠüĻdœô¤ÜüRiŠŋœ´Šų\›.QŌĢžŪöfŗĀ[X­t6ė՛íbĻ;#-õīú­Ō̉ DūŌx‹—zō˜Ķ“ŦĄ8…—­a0 |t °n"Ũክ›M‚Ÿ(æI4Ž8l !pã÷‘đI‰4cĸ>ŋŪ˞ņŖ¯ēņye9ž–šĒ‚§ŨÚjvlv0xāšƒ‰v*Ašž?‰#~Äą;üjâ~m'ûˆriÄn˜lëŽË_—úAĀæR03]f; Į­{MĮî5q=īëûh+Á,`øđ`:ËlFģL‹K=‘Ô+æ*.õkæ&>‘—ˇ5Pšëíö Ųāh3¨l„ã,ēnņüĶÔԚ´Ųˇ˙‰ŠũŧöŸnEë4īä¸ÃEšvkįüûįø{$OŸŦcøĶ šwâæxU–D\i‘u D]Å4é]<‡ŗc„Ŧø–:â¤ŨŸšģX}Ŗ¸I8Ũ˜æÃƒģ@ŗ€YāĩP–ˆ tĒPĪák—3}lôGŧ‘ŧ‰ÆŽ—.(K-đ[îuŧˇ\õj:iaÕåbXkJ›Ú/¨â™í—ĮCrÕZŊRá™yŸ[—Ĩ7 x ČĶ÷gZû™)ÆĨ†ĨvtJ´ÉŦFa t=ÅxUÅEᘧĐūמj-ŋYĀ,đņ-€–šŽᇭ›89Oŋ|†Œ…Ž/\_pŠî5C–Zh.šŌĶ-ų„øąžî„ë čžÃzŊNn…ė•äįJųHƒĖ&%Ä0{Ŋv-Ąī°_”””æĀC”r#VˇÂ*%¤ĩyŋīæĨ´ęfȝРw˛ķPŖ&ę;CŽB.čÜ|:&Û¯–įWj¤ŪžÚiķņ–\ŅÉV¸ęj×ŧėIŲē#Đo įcŽ0ēj;c˜ēîå鋞 ¯ŋˆYęØ˙cĨ†GÂ.eÄIG,ĩ{ŠéīĩrĮl1KíVŽ‘ŋ9mZęAļZĮ6ŗ€YĀ,0Æ,+‘e¤Īøé‰yzĩįĐąMĮņKA}ÚGfFŪ8;-ĩžyyīžÉÁîYwûŦë.'¨\Ą˙č}[O×[üÚ­ŨôJëi}gīŽ…DĀåd^f=ņžFÁéß菎wÖ›v@™°ŋ[k:UJRfÃāÛEīôēWېôR&ŗîÐ2÷¯zL¨Ŋī…7­`#¯5JŽ9ãÔĸ7¸ķšđē‚čoŋĻĄöfGÚɞ㨜\xÚčíR{ŖÃŨ——4¸UĐZՕŦ׎Í>SņugĢĨv܀ß ĒYåË=EĻĨÖ¯ŽG,ūŒ:p+Ö,`ø ŒF&=‘†xr÷csŪzã?’–ZųđŊ[GSŽö^t—×­q˛ ›įŗ“ã›;+kšĩ\pÚÁõŠŦ åŦæĘņĮ°Ũ,ÜF9ārĸzųōĢnģ#8Û}éČy˜éĶk)ŗÕîڏöԚŨ/,kˇã *M´“ãüJpËXĖ@ûĩLj?wídĪąæ=¸ŧĢßΡ{ÕęÉÁãŧ¤!„onØyoŸž6ØųAšM&´‰<}o Ĩ–ú^W9Ą—GņƒÜĀGÔIË?u]á}o×`0 ĖÔ€ zpƒiŠ'įéã;2ŧ{jžĩÔĒžwëJÄŌcᛕˇÖ•9ÆyūØJ_–VNwŌ3›*Z4Y(Ģ+$Žæ8j9:ßQų™ŒÜ=ßqaŧâōĩ՜ô•§īu‚ių +‘ūÚ|#ēō}š¨š‘tēŋ|ųUÛŠCŊ.o>îIWK+4ɡS~‹%䝎(NũĩøķÉzíØė3§ī ´Ô:Û1ÚĮ,ĩiŠÅ&ĘUÛf0 ˜ž˛ĒPú „›Wž.ŋÕÚĮU D1^ē¸Zj'—ęuÃūÃŽáxˇĢw_NŊ MwŪ)‰wFyÕnjũASJö¸=ãS&ģ‰Ō|{.ęŨ­BšLėOëÂ7'Ûé[2ĻũÚNĨjŪũÍtĢŨ+ŸŨŅ0ßN]Ō~ž—¸ũÉķvˇ”bc•ōčĢÉå<¸hqfģÚē‚(vTī]ī~˙ŧEúĢĻĀnÉÕcâ=Év]2)ÁīvAĢíĪ'­RĩUktËOÖurÕĻLJ¸¸q)]]29’pŅÍ(¯?¯åSW&f32åj°ũŽRæÁy›‰š—,´GjŠw¸¨ģŽœoļīiáˇĶV.#¸ŖŅrąÍâ6Øą÷ŗÉ  āūĸ㇧OŨPËtãR˙–˯ŨŪŪÔ%VN´ņ}yĀVŪmˇ[ÅâF—gôžI Žxņķ*–é8—#ڐmfŗ€Y`œĶĢ'­ėĻõÖy¨X!í8…Î&Z­ÖĐ÷K&• ĶéúÕU6—1DüŪŲŽöļWƒī;k į‘'×ŨĢÛ8Rxų(ĖņŒãŖ­•ũ ^ØQāÛgåuæW6ūV\šlv™ĒÛļ?=Îüe-}ŪhOĨ HD¨.V|?ÃO^˨öÛųéúĖbŲŗÕžß(dē­ÖųmpžMũ%ęéåÕU†E阤ˇvņfīö†Ŋތŗtîīŋll”ŽNŽtVąK)Ąå …âoųĩõ››:‘y”Ĩfŋš öö*{ĨfŗšššywwGhkÁōî>čļTĮšœLļÍ,`0 Œąz҉?jģ}ąöČļc…ÔCß)ŠTjeeåĒVËå…×điļĢŨŨB¸ˇ5+˛ƒøw/^íåÉŧÕëîŠéh•ã(ū€[{eĸãŸåœcš'M¯ņ &I˙cuIptu'œņ ʡ2'ŋfĢYØ °ŧUČ —:žhžŪôjĨ´Į´××Wa˜‰Ŗāk=Ā#Ë(ĖŲ]__?8<ŠUˆī@¸@qÔĢ…–ÚkŠXęV̏áXęŽ[Ā@EāËÄOëõjon›YĀ,`cT_΂âJpškœ´{ĶLÆÍķn–zØû%•ɤaŠëWŲŦcŠã4Û§‹ĘRÛd0 ŧģĪÂR—čRãĄũP–Úã㨯‰¯[§EŽeëtK}x|tôŖŲęFŅ0-õ‚úļ5Û,đ‘,GĩŸž–úwiqПˆ[ō‘ŧÉŽÅ,`˜T}*D‡ŸeåÎ|ėx¯mŨŠaqŸ„+úhqŠgāyV¤YĀ,đ DáÚã>Ge:īם‰V>wˇSāht;§Ë’”÷¤×õ•’Ŋ“ëėÖšŋ,.õ¨5Ÿsŗ,­YĀ,°ŒPp™NģžÕøé‰yzõ•Ĩ‰KŊŒ†]ŗY`Ž,С.¸ĖŠÕéžnxQFÆT!­Šę¨CŖY§R¤Ũ2ã˛ļyŦĨ–?{Ŋ–ÚâRcQ\õ\yƒ5Æ,`˜C Čŧ'YˆH ã§ŖĨ žāéõV.K\ę„ãfŗ:¤ âäXö™Ė§OŸž}ûÆ&ŸĄųîîî§lvuā§äSaF¤.Î\__ Ģ_ž|ņåOū°¨œ”ĸ /N^€Ĩ4 ,€"u†ÎyAK+5âĩBĩYęh&ą°×üąÔ~î‡+!ēæ0ÁR›–Úe¸žo|Ěh0 ŧĢXœMV#7-õsxúøĻ-–:ņ"Ûíģ_ŋ~hĢT*ŠYˆ¸ĩ :ūōåWģ}s{K’ũũũĄngã'đwŊ~syyé˟üąˆ5Ļ UŅäĨYJŗĀ|Z`ŠZjˇíāšiŠ'[r>ũÂZe0 Ė”ĨŽzV/Ā3Žú)Ž:ēË§Ĩvī`÷~ ~Ôõz]::Ēė–J;;;îÅnmmũņĮƒ.Oä>BŖpžhžË éíˇķŪŪŪÎfŗģÛ۞˙ŽÕjŸ?ædĄP€ØĻLōĐåË Ku;7—5Ä,05 ˜–:zSŌ:ĪúüÔî¤d0 |P ¸ų*˛Ô‹C?ÆUOju‡%ÔRĮŖÅą^3~.’‘­kĩ+ĪXynĒÕ* ™đÜ@aåŧ}ų•JzģŨn÷ččHËŠ]\|ßÛãdĨr°W–Õ‰’yų“)öƒ>ŦvYKmĶROÆ%đ§HĶTéâĩz|ĻWŽĪ'Ķô/ĩ÷ŲśĖXāδÔ/âæÕ´IK­äî¨ŊwĨhQdĮR'ĩԀŨ““M2†~~Ōû ™×Öĸõ<įíˇV\ÎūââBKûņĮ››lnnĩÚnɊĮ|y>Ÿo4OVm Ė gĶRį0¸‘cøiŦĻŲ}iŠÅ"Ÿæãķ:IjûÎgŦÁfŗĀ›Z@âb~—iŠĨ÷œ˜§oŌĮŅRëʋŖöŪ)GiŠ~üđ0 FrĒâ(‡fĘTĻõW¯‡öåƒËãeáBŽ5ŗŋ~ũŠęv)ÂŊZ„Ø5iŸøM$ĢĖ,0c ˜–z$‡1&ž4ú[[ōŪ—æööö¨R‚ēĢ($ąXԓĮĢŽîŊiЇ=@įįįO>ŦÜîaņ>Žŗeâ_ųÉĮô`ãÚÚT:"“|š¯ (ī?ŲK`X ˜–:zUMŽ™žkĩé,J%‘ˆŽã1*žG˙ųōkĒYĀ,đ.Pö/›v=ÎÄ­­ŗ¨7Ë´ÔCöû÷īĮĮĮĒÖpĩ<˜r}}ŊĪh|`Šãt›[[Ē$aĪÜG=M,=><<ô|6¤ĩ[^ļfŗ æ~—GÉ*5 ĖÔĻĨ~ļ–š°š.HgwÛķÄúˆŖøé"šŠ/XáfŗĀ\Z‡ļ¸Ô˙MŸ[oæGŌROčžCšážŧDŪ8u› Íą Oäį&S"’>¯ÕôĖ –šW!!D(ŊÆŽfŖœĪŸ™¯¸AüÂæ$áDˆ BĖ>ŽaĮ'™9áÅZ2ŗĀüX`vZęßōkë77õƝŦĮ¨ĢŊŦfƒŊũũĘ^šÕl2}Až4„Zą1$&ö"ËėÃÄÆØ–ūåÛO‚ö¯_“\ s÷ööČ [ā‹´3f uķķÁ1AP öXJk?<šgíĄŠ{{ß÷@–B Ā 9Ÿą4Ū>éPtŊ}ũŗöÕŧ“ Įė:ĻĘųž—ķ›Õîn!ÜÛz89Ũˇu2æÆsK~MŪį֕L{MdĖõšB|^îĪõ?ĻRšb˜+ _4OozįĨ´Į„××W!+"ē ‰DĢ'Jŋ­Ŋˇ~ĢęÄ…O*G•V;NÉ܉\°Z(ü–˯ŨŪŪÔŖMŽ:—‘•“ĘģívĢXÜ ˆû{†4Ę]ŦãOĢĢŋ~ŨļyÔyĩ]2}.7ĢŽ{ޜĖc0 ŧØt´™Ã —nĸ5:ĸ~Y Ô>Ŏ‡Ú!‚Ôņ{+Ų÷fR™0Ž_]e]˜d˙nÛŽöļWƒī;&KxpX^ø„ÃĢTĸy/ödͨÃŧ^CōĘŌ,ģY`Ž,pxÖ8ŋ ÎwCÅ{Š ŧŧēŠBšģ3ü ŗĖŨRØëvyĘ:÷÷_66ĘĮG'G͖ŦRŽøģ˜ …âī‘ĻÄ­oŽĢœĮK—‡Ž,l3Šæxr­ķb•9WŪ`1 ˜æÍĻŗ¤Ŗ4môsuäz+—PK=]æe=-ƒŽ“kˆ'‰ņąËÔģš„Zę9tck’Y`,0;-õK­ZjĮ¨ôa,GņKv~ÜĖŽŅ,`xĄXv–.ôš­Ĩ-ūqâRŋЇ,›YĀ,đ&˜V\ę‡AÉ(|jī÷­ģiz: Ņ-Ģ‹ęC&l÷aų1\Âkâi,@Ū7šÍV‰YĀ,° čЋÅ,ĩpĮģ0Ž:ēãË—zA]ŨšmXt L+.5}4o1š-š–zĸuŨŦũfŗĀL- ,ĩiŠ…yfLnŊ/ĻĨžŠZáfŗ€ˇ€iŠ_ŽĨ#Ž9= Gnžh0 ˜ÆX€Á=ē™ŒiŠŖîö<ŊZu ĩÔŲlĻĪŖĀGPhŧ~ú”ũöíkr)rĸÚąŪ!¤ųíÛˇo“ŦRŪWøäK!ĻôgZ­Å&ĶpĖĩč^›§QŽGmÉŧ{ rQãj___Ĩ›ŧƒ6|˛(_;EųŪOæ˛ÉĻĨžˆKžeŧ‘äNv-fŗĀ”- ,5ĶŸËŅZúøN,–zpąŗjĩT.ßŪŪūúÕ&‚mšŦĢ˙ĘJ‡ĀGļvģͯŦΞŋŋ˙\ž<‚Į`JæüüRëMĻá¸Ũî°ągŪ?k.ú–md2īåeTā$—Xg#%_õú y_°›¯…l^`ÆIÚiiæÜĻĨ~Õڇ“đĐO¤™sąæ™ĖījB÷ķvgzĸiŖŸĢ#îÛōiŠ5Bmrûãô”EÂõ}ŋŗŗi­ŋ˛Ō!ø3ŠeˇļļXæPbœĪŸ?Ã^‹EŸžķûûÆĢĢĢgggšfŽãUw’\ū¤’ßÛÛ۞˛änũČh2 sÆĶōg_zšĘ2}ÍcŠs¨åÁŧž3ĻÚĐג¤q¸ēt:ÍZ6Ô |gÍĮ(°Km¯ŲYũŅSøŪ8ĩg\Ë]ĶY6c&öŽ]ˆUūv0-ĩô$ü–<ô¸ōßîV[MfŗĀZ w/=TÚâR›–:ÆÁOzą_ũghĘfŗYX]õĀtÔÚā Fp…Ŋ†põ‹"€Ī/ŪÜÜh!ĀGÔ#āēŒ‹žä@ÉoxebQû”}MōĖŽļ6Z˜i!Ļų–|ÄXķ'M‚G÷-׿QøŪžîŖōŌr‘’Ŋo^˛ÕjĐ,%t\íŽzoCĘ×ėŦąî×l¯]\|j?Øs”2/R Å>y§,Áŗ€iЧ ‡~ WũÁüÉ.Į,`˜Ž~<Ŧļõ|=ņ2sÛz#>’–Z‰ĪQ{īxŅBjÃM rŒqÁŽ´C=–5Ã!ļų ęŽiÎĪOāā8]/)ķû÷īü ÛíW€ūjvHî$uiŖ­œ´–ėۜdĻ=ī ų‹ ŠÚãTß;ĩ3W2Ē=Nšlų¨ē–ģ3ûøW˙FZjíūũę‰ōVˆžMßSĮü^úédŊßÅė ÍfWX€Ĩ^č%Yöš­ĨWĢ/a\ęA-5ędĨ&‚IgD|||ŦzP ßž~Õüšąą ÛJFĪøĒD܇­!2؆˛ÔČE´ØfŖ14e_^ßf¨_›û"„œœT÷÷Ŗ04@ŋ wĪK2•‹°O~Nx;ĀŲ7âY}ÚnŌlnmœœpĀŪgT{\}˛å|Āô™ú}€e] ŧ…–Zņ4¯ŋzĸ_æõq_Ŗcž‡ŧ ã)ÖPŗ€Yā=,ĐuĢgĨ]Äã¤ã—ÉD<ŊŪŽ¤ĨžĐE)7ë•ÉU*íBŠĸöįĩ¨\>ę6‰ėQ(Ļrt¤?U*Düā$q<`j•čE3 įĖÄ'0ܚr(¯ĖŦ>J“@Ĩ’ŸS8ŽĨŽš^BŽĀkTéžôāûJEB”đ¤õgÂnl ,ĄW=4/-A„MšzũĘO”LZ’΁Jô'¯íæ‹"Î^÷ŗĨöĪÃj'ŧļk9Ü˙¨yŸŪDKļˆ˜–úˇüÚ3‚-´özĢšpoŋ˛Wn5›Hûå@Ä› fŲŽWršEtkŗYĀ,đfȞĀˇå ›v¨Z×|ąũ ˜ˇį;ûŪ/D%d‚Ü-z_+IŗYíîÂŊ­‡“ĶŊŅ L/Ÿ[ōkō>ˇŽeK0´KLåÂņ:¸&PNĨ4+d,p|Ņ<Ŋé—Ō´YûœëëĢ0d¨LÎ'ĸŖ:ÉNÜ}9ę'+&‡'•ŖJĶMyUÎ{#Ŧ ŋGhŨĨ“wĨ;M›œ—åxxâwnÃ9‹5Õ,`xs ˆ–šidŽvũ§qՓÚ!ēW˧Ĩ~s']ø ŋĩ§u(L|8”i•iå,„fŠĨāėŒ#é”;î6-ĩŗËÃÜä…đk¤YĀ,đö ˇ„m•ū"Ū;ž#:cĮj™ĄvЛĩ„Zęˇ÷ŌE¯X‚¤dZWáŨ§ŸVÉVΜ[`æZę^¯#Œ´ ęžS€Č_ĻĨö_sîÖ<ŗ€Yā-@Pj˙ņmüôsyzŊqK¨Ĩ~GĩĒÍËlŲiŠw%J~Ã+ûeĀĮ°ÔÚfØhŌ8bZ4O}éi’_{Ō7Â÷ö„ē•—–‹–°÷ÍKZ„•YÍRBĮÕîĒ÷6¤|ÍÎúã~—ÚÅÅwgœJåúŋ//R ÅÎwīb­›žfŠĨöߗĖGtΆp´‹—ēvuÅ:Kô&ô:åšgžæ›ÍĻÜĨ‰×†œū-ĩÍfb‹Kũ\ũt2ŊzÁGŌR+ņ9jīŊ>N<P­p¨Õę‰Į¸`GÚĄ ¯šÍÍM~‚ēŽŪkAp~~ įm§ëņ%ĖĖ7)aģũ đ@^ÍÎk1I]GÚhĮC+'­ĩû6'™iĪûŌîOŸ˛PÔ§úæQKĢå–o~ĖjûrügŦšoIō’!š×Öĸ%čÛãŗsášņ‡cũ9ØÜÜjĩ‡ÔÎB÷†0nļ-•ϤĨN JƏqŠŨȉ“ęúæ~õDwfQuŌÕ?xZœDœQĒŦöruaȡ;Īš'Ī=^*ŗ‹5 ˜ža{‘Ė1aËõ2kˆMo­ļЎŖĨVŽvÔŪ_đ(0ŦĒŖáU#E2¸vÔ|DdÖLd, ŧæ<[ ƒ;ÁûÔĪÚ ôĪŧ(UŅnˇb`qĀĘC+¯Ŧ?%(¸ŨŠįŒIŋĩĩ íí̓Ũŋ}ûŠęƒaá~”סDsŅr%ŋûļ{–ÎpÅ=eũí!‹ždīŗS;ÆSû¨ēžņđ[Ō´Ā”´Ô‰AÉhz:qŠ'í,U÷ĀR+WšFņķŲØˇũ}}|gđÂ6/ ĶX“ÍfˇąĀ† ķ™–ZQ×ŗėŨ#ĶR'œ•W~R‘ …|~~>ԙˇļ6×××..j pY×ƒŧWÖŌā Á7ŗE@vŒ–ÚcáĄĨQ ¤øGž‡rÎį× ė™ļ8>/-Ņ6ŗœ$•J ĒŊ}™™øWŌøėÔÎ\ɨö8i˛åŖęz›~Ãjy/ ĖRKíBčyTMMžĨūqŠŨŪĸ3ĢTޏsĪá¤ûų’÷ēņV¯YĀ,0˙p$u1-ĩC-Īåéõūš–#@ŠĒ\„ąhŠÛ͆ øøøXõ  @ž}ũĒ?ņᯯ&l+úcĪŨĒD˜Bā°5D†ģ/C¸aä"ZlŗŅš˛/¯U€úõ¸šOK}rRŨߏÂüŅ– īŸ—d*a¯YúļõõõF<_ķĨŽmnma1ūbīŗ?Ē=Ž>Ųr¤2`îÁēėĖĮļĀė´Ôŋåry™Äи“ųˆŦL„Ė‚•É åRģÕBéĪ{Ī(Œ<zGk0.Ė1íe‚3ŸČ}í‡ĀpOr]nūF§æũĖΘ5ÔĖĖĒŊ­V;Ũ 6rÁŅņ1čđn" ģ§ßŦȉ÷Īyʡ4IûđnqālČ;%•ÉëõĢlV‚3ø4Û§ŊíÕāûÎbƒ!§QŽéŦPæí§yššL(LĨ Ÿ!ƒ\hŧ:"–ĶĶķÁ)’äE0­ŌđÁöT”ׇŅ”GÆĄöũr:=Pûö6 qZNĢĀß~2åĮƑvuŪ‡gķÛāŧD—"˜–žųōęJ7"ųö5ā qNw,[§sΎnųđWlļDŲĄzŖb.(Šŋ1(ÞzƒĄK„J)J)äVöđūīåVŗÉŖ+1Û1Åm!&ČBķ¨đ€Ą㛕 <==Õ+đíGÚÕūõk’kYÉåĖÍfŗĀ( |> ę­āĒäĶžŽČĨ l?ÆLäņ|gßûd HĄ÷õÆ'Ífĩģ[÷ļNN×9Ao/†Y¯É;ŨĢøxĨūÁå¸ÄT. ¯ãF'VŲļT8žhžŪôÎKi ¯¯¯Ā*֏žŨû:n§ƒVm&‡2{¸Š‹|9P ŸBˆšßUSđ uG”ÆđrÆ}>/Ž–š/]†ąā§ÁĶĮ•Š\÷ktáKånvąfŗĀs,kPŸ§!~ŽæøŖĻ,Ŋ|ZęᏘĨ jOːŲ>Ę´Ę´rÂSÔRGhŲąÚ ëߝēĘqÚĸú€‚ž÷Ģ'.´–zkkįįΟđĐđĶJ3÷iõœ¤_3=JoŊ^b4 ˜ŪÅĒŌ\ąXĻĨŽcįŊ‹~øJĄufÔT6XÆAyÉTJļBæÜSÔR ŽN„–t?ĀÔVå€č”hūw ûíÇ.r܏WņĶŖÍšËXķĖfˇ˛€.8! e=?ŪÅGåž'ŋ.ŊK).õ[ųÕc0 ŧÄ3K­3?d›+Mô!åUņŒøÍ“ōž‹VæKn›å1 ˜–ÁĘRëZĪwaécūîyķņ ä¸âLųÅ×,RũšŧËđ°Ø5šŪŨŗŒK-o‰õÁĢ@ö –zĄĩÔ/Œ?=Joũî.` 0 ˜æŌô tĖą›œ—õƒn$PŽj™ķFwÕ´ÔséŪÖ(ŗĀĮŗĀ,ĩÔB ôBBĒ‚ŠSÄI)<Š˜\ŧh<ô8~ũãš”]‘YĀ,0 šŖËdpĪøæ—­Šwa ãROÅũŦŗ€Yāš˜Ĩ–:bЃÅ‡Jģˆ.ևiŠ#ú蚡ËŌ›Ėķdf7Øŋ ZŨ™´‰É'žĨ^ržųe\ģŪ•%ÔRãšĪ#lųB€×OŸ˛Ŧã\Qc9=šßf5j}ō1.>tIÂĄéIy}}Ũ÷SĩzâK`Éņ™NރŨÚL Ĩ,ĩt/ŠwaÜv|W–NK=8õũŦZ-•Ë,žöëWģXÜЅØX@ÉÆĸáüJĖl^xŽ7OŽ>'Ĩ.d˜ÜÎÎdá=Ã")Ī­ũÅéų~ĐeĮW:ųÕMŪ_õäY,åBX`ēZjŋ: Šņ#Z’-õ#–Ú´ÔØ#âNÂMŦ‘fŗĀ€Žî‚杜Ŋn×î`ēÁūÁF™ÔRëĄ_ÆO?”˧ĨŽđiÂ˙8=e1?}CŗäĄŸéHødV.ĶEų•5˙øãÍĮ:8Ÿ?†ŊfÕĀäĖȃũ}ølVf8;;Ķ”pi¯ē“äō'•üf sOļqĀr*Ŧ€č›FzpŧOāųuj¤^ūd¯ ›Ëƒv}íNJ“üI]‘“ģģ%SÖjQöäãIųŦƒ¨kģx:›É€øš .P/Ų­CŲãŧĻZŧū—ĪŸÉB^ßū‡”‰Ë§Ž—vR/ĩÛ|Ķév˜ķPÚ,ĩÔP‹rZV WíYꅎK=]Í÷<8ĩÁ,`x@Ō,B;õ­Û‘ō™žh|ķËxzŊ#K¨ĨÖ@1Ŗ6mauUáЇĻä§ŊŊ=ØkxkH°˛ā6|6pÅÎô$oyÔ#ā$ķ)9PōFœ8Í>%Õąđ°¯ĘĪûo!x\ d‰rözj^ĘLž¤p­hwˇt|UT*íú”ĩš€ãžzÁúžUz€Ũēww\_ĘÖĶÚÖfŊõŽ,g7´.ޔ֪čuģžũ>%?ų˗ōģ]E§(jgč`Ü}˛ßĐŗÔR;–Z]Lũ˝žhZj÷øŽī÷ЛŦÉfe˛ĀM[âōēŧ‰k­é_yÄRģˆÚaXgŲAoÉGŌR+¸ĩ÷.čˆtJX4Č1Æ)B—õ]–„Øæ'¨kĪ+ŸŸŸ*†gõ Ræ÷īßI ÛíW€‘kvå$uËå€ŋNø‰ba)ĘŗÔž_§ŠzŊN2–hö+{+ŗÛwēšR՝ŸF„t:””ā×döäeBc¯­EËÎ?Ô{ßŋ+[O#5}Rô<´.*Ō+-īíųöû”pđžPįžŦ­­ią\ Čʧ߉X‰okYjŠ•ĨNŽíĸ+)š–úŅڊo{í6ŗ€Y`JhÂ"Ã6å„HnŨ鲹ĶÜDKí„Ô/ãhÛŽoÆĮŅRƒĪ¸¨Q{ī|Ŗč &€Ú Š‰A´Ŗæ#‚\ŋ~ũZ,ĐTxÖv‚'ÕÆū¸ĶiįķyĘw:úâÔ_i€ĸdˆä=§ę~`Šãk§÷ˇļ6Ÿ4 = 6˛€_ŋ|ád4͑d´ē “Ŗ”ĩZ¯×ÁPØ>{ōáŧŋŋôžäČ9™ĀŲ:šĄuqr°ũɔĘpģ2lâ+Ĩ Ķė/ŦŦ9°Ā,ĩÔîąîŠ3Ũw{âRęŖ˛wq?8žrŒįE,sœĀš`0 ŧĀŲTKšlw¯æú´‰j–N¤—4-õ‹šų螚–ÚJÍ1ĩRĒēA!ŸŸ‹apÎŽ¯¯]\ÔākZįė Oō¸ū8“ÉÂXƒ&É×ōõW.ˆ™ö īXÛ$OŦ)îč%ęõÄ'4[OÂgC‡#Ė(—9éU@ŌZ‹î)+.ešgO^cŠ@dņ—Į GNJŖ“W7´.NzlãĮ†ĻLjÜ1#mjy;š¸˜Ą–Ú¯đŌs_b=> —Z\ę¤ļoq]ĮZnXf @!ƒĄtjŜXĸŅ˛=ē÷ĻĨ~•Ž\ī‡iŠ1Ō $ŋˆŊđ@ķ}o]˛*y|ûúUâĪM -= ‹˜BĀ pØ.CßéŪõũ1Ú -ļŲh Ϥ€˛—qÕRĢžlꋧÁ'ík¤""íÕ$S>‚§oÔë}íOĻôĮ…BÁĢY†ÖåĨXŌ M™,Ÿ/ŠxŦ`ʇ÷~˜Ą–Ú)§Ãûī…Ū=|‹ zD,ĩĒAäĒ‘Wžj›ßīÖ[ÍfŗĀË-@DjļĮä]āŠŗÔŌcņôÔ/ՑëŨũHZę ũ Õ8ƒ‚éz TÔÃūŧ•ËįOŨ&a4 ŌTŽŽô§ĘAŅ'‰€‘IĨ”œ†$† æ 8†[SeŠá˜)Mq”JĒTIĻt`ē§čäyÍÉ”§ņ°ÔGGĸaÛÜÜāOʄzöÁøHÎæ$eú&‘=JYzH™´‰ĪÁIô§¤Ũô¯ąĢ‡ÖŕžžV‰øÄŎߧ,'Z•,ŸÁC“í´ãŞĀė´ÔŋņŦ2ėr~Åhhž€e 6 šũŊũŖĘ^‹īŗÍM™ 1â…EKrŧ’sė–mfŗĀĸYāĸėž›ų ē)KŊĢAn%¸;á…×öå"¸j§[R‹‹’dûįY@4î­HŽĶ÷Na1_Tpˇ^ÛĘM"Ífĩģ[÷ļ"Áë īÜčl L/Ÿ[økō>ˇŽeHR4ŦqôϞ§tōå“Ĩ~ĀÜ>^á“é-ÁĸXāøĸyzĶ;/Ĩĩ?Ķ^__ą~¸ ÷…MÖQd‡îžIõ“•Á“ƒÃܒ‰:’Ū}„mä‚ÕBáw04g3 žvúiwʴԏ¸ųEņk§YĀ,°ķ؞ޕBQM×GŒęĄCĖ/6›Žë -ÖĮËbžD–_>-õ‹]nŠ22EōĮˆ’ͅ#2Q ŗ-Q‰LR”Ģ:ĸŪ'IoiÅ3ÔR§äeĶTŠŲ@ęŠģ@×ŲÕē-ÉųEqk§YĀ,´@ۍŠåœäƒÎŒŠėgOkcNũ#Ķ-vĮËbžčXB-õ´<đc—ŠTĻŠv8¸ŦSëf­°OÉščXΰ.—c=‚xÖ8l¸¤T…I˛„Lšŗ!ëYčŒõ{MC q9š+1ŸŊG­ÔK­iVÁpuuģõ‰E $2 ­ Ŗr$oÜNŽH/“ãĶîZX˙ŽĘ ¤v”ŠI´MkLģŧöäJE×K+K›VöJívĢXÜākøūAâî3č¤įÆįã˜fŗÖKūúõ̝mŊnī„/_ŋn'šŽ\nV ,„ķY#Í jØŪĪŦķ’“uXtŖŸ‚WŪ>uõåÎ.ëĸė^HUW…öƒ˛§Y÷¸sč 6wYšųy˟ÄCË+_“Ĩ^†ŊG˜1ŧ¤Îō~ŦxįmôļĢŊíÕāûÎÚnáûÁē‰íŽûØā¤uE4Į,48˜pŧæ‚ôͯPs°ŋv! ˜ˇųΎø:<.oŊ ŒoEU''Ąëî°ãŦhČSĄ)Ëå2¤¸ŒA-,$NŽŽk0üz›ņ‡ #åˇZMŪ’4R×Y„áĻŠÜSšäOúŠXĪąZ*JĻ<8Øß܌Ôø:)ßĮŧŖUQí™ ‹GēuÅ{ŦÃÂ%ĶžXšfh]@‚ŨíífĢÅŌ’ÔĨíHY,VãËįžpĖR‹§§į´CŊxŨŸ÷s.ĢyœĪįˇÁųŽN*ī•ãōę ‹d^įöö†čâDæv˙ecŖ|p|trÔl)úZ„…"ÂÕí‰ęÎyØŊKāT}‹—š-xlÔGüGĄXŒWNš”ƒ7÷4 ˜ÎÚŨI=Õ~8ŽԚžíĒ4ēS¸ D”é˗ēP$Ÿ÷îīC¨ß/OĮ$y’0ˇcŊ1n[¸ÔŅ××7ūzаŌ3Ö}Éų üĘ đ§ ļÃÃC–8gI™+Ĩ'y§ ˙ũëÉ|J”üCƒČ}JĒŖž.HkÎx&Øķëđ¸@[ā){ęDķRfō$…kEģģĨãã¨"`÷)k5Y‡ĨoŖŪíímß*=ĀnŨģ;Ž ˆ¯l=  m|Ÿ(æZWZ*—ĄØ0Šęņ)ųÉ_ž”/”Ü9‰¨ũŦZq‹ėôĸZ`ĻqŠÅš:đĐĸ‚“ŊĒL=.õíÍÍÃēGį’ĶŅšGzØķĄįÕqŦŨf%ļkĮŠ–ÚahĮDwŪQÆ:ŗä•Û]W &ËĢGÂO3÷ŽX ãęĢūQŌĄė% míîÁGŌR+¸ĩ÷.—ø xä†ŌfŒ¤zRLãĮÃ6^sĘ7Ã{éüüT0“;ũ˛&ŧĶ!hI iíW€‘kvH(Žĩŋd7‚ū¤XÚCQ^Xėŋ„¨ĸ^¯“Œ [ū…KSšöž“ĘaSŨųé…V”%%ø5™=y•ĐØkkŅ(ņCía°÷ũ;US _W<)zZ镖÷ö|û}JxhŽĩjîËÚZ4ÂÁՀŦü•†ew ĖPK­Ģ'JÖö›~MŪĖÅū ZjšwĶЂŋģXĖfįY %ũ˜h<úč`]Cq* žč„ņGZęæuīļ%ŨęZ.X+?”1<}tW‡.œ–šĢQĀ¨ŊwâA-ĩūLt´1˛ę˛žŅŽšre"cąXˆ{Öv‚'ÕÆū¸ĶiŖî |DH=ĩ:ũ•Ú%C$ĶžΝƒSû[[›Ÿ?FĸŲÁĻ@đë—/œŒĻ9’Œęׂ—ģ\Ôj ¤c(lŸ=ųmŒ1ÉVIí z?ŗF<†ÖÅÉ–=Ž#™Rn-ß+^ČĸĪlûH˜–úwšÉEĄTŨŠŖ59ų´;¯OĐ";/˜Bû?’7Ųĩ˜–ÃĮRĢŌC;0%ˆu ÅŠ,KNø 6}įG埞Ę1ķü6ļŸî7}ō6øcõÆaīwŋĸT}ifŠám´ŗpāyä•Í Ŧŋ@!ŸŸ‹apÎŽ¯¯]\Ô@áŪ& ×Až´˜?Îd˛0Ö Iã=Ļ”#€˜Ą¨IwŦm’'֖ܡŌëõÄ'h¯õ$|6t8 ž yčIUBKnŸHYq)ËĨ8{ō‰ĸč+äČIém˜ŧēĄuqōa>Î64eōž`FÚ0Ôōvrq-yKÜįHOųnŠ"åÅQJãĩYĸqEų•C>ąH/89oŒüJĖĘôDúœ‰<.ÖģBno˙¨˛÷âRoČMO”­/î)ąˆô§'ãmķÍ­ķBP†y˛3f uķ„šõ„“^Ф™†x}qâĐ­D=øžG$U'­„ü§÷š;¸z|×ę­ė†š^kīûcät¨¨oVŗÍû@AÆųéŠP´ŽÛ“3Ŋ€qųGg–;NOü0qŠ'Į~žĪߌ*,ËKqŖ^˙Ã}ĄĩšÍ/ÛۀW¤€<.ŗö~üņ?­Ž~ēŧü †æW˛(ZEK (§’˜ķJ6ûɎÉcÆĢĢĢTÚl4˜ĩߗ’G›iz¤Q͆/Áˇ-5?Ņ` â›füœÄĪŅr@Áué*ļ™Ė‹epsŗܖ\ŊÄh5ĮüDčz9XKŋęš Õ y\•¤úĻtõ KDg(ęíQKŋ(saû D?†õíŠL´^ŋĘf]ď8Íö釈ø‘ˆSÁÅAā•AąM&ĻRĐΕƑ×*€D\ΤR›üTQiĮÅŲĶĶaȞhõ›=Īŧ=Č žˆ^ېDāxâĶX=töŋōö'@‡å$~õąJ¨xĒ?|ŧ‘Z킖ģˆŲãã°5yųH@÷Ugoå˛Ųã“(eōņ#Ž7âpv{DãvĶrĒZ—DüØŨ†s§čXT|âSŌ 9DeĒ-âĮĢēČšĖEü(ŅĨHoŒ6X#~ĐØHķŦ}MÔU‹ŽÃËÖ鸈‡Įx>üŊFZ#~DĢ'Öx PÂęû`}5Ģ,5_Æ<ē2A!!&z’ĶĨMķ“F!õĐöđSûׯÁĩÛŋbĢ'ÎåSa2 Œˇ€.ØÚ“Õ^ú@lš˙îĮVPzŨ÷ō' 9¨Ŋ~I&)îīöÚA ĶÅ(L‚Āhŗc5Äc;¤×\=ˇõõÕ KTL›ķÚVMŗ9ĮĢ'ÚS9] €øÁ¸Á´Š9áøĶĶ“?|f@~OĢj+gN, ,õy)íûˇzĸ, +¨:ÂĶ_N÷¤zĒhõÄJ…5Ŧ< §×_-°zĸŽv+ËC¤K‚­]îÅ×R =BK Ô~†ÆzNÁša0 LfY„ÅŠŨč#û´Ô k(^Ŋz ÅĢÉ0úėúĐĒ\ļ' ģlL¤!lÖrÆŅē|ZęÉyŲSĄEųņCT"¯ÜāČ5 ŗ- …Â$ĨšĒE3cÛŗĀė´ÔŋGk%JŒkēČđÍŽ;âĀ•f™F܌E-įƒų“]ŽYāÃ[ĀGŒÖņ;×ŗ=„߀Q`ģ~š¨g§;'^ȸø!R~ŖŪ­ˇCåNgúƒŒ m„ÅĨūˆqŠ?ü“õÆAFōúJ| oņkĮĪz!ã~ŧ:VÉĸ8ŠĩĶ,`P hÄh$lƒäo.-?Á1ķīÅ’JÎéđ ĩÜJąžäYŦgđôî|˜¸Ô/v'Ë8S  “ūųŗŽŪãįΟIŅL+ĩÂįĶ}ąėâˆQL›ø}á•.&ž@cŲG?\<C&ēĖ0üŨkĢ‘9ū¸˓mYšęųt k•YĀ,0ܐÅl9‡oÃāŽ×Ŋr$õ} QM‡W‹Dŧ/1#B:JXņ”´hM¨2gą¨Ņƒãã×ŅĐ÷Ë"ÆĨ~‰3Yŗ€Yā­,0­¸ÔÉõ[”Ŋų]q÷#ŦíųéÅ×ROSĢ;mõ˜ĖSą@ëNŠ‘YƒAˇ×ūÜ×zíã¸K“ŽHÍū5†[Xjb}€ĨÛwēbb]{ŧ”â0)÷rjĻGiĮõ~›–z*~o…˜ĖOY`–ZjYįÅņ8‰ūNYjĶRģûâ§ë?u—ėwŗ€Y`n,iŠĄ{ĩ˜/íWëĒ…Ĩ~Ũ Å[XjG„K™ Xđ”Ė…DH-ŨÆÄí Đ{Ųō:Ÿ:ū9fŧtĻ"WĮf›YĀ,đ!-0C-5k8‚ #ķã;,ÉH#ĸŠęQüÁR˙^eeøĀh9LéÁÜ:W™méÍîëņ,!°×V$ Ņô^ļ5ˆ—GˆkˇcØēíõîŨŌäqŸé:ШŸ´c÷šÎß;ë/Ž–Z#"ĘfW?}"Ŧ„_ļ0ˇ˛ú6ûl†˜­ßž}õ?qžĪåHŖgđOBSkˆ\ŨĀ÷ÛÛÛĖĢŖ"ĘõÜ"Įļ™Ė/ļĀ ĩÔ7€îđžŖAAe¤ôū!~5^Vũ´ĐYņĩŋøŪYFŗ€Yā, ›ÃnˇĶPa.ĖƒēöšŽû”&}YuåErjTLOÜĘyņŖ×ž“2YecŲ8æ×_oäÉūöáø]´Ô“û+Ą‹ˇŋČöĢŨžšŊ%Ŧ‹ƒhvZ.ĢoËúÛbļnmí°v ūäž´múņÅļšËĄ“üŲؐyu,CȲ#~‰īÉi)Íf¤f¨ĨV–úž×u Z¨jYģüŖÄĨ6-ĩ=Hfå´@ËČË-!ˆS†ēäå¸S—cG˜n8–úüE,õYS K”2ÁīmúM„Õ‹õņln>‚™îM¤h3ÖUëHéĐņŌhu6Îí9ã'‹?:Ēė–J,sMJŪĶ„•đ+ƒôĩ´ÍڇZ`"x`Tƒûē hŽë˙ųĒ)PWõĶō}!Oļ͘ĖC-0C-ĩ[āů ë†HŨŠĻĨNrķæ—fŗĀbY@ĩÔš°Ō CY#1L„ŸĀÚ]ĨV×r2š°K€ęģg_ÜĨãļˇÖ\x–CåYØđė Ø¯įq?ļŪÚŲ~Ž´Ô @û„cÔjWž{îKÚ§ö&2¨ZĶ –ßĮ[RŠ7›ÍÕU7ļb›YĀ,đR ŧ–ÚŊk4ö^ĖRåĻÆû›ßí;›gŦkø&åŧôÆY>ŗ€Yā},@\jY!<õS{.>ĩ0Šŧ ėŪ9oæĨKģ”TĪØ`Ĩ¯[R~AƒP߸Ųl,•%čŲôĶ#4Ķ‹ Ĩ†ŸfÃ[T­>Ąŗ“{ī+č:Ō¨}†mø[RK ¤fŨ>Mč9iŸ¯ˇÅÁŖ*aũ‘gxĒ%5 ˜,0s-uŒŲĨkđ,ĩģŨ´Ôæ“fŗĀYjZÖ5 E3ô˛áÕuīø$ŧš ‚Ķ~\?ŠûĪ”S#ŋĻ„bV´ĶŌOŪ‰Ę$„ņvĻÅĨ~ž"Įš -5ēg6žŠT­MSv9š÷ĪČÛO:ė{@ú´Ô,m= |KÆĮ4õ K `’bš\^___ 'Қj˜C ĖTK-/ÅÍpÃĐ,ĸ4-ĩôq mß:…5É,`a(jߜíĨ›°Ō72SäēÜ´ū™hz׎ˇXĖÁs—QRK^ŲZĘqÂíĒí7´h;žĐz‡7ž—–zPë<ęQ+‹įįįCŖöÎåķH8|.Ž9“,¤//AE˜yppĀôD{čÍfWZ`ĻZjÁĶ™T­û‡õ…éŅĒeįĒ_y˙,ģYĀ,đ– ‚žŦk˜ę&Cé‘ÍÂ1J/v]Së ¨vkiÔfN]šéŒn,CVf7Fą>č7 —”Saĩ+Ô¸ęÉíāŒ>WZjĪO?éßŋ'æŨÅŅû(čq@œ;Í5&r6ú 6€2ÉĀĶ—“u%ķ"8!6ßéųųÚÚړíąfŗĀ“˜Ą–šnĀŊuxūS˛‚Zâ荚gmZę'ī–%0 ˜Ūׂ—{LėŌõˆÅŧ[Í÷VĀÎAīĒf˛Ŋv= šž#”!ĒŲjĮũĐđy¨ŗķ."uĐŦ÷îīÁīĀBã§Å”Īå靯ĖU\ęÉŊ7—Ëē¸Ņ…Â*đ÷ččHŗaŠ ÜąˇˇøFŽ \渏~NæEīqS¯S‘ęŧcZOŪZKi0 øgĶ÷9>š˜wņ3w`~eqÁÃ2ĪЁãûû{ŌËņCúXļ†ŋ+ZOË>”ŊüíĮWͯĻĨ6/4 ˜Čˆ`éɲaKúˇv/\ÍKŖĸDÜoĩzŦtxŊĨ’™Ąˆ ēŲ Ž¯ƒf7¸ëÉŋ¸rŅ׎ĪFõÁ&å4o¤%ÜĮäŧŦqØI[E6>ú.qПĩz"ä1Ú7úWR0{Ė#ƒ2ûįΟĐáė}$Ÿ>™WTŨq|k=^ 'Қj˜C ĖPK­¸ÛņĶú‘ëĒ`s‡rnŪâoŧC{æĐ)ŦIfŗĀ @$Ķse3Ŋč8öV˛nø- "õŽ;2 wßėuī8G|‡r1hŪWÁęI; ˛ĮAæ0H_k.“Ü.Ÿ­Üļt“­;é'ŗNX2-õs¸j5îäųåMv1fn´Î.âG§ü]ÍIŦ}:6‰¸öŠëa˜éÕģrĻSĖŽ\Ūī. ЯĩWΚŊÛöÁĪæ~1ŧkÜÛ2÷,Ú(Cē‚ļEč!Ęl‰X܃ąN;aĩë4Ŗ>ЎõŨņ¤œiįJKm,õGī!ėú–Ú3ŒK­œ´ëúîAƒäĐ͑wü´ÅĨŽš“Ĩö?ģxŗĀY€ Š€éƒVIėÂRgŖXûĘUįX21ŪŨwNPŨ8 /ÎÂãÃđā wT O/‚ĢËđöļ×le{Ũ?nōAˇŪ Ž#œÛ:” !ŅČáųØrāi‹õņŌ8'‘iUKŊ@†5Õ,`đČ6?:H1åĐãŪ(b‡@aÁĀ°Ėą^Š •J1•Q•XĮĄe‡ŖGXČe2Ä}Đš—ŊâwĶR?Ōö™šĖ bTŅlŲ@ū̓<†™ÖacŽēˇļ&}ÜeˇwÜ~Ô{——=04’ęÜj¯°|Ųîí–všœÉ­ŨJ‡ÃĢhŅō‹Ļt ēæbXŋîĩÛį:ęÃøéÉ8éAŪZũĘ´Ô ō|Y3Í‹njŠqã"ƒÄ=š o¨jØkĶR‹ã¨šÜ6ŗ€Y`1,ĐváōŲNĐ…“Ļ7ķZjw6šĖ) ZØ Ė4ÜßYįų[9ÜŨ•ÖÅBÍ†Ė8,}ÛXM•Ûô[§˛Č"‘Y3Ŋđ´\JMkŠ Wp]…­›čzËįÚÁš•iŠãé˛Všß3ÔR+’~`Ļ#ÅĄœ1-ĩxŽ*#m3 ˜Ä¨ŸŲ˛AžēĮ"/ ~Zžįj5¨ÕäŠÎ¤z™TPĘEd"Äq{m4>†áŪ)„7k&b’NĨ\7¨TPQ‡H¨ˇSë#ę'_ĖĶ;ŋZ\-5+ļėîî~ĘfW?}"Č´_Ÿ\cHË>›ų$?}õ? Ɩ&>^ŧyY;†\ūiĢÕj_ž|!î5…ĘzÔúį ōtZ3ÍīojŠ•p܂͋ĀLÃč8–ZÎģũ"ÆĨfÎŨÜęjaw{;šĮ-ĄK:9ŠŌ==įēŪß ŦfŗĀ$ ÜÛÚJW‚EgŌŌiŸj>Іˇ-Y™ek3(•8l§…ē,ûxüÉcō–J§Áéæ}Ŗ~0aą|q´[ĸÔÎÁ;ŦŲgķ˛Īåq?vúčĻΑ–z7Ķ4www,ÎöĢŨžšŊe^#K!z_ōņ¤ũúĩĩĩS*•ô§AžÆS7ĢšņF’Ģ'˛"cš\&îõÍÍM>Ÿ÷…LŪHKi0 $-0øÔN^¨3Ŗ@z‘îpÁãRU*Åb‘>¨P,rėē1šJ¸ÎŗŦ'Ī;64 ˜ÄĖ&dË­`%˙ëãŽÕ;> ÚíķPž•Đu¸…׃nZÖ&ēAĮÅîĘUÖJÕLũōv˙üf/贃ÕT°‹d$ŨË~qxz‚¸–fT,”fFũs˛Ö‘ŌĄãĨcÖ&|ŊŸzÎøÉĸŽŽ*ģĨŌÎΎŧAÐe˙øãø‚!gĐvŊ.A]Ęū‚ũ  ų%âūÄč(ęڊétšu}!Oļ͘ĖC-0JKŨëvų×jˇģŨö]Ģu×íFûģŋôzĖvWO:Įãr‘ĘÃ=ŲŋĢ–:Ļų֖ˇD¤;\p-õuŊÎ:Ž™mäîuŖÁEzNúööļúã‡ölÉķcŽÍ/Íf…°(ĻåL!č@BKovÛ kWÁj9 ’.—‚t&â•9ØØZiëՃ6˜ûö]9”Ũ_žkIT&ŅÆ%›…õPc˛Ė(ēKÔ ēWæĒjÉnĀ\ķęUĮ-yŽË]‘äJŨŗŽ´žrĨœûTÆ5=ŖmdīŦĒuÅå?ē.-'¸īDf‹ž)ē<”ãj‘ŧÅüuE÷KíķT7Œ´ŗ´S­ËUģ!mÜ;ĩĒ2čJÅ'}cđ~9FwJÛåRČįŦåJŪßœFŪöø*â–tąjäQÉöˆo¨īģŊԃîûŖĢđ~8¤=ą/‰Í|/˛ƒ‹|×ë˛üTėo\V†åÕ=ã'Ė?Sú\āÄđČŨˇNģ'0‡íôĘÆÁ~Ļ ĄāŸIÔ¨?ûLi+č´×W­N b‚^XÎËj˙Đš0Ļ@ęÁņ=‰)4‰é:¯14<~Ė'čmWgÅR̟ôqÆŪyÁąžž>Tû1—ķzĒVĢū;š=”IÉKhôÖcšd?™Ėã-ąÔģyōîāKõn‰IЈC~PÄoüqžjŒ}ŲØ, cŠķųĩ:,uŗëĮÔÖō+ûû•īežsÆĒ`Šĩ‚yæ¤ÛÆGA.—ŖŸâŨÉß}iˆHäÔ v]ę(‹u/ŦÍvŋĖĖ^āČaНj5¨Ÿdŋˇ=c–zrDÂĮŖĀŧ_ n`ą ÔŲt†â–ēHũãĮ^Rŧg&χÎAŒ1ũƒžÔÎôG”'”O.÷éŠ ˆn›YĀ,đ" (K]+ ˇ¨›°ÔwwL5‚ĩtDS;~MešŽ“Ē]\¤WVx…Ĩ>Ē4Ũˇ­˛Ô"üX-ü Žē<ÉY‘ŪQËrûgÄĮ˜ƒôÄĨ#ÖQ9ŽŽũ‹hskķb=#vŋė~Íŋ¸yŅģ)yŋâ÷܋ۍĶË&➠ …U^Ė(,ĩø1-3ĘV†įõÄqO÷å%1Дŋē ūUO¯ųV’Y`- ĪĻīOā;´Ÿ‘sOëL'7ķįA€(ĀjËĖ˙œ><éaiŠk˛:‚ ˛’jũChŠŌÅė`v00XPx/-ĩÎD\FŦa×lX ÕRŖČ(—KQä"{Ä ÃäĄĨV–:Æā.ˇ[âŲ´ÔÆ!MÂ!Yķķķų€iŠß^XfeŗĀx-u¨ü´*Ē=Vå´ĶtŒ×Rkg¨"lˇ °ŽH7bžĩiŠMSn>`>`>`>đ6>0˙ZjV{ŲŨŨeQÃÕOŸ2Ízà PX‘…ĮeŸÍ|’ŸžúŸ8ßbHŖg¸^ĸP“k吝*– ũØõšĻnŅqŠ% ĩcĒ]܏xerå§ũ,û8.u´ L2.5ZjĮI§Ų‡Ŧũ*˜:FåĘU Zˇ¸ ģÃ|Ā|Ā|Ā|āÍ}ā]´Ô“ŋ‰eģÍō‰_ž°HøÍí-ķY QŗĶō6+÷v:ív‡•zˇļvüēåúÎNnŅmA°šËod¨Rœ šhQˇÉh)Íf~ ŒŌR‹Æ#"™…Ĩv ŋDΞōĶĩÔkųMö4.ĩāipŗŖ§)1ŌŽ ]ŗĘ¸™ˇáfĖÎfgķķ%÷÷ŌR{ÎøI0rtTac W ÍJ‡ēšŧ]=Lv‚ļYĘ7FÛīøøĐüōōrp圓“ÕÕœŦų¤Y,Y`ž-0JK­OqôÅ)ĒŖ§YãM?­Ĩvé‚ Pœ˙9@žœÛ؇å]ŲÆ[ģ¯ŗÃ›ķUfs/2X*x/-õc0<ÔjWž{îK×Į4×j5›o°ü>Ūē//ëC]˙ū}ĻqNæYÛĖS´Ā(-ĩ~ GOY"î‡ņ]õ“ZjՍô ¸ÁԂĘTÔĘčį6ģ%įKĖææææoėoŦĨ†ŸfãŨ§h}‹ëĘ/ÉŊģŖëH§ĶC_ö´<ŠĨRÃ4kJee’[DPħ’l4ĮH´5¯ąÔSÄUVÔŌZ`´–Z#s8ܛˆûĄA`>`>`>đæ>đÆZjtĪlŧU­˜CŲåäŪcˇŸt8€’iŠüø1 |KÆĮ4u’>8ØßÛ+¯ŦŦīƒŪK‹‰ėÂͯ°ĀØ¸Ô –ÚĮũО_įd\ęĮZj‡ļĐz§ }īö~õDĶRˇĘč5Ԙč yđųsąøšxxpĀ<M_,ØØå …ĪŸ?3[…>WËá|_™¤qũiīââbã˗íííŗŗŗņõjzŸæúęŠųækÅ"y)d0īÅŲŲ—ĪŸwˇˇĪkĩžŧZNģÛ­Ṽm{÷q ,\ ‹ZδYmĢvæĮ=gaŊoOo]Ŋ^χLĪŊŽņöņ÷:ēīÅ!åĶ<,<Øļ÷˙šmĩpÎ"ū‹ ’6ô>Œocų¤o÷ĩŲÛŋÅņaqÂįŒkŋ/É{­Įƒ>Ėâ¯GÜŲbáYõ}žëã͎šM,Ėŗ‰…1cˇ{įû]ęÛí„o÷•é¯ëÅĪéxû$û+=ėC^üėŧŌžīĨĨž¸ōøœŸŸ}ša”sų˙Q{ŖņĀjõÓņį>?cķņ÷EŸ#mOåčhsSøÂž6llē;K×õæę˜ëēģk}s_, ãĀQ?ā¯ëį•XøĀYX}ģ¯L]/~NĮÛĮßkė\Š`áÍÁ{÷âgįšĪZ˙ĩ'æöôŨ÷™ōĩžŸ~w o&ærŧ=9øúõĢæĶBú+6(’§9Ļ÷N֕ĖĢ|šė;‰"sįOļ͘ĖC-0JK-ą¤Ŋ–ÚiĄu†aŦĨ†Ļ–ãņqŠ%Iˇ'$ŸŌTžĨV†@J{į´$éw[;;[[›Ą 7ˇļ*×'ēxŪž›áέ­­ŨŪŪĒ íŠg"år9MY++ĐÉ6ß՞úņy2~ûļŸ_[ãNSŅ÷¸ $Ô6œTĢåũ}ee%ˇ˙íTâ oTŋēúãĶyģ×GjáÍÍ0a‚ÍÍ­Š{ëĐ~7õ6â靅‹XX¯ĢO#•´9ß*üņ#™ˇĪžú.|}đ ] ŪģŸu,üĮ`Û^ÉáŊæYƒ5':aiū1,5ŽąˇˇĮũ‚ræË–ãøK&ĒzT^ĶR?ãŪXRŗĀ ŒŌRĶ}zÜ+ü´€jaŠĩ’cĄD|\ę-§–ZWODô”–ĩČ{÷îÄ#Č\q?´|ÖíÁ.‡ĩģÂaĢpØ<ŽĢņûyGX1b‘å`’<™:cŠã9T(Ã|.§iōų|ëövđ56SßyšAbZB 'xĐÁö¨Cčy0wĢu;Š7zSBņđ0ŗQL ŠãcsđzÕÂCīu’tn0^=~Ā—3†Ë<ĸ_ ᰟ´L?¯Õ\.?Ę?ß0~N/gī Ųn1Ķ=tæû}Ry{âÛÉvōķÕUĒU}o‡ ߖΝišj_Ũ ÖËPœĖÖ,ëõĩ€ÂËË+ØŠņ*%\lÄá Y5i|ķMŗyʈûÉ ¯‡ĒåÕŠâŒ?tīî\€=†oz¯+Ái•a‘€Õjęäxŋäĩ˛Ûäq›q]¯ĨvžĘ˙ ĩų‹šĖ'í+Y* Ã7Š{~ŗq€Lī8ėVÐA§nØĢ†ŨãÁg‡y Š }Ļh§××~ūō…O•ÃĄú($M:­/ūgqđOŪĀJŠTSæ˜ņ‡Š÷Ųãã°Zīív9ų,¸^}¨‡ÚA,Ī _Eîãjķ'ũpT?ų¤}Ēbá!c,cnĪŧ§ĪõÁôĘyŪ(ygĘ×bÜ%‡ vųfdžwąŦ†÷3ŽSŽUÔÂRĮŖßÂRO ĨÖĩ]"Ëaë0ŒĸSåfÁCĖ[™ĩ†cb^äŧŅä~°HÅĄz}- ÄqĨâ&z‹¸–h4€Š'‚/ãØJNž3’Ī“ī§gsDų`Ītšw¯įöiÖ××y=đ4ļål#ôĻoČĄ™K™(Š^'{™ßĶŊ0žŨģČ&ƒŧ××bađ4Ø%1āŧtQQųOru/ŗšu XpLíģ„Ũs§ ‹\8 dúTßuáÚ­ ^/ō Ĩūųķû÷J6 &Ē.ĸgÄųu¤7s§đäx9åI}x(˙lߊøƒ#P_ÎsOūL=9>ž‘–Žū|Ô6oáÁzÅÂąFŧŽ…Ŗ.bÄøÛcŪúÅĪéxŽ +EũžûIģ õˇq÷4î3šųųaŠ?Ô°k1 ,ƒFiŠšvĮR˄B:p'ũŒMzé?ŨBäüiŠãŅŗ‡ž¨K †–ŅF‡Ē#œî´#¸Šķ=r˙Ū„{žœ3 Œk֘ļņîZ枍)Û2%%ž^Ž9C-š••f|^fįrÚduuĩĄ3ĮŨũФôí ĒfŪ#T.Å2FųîĒkŦûō{°ōP eĀ\ĢÕö×(Ö ōÚëúÛxŽY4ëåqĒŋęÉũpB äū2žÖ‹ÅMâL#’w˜Ũ— ÅÂÛ_EęB5šÜ*ō=Íņįį>×ã9T”ßC5žÉö?ÉsŋܞÃú¨G/oîÄŊX__S Ö;ƗPi,ĩšX8—KŪߏuŒˇˇđœŒxģŊ‹–ÚXęGîm˜>–FiŠõ*u–DŦ!÷ĮĨvsũ›=ÖR;1u„§ Kí¨(=~ĪäķŌī5Âŋ\ &Ø\ãĪūë?ĸŽjWؔ#įėSŒō7ä"Ü^ĢŨVäA–ōŪįˇvwEžáfˆĘŧąÍÍ ųĻš)6ˇŒƒ2ãļyG" ¨„w3ļÃ%ÃīŅ›ŪëmH8ûēũ—!×YøĘY¸+Sé5*‚đ Ŗc;ĀõđđT-nˇÉR.‹…'ŅRˇųû×Įs¨ūizƒg¤0kVWā\/í&œ=žīĨŨ]ÎAŧŽNŨņđÔm'€ÃããVĢIŅxÔņq…3Ø ÅoņC|?ğûüđIsOųhÔižãËtoÔ˙ôĸ°ÎŽt Ãގ´[R “@,\ 'ÆR†sŌØ“.„ZøØ=Ô×õâįtŒ} ąpŅYxNÆüŊV~hžĩԑØĖfąĀ(-uÜ|ÁĶÉXŌ ‰K=TKí”"AĪIXjAåtã/ÖíiiėŪ‚“›v]{éRQĐJ§‚BϞ9D3ËeA¨g“ŪØ š4o}TËzŊcø6—ž•xŗ ›uP! áČEŧ€wyĄ2Q‰ø!ƒvûúuˆN—yâDŲ+—™›Ĩ°ƒŧ~î?ĨŅNö¨#(\ˁ–úž‰ŨąēÛŨûŊƒÁëÍe#  oJ¨)gaõŌQ~%.y ˆ…ŨįIô CcĒøŧ}ĪB2~ZvD1Ūį_Ė/>÷9ę¤ēÁNĻ1U/ÜéōQŅ×6âāâēH2q™K'Ą}sy~ņˇ\ą5ŠĻŌN‰3Ø/tú"€ú.ތ?ļyT<ŠĄ÷4y/ĸoŧú“'ĩÂSė‹Ú™ŪŲÁ{éS{ĨRÛ}÷•[ø‚‡Ũ[xԜ Ÿ7ļđbkyfwv°gōŪé“GÅTÚ/%ûĩđ“öyãqéKMKŊ ŚiX ŒŌRG— x:Šõá†z Kī7–úˇÜJîĻq™"eņR ͍¯ĖŖĘ †ūæI pŽ:ÚėX av0;˜˜˜ĖČ Š˜NĐ~] ÅÛyģÚŨ-„{[–G™âəi?ĻhO+Ę,0W8žhžŪôj%âD<ŗãJårÄY€Ē=âPu€ŦúŒŠk˛’Āá ”G3šv'ŖˆÅ\PX-ņÕÉätö‘ĸšŲF*3^mã‘V=ÅKYŗ›ų€ų€ųĀöų×RŖŌapæS6ģúéÃb~}r]īPöŲĖ'ųéĢ˙I§Đ$7ŋ&"׋Ú'žā+I’åčņ\ĄkŒY`á,0JKũOĢ‚#Ą×č‹K=\K­ņ=$'AĩTIÉáRkПÔ}b%KķfēU÷¸ž‘FÖę2ß6˜+x-õäQ\DwlŋÚí›Û[č+ˇVŽëŗXéP=”ĩũúĩĩĩ#1 ŨæŪՏ6āĘļšËA˜%ã™$ËŅ5'ožĨ4 ˜-0JKÍT!ևŒ‰E’ü\މâR |ŽįøƒĮ]|žˆĨ^Z-õ“šB,fil Á|Ā|Ā|`Ļ>đ^ZjĪ? GŽŽ*Ė€a‚bhæ‹ëš\oÕ¸Đ63b´Ũ_°ÎÕfš)VΓM˛fŗĀx ŒÖRģDN朜÷ŗ×ÄĨV‚Zæâ ķĘō‰.ę–cŠ‡Îŗ6žĐøiķķķķ7đŪoŖbOÍ4.ĩጟ„&rņÜs_âžęTām÷ÜĮ['ķ‘}u] ž|ôd“,YĀ,0ŪŖãR‡ƒą>\Q"ō˜(.ušš¨d˛š jéMLKm<´|j-jĖãíŪ™| xc-5ü4´’j õÅŦë“'÷ū…íä5dŖåI-5šČ*šÎsŌ>[´Wüw’Ĩž• ōēM „eÔ ¨ļ™Ė/ļĀ-ĩõázûNąPڎDŌnɗuœF€ßEä„v4õCĖTĶRĮũžqQoĀEyßŗēĖßĖĖúâpŋe\jtĪlŧEU­īle—“{˙.yûI‡}/ø>-5ÁLGo÷Î~”{(Ođ ĩ‹o›YĀ,đr ŒŅRģūsË:ċMG,ĩŦŸ0bõD—˛÷ģjŠúvå¤*w,ĩiŠÅ2c×Pü<]ŖŨGķķ9ô÷ŌR÷qÆc^Ũ„ü?ŧ:ŊOœdšûJ & Ž‘Š›Ŧ˜ëĸ>™WâãNŪ¸—CËiøČŖĨ–Ú]zĖRG(KÍÃG?ąÔņ,D-Í}?bŠ%(u„ÍB7-ĩŗĨŚĐQJŗƒŲÁ|Ā|āM}āŊ´ÔžŸ~V|˙ū˜w*qæÍʁ,i¤=æhėKT6ˇFĻāiŽû¸įd^ TÍ t8)+cПŧ-–Ā,0ÎcĩÔ,ĩû‚fŽûĩÔ1ō~xZ{žĨ–ŽK[Ājäō‡ŽcqŠģšCîJ?!ŲYÛĖæÛŪXKũ\$’Ëåc°šéƒĢ`ßŖŖ#-d KM`ŊŊ=°2rmBYsĖĒjÉĒ“yI j§üímV)ŨZ[[n#-ŊYĀ,0ø|ųw‡‹tׯˈ¸įxEa Û1 Ĩ&ŸcŸUoí´ÔĄjŠ5:5“Z –5ĪÚt~Æ×š˜˜˜ŧŧK\je…'ÜÖÖÖĐ~0}I„IÁ4jė1% Ėūųķ't8ûÁ•“yAÛ„ÕŖüŸ?ë}Č{ÂZ2ŗ€Y iņqŠ5žG„c<,a;DũHK­ėsDņŠ–ē°Â‹Ģ ŦíĩgŠMK=ŒûؘŨ_ģŋæsë}Zęä<ũ1đë҃­FūzZ fšĩĀ(-u"žG4ĘäšjeŠiŠrŽŲgŊV´Ô)”A(a€ø)ÁU›–:ūÔp,ži(ßTCi6×įĶė`vXfHjŠyˇÁÔĻDĪčFSg9KīY,õÜâk˜YĀ,0ÔŖ´ÔQ¯ĸč7ÉB3KđĮRGQ§UÍĄrH1âR‹–Z&7 ?:l-\ĩ›îhZjãŽæ–ģReg÷Čė`>đą}ĀkyÛ­ŽŽjdĢčM63ŧ`,õĖLk›ŪßÉņ.ú“>-ĩÆÎs€Y#Rk—3TK­ąö\hHK­úéLF~ Ķ‚ŠõL"ú‡q´Æš˜˜˜ŧŊ$ĩÔ÷3ēķ°~ÂĖ^ÍÆRĪĖ´V°Yāũ-đ„–ú1KíbßI›ĩÔ‹:úĩÔ=ķ/-Lļô_‰}XŪxAãGÍĖĖĖŪÆiŠ{=ĸÎ!üĐq ĶRŋ?0ą˜Ķãĩԉ8Ķ^¯ąÔqŠ…Öų‡Ē !â‡S}d„ŸfE™ÍķĶ—Úy‹éYMGn>`>`>đ>ÔRķf* îõdZęÅ2ÖjŗĀ|X`œ–úŅjJ.F^ÔfâRōC0Ą ϧũĄbn'ûp?\üi&āĄũÍåq‘ö”!0>æmøŗŗŲŲ|Ā|Ā| Ī’ņbŨbáyĶRÝīîî~ĘfW?}"Č´_ŸœĩĘŗüŸ}6ķI~úęâ|´ C…š\É××ן?vĄ¯ ēŦŒmfŗĀ‹-0NK-Ęi?æcN ,ž—‚ĸŖÕŨąjBTQ­qŠĶúwĨÄâR;+ɆÅú0;˜˜˜ŧ“x-ĩøņZęÉ_ÕwwwÛ_dûÕnßÜŪ2¯‘5;-×o‚O˙úõkkk§T*E¸y %¯ØVs9”ÜÉx&,¯ČZ0Dŧ&āIĩZ5÷äwĮRš†Z`œ–:VNģ4bVTܧĨvĪŦÄd…ĨÖųŒ?Fĩ$qØ`>`>đÆ>ę¸ĒÛܲ‚rœäq§ŽƒáqÅ×jWž{îK××ÂZ­æcķ –“ÔQÉŧFãĻ^GōQ,Q€Lũb­@ŗĀ˛Y`Œ–ÚĮ÷p8ÁRkoˆ–ÚãäˆŦ~`Šo'”ĶĻĨžCÎÆ}ɝŗļ™ĖĖ–ÁF—Î(âü4˜]5Đ 5Tk‘Ü{ÄOËBiC6Z˜ÔRŠONN4į¤}ļž…k’W‡ŪŖŅl’œžžĒ^6üg×;u ŒŅRģ11…ZĘ@GĮ<´ƒZę¤ęÚkŠŨĻÚ=éĻĨ6Ũ¤qķæææsāŖÆKgÄRŖ{fã5Ēh}—+ģœÜûw<ČÛO:ė{ņ÷iŠlŒßîĨũ(wōę¨1ÉĘĘ 'ŽŽĻŽ0Ŧ@ŗĀRY`œ–Z!p<–<†ĨaO#~x‡ĒŽuæĸcŠO?bŠMKm|°qáæææīéI-ĩ’EėŪ@KŨĮAˆ1ÎĪ·&ÃŖįōy&ú\s&YH2/*ędâq¸|Š`‘]ŦYāĨĨĨV(ë˛Zj‡‘aŠ“qŠ=fŽK“Ėą–:ŠNŗ¸ÔsĀÍŧąf_0¨ŲÜ|Ā|`Ž| OKlیXj}G{~úÉWö÷īߙ;¨íxŗrđõëWÍ5Ļ…Da#úɀË÷qĪÉŧåry_bķQ>SųķÉVYŗ€Y`ŒFiŠõģ=Áa?:îĶR÷Īôp;ŌR밓iŠ—A›h×h\ķķEņ7ÖR?ˆär9ôÍl.hô*rg´ZČ–šĀ ÄÅ|#×&”5Į›››ÉĒ“yųŠT*ŗ/ Ôâį8>ˇŠ–Ū,`H>›ž”8ĶQ–Ú…ũP–:Ū?"lŪ‡åI8W†ĩGŲŗƒŲÁ|Ā|āCúĀkŠõeøŦØĪļCûÁ$ÂÛÛ_IÁ´ N2r˙üų:œũ JîË 'q¯ĸ6PhxŊžŌRĮë´ŧJÆž{ŦĨ~Đ[ģ‰}ZjÁäŠĮ-.ĩNąØfķķķ÷ķ÷ŌRüzÔb%˜æÖŖ´Ô>‚G„ôq?7Ãmëę‰iŠe>ŖiŠ÷ĩXæææīíīĨĨ~K=ˇ¸Áf0 ĩ˜¸Ô7{ÖyāXčÖ(âG"Ĩ†ĄĨž× ņšUÆ×bEŅZ;mlÁ|Ā|ā#ųĀģhŠĨ6(føĀ—:BŧqÜØhĀrßĒãĘR+NŽ÷ēšiŠŖ ͈ëG–ŲÁė`>`>0G>0˙ZęŒ<ėŌĖŌãĩÔ"īˆã~Ú­:Žŧĩ`뇸ԏ´ÔqŠ#fÚ´ÔÆI;W0nŪÆgĖĖŪ×LKũ!]”Yā}-0FK íÖmqq?„zV–ZÕ˛7-ĩqNsÄ9n1sĖĖ&ôĶRŋ/ō°ÚÍŌc´ÔŦŒí9é$?-ĮĻĨ6žé}y&ŗŋŲß|Ā|āÅ>0˙ZjVlŲŨŨũ”ÍŽ~úDiŋ>9 ‰gų?ûlæ“üôÕ˙Äų>¤B=Ãõ˛v š|‚d9ZڇD9vQf7ŗĀ-5,5¨ZŸDëÃŗÔēęø0-ĩ†Ę3-ĩ˜Î4Ä›Üė`v00˜/x-õä¯öģģģí/˛ũjˇ‰ÍŧF–BÔė´ŧM|éN‡āĶŋ~ũÚÚÚ)•Jn¨ ^9ø˙ˇ÷ū0q,]´/į<iÎ‘ 3Á † ¤@f‚`é8ƒĖ„á gâ 2ČLˆ3Žd2béAp%Į™ Žäy’ƒ÷ÛĩĢkjū5ƒ™Vßi=ÕÕÕģwW­ZŊj×tĩJŧ‘|Ŋ+Ą(‡ØīŪ-õ^=å”dv ”kŠGƒøŖS܏-ĩâRK3bėžS|Ũ‹žŠ|āŲøĀ ´ÔŊ3ÁÛÛ[Ë++īßŋ÷>˜5Y>ūqs‚ÉáoĐ6Ëĩhģĩ—÷‘ĐüëׯŨV^üôéĶÆÆē@’, ÜĮŨ´ÔVæč蝂ĻNą>|%EiŠĨ!ļvۜdĐņeU‡đūëYˆ–ÜÁĨĨnÃe}÷ņņIâž[ōåL3?§Ø|í厑m-įú/°×Ŧ^­NŨLč\Y@čĻĨ6Ëüú5ZĐÔ)և––ZüĢxhų€|@>đ„} ĪZjøi6úĪ\ĩė+ŋäûJPdŒuÄ(Ô<×RŠ÷öö  ąôYKî™ÎŅ5ĐŪu;ģœīS—ōN“[úų-õîîn7đģėėüv–úû÷īœ>5%ŠúžpJįˡiŠMLŨXG<éĒnnÄĨļP{16ˆ“ÃĖÅŋ#ž6vĨHÂæ4ŗ-Xž<Ō)>b1Ķ3}žŪGŊĪØĨĨnáŒK ČÜÜS;fčχ&sujęōō2Eš#y!íįnmmmnn ɲĀũ-P—ÚP.ڏ"îGXķÅ;_QqŠÍŌ/ŪAŋ˜Æg˛›4čōųĀ}`PZęÄOßÚyoll Į8::rNŠÄęęǟÕQí?„č{¤Áͤ[ārËšĸ¨o}Ę ôn’¸Ô†Đ~Xtęø‡…_Š7Ú°u…_ÃNJM,u[;Køjo Ąęg8ĮCĨeų€|@> čôYKŨ{¯ė9™-x‰ŲŲiôÖÛÛÛūS KM`TŅ€o¤Û„˛&Ŋ°°_ēå\ĘLąųîZCå—d ”ÄĨļœa†ĸ‡ũ(VR,Ū耖c\jiŠ1†8§rN˛xEõÍDßLäŊú@ŸĩÔŪ‹úLġ™™´ĩZíââ*LŖÆ.)eöˇoß ÃŲ§H )Ëš”ĪUzŦ˛É˛@šĘĩÔĐÔ)îGXI1Äúœ´´Ôâ×õmA>  x‚>0-ĩXę­t›/ĶåZęđE¯ˆûCTœ´´Ôâ“úÃ'Éβŗ|@>đā>0üZę— JtײĀĶĩ@š–ÚįyøŠÅBŠ´Ô!H ŧHKũtQ‹j. ­J´Ô6†÷zGEuŽmnĸŨNZjÃĖŲꉮ ‰ZjاĨĨVLÅE‘Čä÷iЇ”¨b˛ĀĶĩ@‰–:āæĀSgq?Ŧ/đãIKŨÄO‡ü@qŠ3Ŧ­¸ÔŌ^+æŽ|@> ~-5+ļ,//ONLLONd:­OÎZåüĮ~b|Ō~ZM?qŧސ'˛aõ:kĮpVĘđãĮÕåeâ^SęļūųĶÅ7Ēš,Đg ”hЃŌ,đÔY܏NqŗGü+ēd,ĩÅ×#O`Š‹Œ1ÄšFdā\… ˛z*-ūL> ŧˆ–ē÷ŧģôÎļĢZíėâ‚yiMj^#žôΟŸžēēZ\|ŋ˛˛R|EnŊ‚wâlĶÕ*ņFōÕ×ÖÖĻf§îŗŗŗŠŪ+М˛€,[ TKąÔ•'GZ=ąIKmš~,uČé˙fÛūíÆ oĄ:ˆ?“ČäĪŪĨĨNœņ­@d{{kyeåũû÷ä¤÷dYÄΟ?Ŋo“Ãß íĶĶS˙)DYéąĮšũú5_=‘ŗÖ×7(œuĪ‹Bn­›2Ȳ@G tĶRûÛYŧ}ėƒčÔļ÷XIáÍÍĩÔÎ>wˆKiŠëŌRŋîG÷(ŽS> r”–:qÆˇ"’ãã“n´qÎ4SÎņņqŠÍ×^~úvnĢØ Ā÷÷÷ķĒ×?}ú4˙nūÖZ)ƒ, ”X ›–Ú߲âíkŦĄXhŠ|Ž€˛ĪŨČßQKmŧĐU;C`ÕR<×'ĪUžüV> xę>Đg-5ü4}Ÿk ŊWö•_ō}ę­ŅuŒucÂr-5zoo/r]m'´đÖ9KŊŊŊ͉ԧZ`erūZ’dûX LK]ā^ã§ TK]´BNA´iŠžî¨Ĩv…ĩ´Ôái™ņÄcÉōų€|`P>Đg-5ēg6ēA×@{ˇíėržOŨ9HˇÛ|Á-õîîn7đxĢ&„ŗÔˆŗ!ÂkĩÚõu ]5Û}Ā„Î•d-ĩAā` ‹Km “,WīĸĨ ÛŅļ´ÔâāW>  Ŗ JKŨŽuî†Bæææ`Ž;ūš3Í-ĒSS———é iŽäyōs‘VŖ]K ļū(sm˛€,đĮčŽĨĢ$ŌēŠÚXę0ĪĐã{´kЃJ$”ãëĩĮĨ––ÚÆ$6FQŒŲA>  ÎĨĨNüô­}öÆÆ1īŽŽŽŧß%ąēēęgĩhŠķĸ žŲâÁAđ4éÍÍÍ,xŪ>Đg-õ]HĩZ=qŖgg§Ņ[#}öBJXjƒģđ\›PÖ¤ōKįįŖß\hzÚĘßß?¸k%•_Ú߯Ôw´Æ™N,ĩqՑĨŽs ĨĨßėŒ‰ė ;ČäOÎúŦĨö~×g"ö¸ÍĖĖ ũ@ë|qq• ĻQc—”€2ûÛˇoĐáėS$”??6ų(ŸāÖ`÷2Av5V6Yāe[ DK ?ũ˴Ԏ\ŨáS;ÆĨ&—´ÔAüÖķæˇô|õ|åOÚĨĨnĮ¸/čîegeŌ¸ÔFM‡ÅČMQíXŲ"åy˛%.u8$-ĩtŌaė%Ŋ¸AeŲA>0”>0(-õXęg…5t3˛Ā °@I\ę@RĩÛÁc}Äš‰qĸÅĨö¸ ¸ĨIKíãqÕ˛ƒ|@> Vˆ–Z,õ €Uēŗk’¸Ô† aŠķ!°ÔĄŒ‘ōL{0t<^ ovˆøQđŽZS\jÅúw+Č†Á†_Kũr‰î\xš(×R[Ŧ¨ę‚‡âŽFÜŽG–:w>BqŠĨŗO/Č†Ö¤Ĩ~šˆEĩ–†ÚåZjWQ{ŋĐā§ãZåv<˛Ô’––ZZjûPÁâádų€|`h}@ZęĄ&ǜ,đ4-PĻĨÎc}Œ¸NÚņRH§¸Ô -ĩsØĩÔž6LĄ‰qø†Uc'nihš%ķ@éÔ}‰aŲAvÜÆ_KMĐčåååɉ‰éÉI‚L§õÉYĢ|‚˙ØOŒOÚOĢé'ގ@ōøî—ĩc8+Ī@hj‚Rŋ›ķ5e´É˛Ā},ĐMKmeą>œ{ÎøėNqŠ ],›'0ZjWŠ8ˆ;iGÄecqēâļåōų@_}` ZęŪ{ë?~,ŊŗíĒV;ģ¸`^#K!úéÔŧF|éŸ? >MHéÅÅ÷,'qsÛ°Ëļéj•x#ųę‰ßŋ????;;;8:ÚÛÛ;>>îŊzĘ) ČíčĻĨ&gŠõ8ip|™ct濏Ô1YZĩÔÅ íĨDl^Ėg,ŽQ> ČúėƒŌR'ÎøV8˛ŊŊĩŧ˛ōūũ{ĮĐ,‹Č˛,7'˜ūmŸžžhģĩ`ī¸Ų€æ_ŋ~ÍWOÜÚÚbEF ũú5éũũŊ[kĨ ˛€,Pb-ĩE¤gN:D눈ŲXęÖ¸Ôq­r˙"mm@`Š‹y‹^ŠëęēqŌ­î§ĻēÉ?åōgãƒŌR7ƒá2pr||’¸į–|9ĶĖO°Ë)6_{ųIËČĪÍáõ›7oÎĪ/…–dYā>(ŅRį,ĩchÛÂz.´ÔqÎbČ€ŋ3ĨZDŌŌR‹‹ę3GŠ ėŖaŲAv>Đg-5ü4}Ąk ŊÛö•_ō}ęÎŅut[!œšįZj 5˛ ?1qŌŠœØsį0zcccss“#HąWWWãĸn÷:WxŲ(ĶRw‹õŅ).uĄ÷ˆ+Â4âR3}Ūĸ´Ôiާxߨū?Ū‹ûŅŊˆĮ•<čŗ–Ũ3ãZ×@;đpv9ß'@ōN“[PJ‹–zwwˇøœBĶŲ9KÍu—–lĪĪÍÍŊy3Ķ2sņeC#ŨŊ,đ'(ŅRĮ1o\ŅWI4~Úŋ˜AyåqŠã˛Šöū6ĮĨÎg5JK-ŽV\Š|@> ¸ JKŨ—tÚĀÜÃÃÎrĻš%Cujęō˛Ąß ͑Oõ”¯>3”–:ņ͎‚TÄŧķØvôÁ$ĐføY-Zęŧ(ĸ‚°}ƒāiŌH;ō ųšæ#č‡įÜÜ\_[[ģĩVĘ Č%(ŅRûĢIįvéFl‹ÂĸzbČą¸ŸĐAKmší$Ī×2/ŌJÖķ1VÅ{Éäōų@ī>Đg-õ]HĩZ=ÛÄÄÄėė4zkĸsx!%,5AÖ××ßČĩAˤōKįį"üIS>9‘UĪĖĖÜĩ’Ę/ ČíīWj‡ĨnLqŗŖ+2Öņ•î—ÚQ¸ƒî—:ĶRۏŠKėgßÄ{Éōų€|`P>Đg-ĩ÷ģ>ąĮ Œ‹öŖVĢ]\\å‚iÔØ%%”ŋ}ûÎ>EIųķsÁߨ>(?älBŪ=ÖPŲdY ˇĀ-Zęf–:ĶsHø'Zę¸ÜK7n@üJīüŠl%[Éäō?öAiŠÛ1މ, < ´č/Z´ÔQE‰i×k؝âRĮ™‹ŌR+χ8xų€|@>0Ô>0(-õXęgƒ3t#˛Ā ą@™–ēˆõáˆÚc}¤´¯į"-ĩ™A\ŅsE>@“ eų€| Ī>0-ĩXę­t›/ĶeqŠ›b}ØėÃĸÍü‹íęÆjÛ¤Å;OZjé¤ĶC>  Ŋ ŋ–úe‚Ũĩ,đt-PĻĨ.”Ķ>Ã0Ѝ=tüÄĨ––Z|ļ8ių€|@>0x–úéĸÕ\Z ”iŠĨvbÚtgžöÕZŒ¤ŽXŋYęđKT†Ä!M?‰ŠK]ûšã?¨9ūēŽ|O> °ūŦˆîÜĸ}ôžėņ:liŠĪļ*Y¸J´Ô)žGø>–ąÔh[ۓiŠ‹5bšâRx<E—zđÜL1*OĻg!ŧh~-5+ļ,//ONLLON::­OÎĘáüĮ~b|Ō~ZM?qŧU‡#@yV'ķä$Q¨ųŋ|ų2ļũũũÃU@xę(ŅRˇÄ÷h0֍Šė=kŠ3}‰ÍsLL€øqfōų€|@>Đˆ–ēwÄđãĮ@0ÛU­vvqÁŧF–BôĶŠyøŌ?|úęęjqņũĘƊ˙TÄh\Į{î/ûû+kk,<~uU›››÷…Y‘‘Ĩd@ÛlĮĮĮĀëŪ̧œ˛€,Đnō¸ÔūŦĀĀ´Ŋšõ‘ŅÄR‡hņeŽ+ÅÔ˙ÎÖ1ošÛ¨¸ÔŠqĄ)ōų€|`€>0(-ĩsÆŊlÛÛ[Ë++īßŋ÷>˜eY>ūqs”aÆb@Û§§§Ún-Û)°Ī,Ŗč}9eēūdoookkk,lŦ[ŧîĨbĘ# ČŨ,PĒĨ.ôĶa9Äâ%ū-ĩMt”–:<ƒĄŽÕJũúĪÉ&˛š|@>Đg”–ē —!“ãã“Ä=ˇäkQ{C0§Ø|íåˇķ֗——ŗĶĶ”‰°djjĘ gĨFŽ *ɲĀ},Đ[\jø‘>iŅđa'-uhë9Km 6ëÄHK-íæ‹Ön“ oŦŊ˛Ēƒė pčŗ–~šÎŅ5ĐŪm;[œīSwŽŽō¸cīNÍs-5žŲsļĪŦl™lÉšH6ˇļČĖ%rtūëē6Y@øs ”ÆĨŗ#|¨Øūˆ,u{\ęønžļU74ĶŚ1(E6oŸg-žĻĪ< S6—Íåō—é}ÖRŖ{fŖst ´wÚÎ.įûԙƒŧĶ¤Ã–žEKŊģģÛ |Įîģ8ŸažRŋyķ†cāōø:d%„—6Y@øs ÜĻĨą†|äkq?ņ…ÚĩÔ I@Ū´Ôŋb4>[ĻËs†¸ņgōų€|@>Đ”–ē÷}sss‡‡‡ûöˇd¨NMåú Ōņ<¤‰E¨ÚT§Ēįįįž&‘D (tĻ,đ˛-PĻĨ_Ɯtļ-°ÔIŠu§¸ÔqŅįBēqâŠ^&W¤įŽį.ôŲĨĨNüô­Āccccgg‡ Ąķ­“X]]õŗJ"gƒ˜ŲI;†6Įæ&iÄ!$ˆ”‡f:]šé‰P×l$ܡÖJdY ÄeZęNą>cėģjŠŠ„s§7Ö7?-Ģ|@> ôÕúŦĨž+ŠVĢ„ā`›˜˜˜FoŊŊŊ]Đ[íscņY__|#×&”5iį¤WV–)aff*Å´æ ™Ų°‘ÍŖ‹h“d?ļ@™–:Ā_×{X˛)î‡S Y\ę4QZj #ÎŠĪœ“lîė•ė ;Čzô>kŠŊ“žĶę‰0Ęh?jĩá¤sÁ4jė’.|üíÛ7čpö)ˆ+šã>Ä´öVŸ…­[t‘?Æ:Qx(×R‡hĶÅÚ,ŲÜBÛMqŠ-ʞCoSJKK]ØBq÷ ŲAvČ†ÆĨĨN÷ĸ Ũ˛,đė-PĸĨ“Ĩ*ęČRĮ•Éī¤ĨŽÚ/iŠÅ#Šŋ—Čä÷AiŠīÄR?{üĄ”ž™J´Ô„¨ Qúˇ2ÛŊÆĨTwƒ§”–ēķŲã÷‚Ąá„TņôōųĀPųĀ@´ÔbП„ŌíČšJ´Ô°Ôø=˛ÔQĘYę;hŠÃųA劸ԍõr¤ˇļ1™y…l";Čä}÷á×R ŦȲĀ͞@‰–:°ÔļYž/ĻÎnŌRģēģ–:hGŒĄQ\jH(mąė ČįŌR?-¤ĸÚĘOÂeZęŅҏ –úIURxZ(‹KũëW\ Ô0tÔn„oõ1¨ž´Ôâ›ûGVt}ߐČʆ_K͊-,ŋ2911=9Ié´>yŠ-MđéIûi5ũÄã-„<aZäŌģwdžœœČķc”ņ<ÚdYāž(‹K Kũ+đԅ^ÃØéĀGÛŋŊĮĨ––:<$醃ßČ}׌ƿŌëËÚ}` ZęŪ;ė?~‚ŲŽjĩŗ‹ æ5˛ĸŸNÍC˜i‹3}uuĩ¸ø>E•öāųæņžėī¯Ŧ­]āúĒ677ŋļļæyXP´VEîŊzĘ) Čí(Kí,u#ö]ļęiŠ‹8|ĀĒ,ĪeŠ{P9âąäōų€| ÄĨĨîŪŪŪZ^Yņ é§YæđķįĪŪ…GŊfҟƒļOOO ´ŨÚË;“ņųā€õŊŋ§Ėˏudž~ũęy´É˛Ā=-PĸĨ6”‹öŖˆûÖ|ņFÚļ?ŌRˇÅäw"ūL> ČúėƒŌR÷Οt[Ұ`Âbg|||œbķĩ—ßÎ[_^^ÎNOįĐĄ=Ī=…N—^ĻĘ´ÔđĶa†ĸĮũđy‡ņŖ5†9ˆüJŦ˙ŨãMGaHŊūˇŖõ"_ˆøá3Cž,ī¤kŠá§ŲčûL ]h—-Î÷ ˆ ëëˆK¨y(ĖĘA ¤ŪÛۋŨtÛ >ģ觐lnmå[ōŧL0¤ģ–îo2-ĩ\›Ąã~ĕ‹kJK%Ä-õ™[’Í}x+;Čō{ú@ŸĩÔčžŲā\íŠŗËų>uę æ4é°Ĩ§oŅRīîîvßvbFAS Ė7Bę7oŪäeöΝßs¨Yā[ \K Mí¨ÚZīl{M˙$.ĩ´Ôƒ‹ÃÚV}đh5˛ƒė xŅ>0(-uī|đÜÜÜááaGđŅĸĨÎķT§ĻĐu¤#¤9â’&~5ĸęVŒūŒ1ŽnMčŖĘĩÔāiPuĄ¨6õYTwHK-~Z\Š|@> xĸ>0(-uâ§oíå766owttH€:‰ÕÕU?ĢEKbf#úžchĶxln’FBbffĻũŌŌRßú8”AčÅåZęŠķĶŽ¨ĩ¯Íq={Éí/ĶRÃniŠÅ{ŊhŪKü7öWß@žŠôYKŨKÜÄ7WĢa›˜˜˜FoŊŊŊíJXjƒŦ¯¯ž‘Yƚ´sŌ++˔033•bZį×ę;ŋë](ŋ,đĸ,PŽĨöąkdŠcˆę0Ņā´ŊŲļzĸ­­h鈭탚íÆę‰ŠK &Në‰rZ6˛T0(-u¸/sčÆegl-uCãÕn†H¯).ĩ…×+4!!Ö5XępJˆQ ¸ÔΈãß,ŪZ> xĸ>0(-õXęgŒų€|@>pˆ–Z,õŗRē)Y rÎÍßŪ3mt`=gq?K--uāŨĨ­ī.Čž  ŋ–Z0Ež–JĩÔŌÃ<îGc­DiŠÅ Ũ…z*qTOiÜåĪŪ¤Ĩ~ZHEĩ•ž„JãR,5‘ąBÜÛK15iŠcJQKKmO^ļĪm•dų€|`č|@Zę'PTIYāiY '-5|tą†bg-u¸gÅĨVü ÅĨ–ČäOÆ_K͊-Ŧw8911=9Ié´>yŠ-MđéIûi5ũ41>ŪAČÃĻE.Ŋ{GæÉɉ”ß.-÷𪄞Ž×ož|Qmeaŗ@I\j˙2ųiÕÆRQ¨—ZüĢôŖōų€|āÉúĀ@´ÔŊ#€?~‚ŲŽjĩŗ‹ æ5˛ĸŸNÍC˜i‹3}uuĩ¸ø~eeÅ Ŗ™ĻÍ'D}Ųß_Y[ģ ĀõUmnn~mmÍ~ųdq¯/Xfy9Ō{%•Sr ”hŠíËXČjqŠmaōø.ûKË{šĮĨöĩĘãvZ]qŠwYkéÉäōaôAiŠ3îeÛŪŪZ^Yy˙ūŊch–EüüųsÄÍŪ×hûôôÔ˙j_Ņ{ņĪ€fīī)ĶcųQ ÅĻōS!ŊTOydY ŨeqŠmÄ[¨¨Ĩļˇ8}+kŅRû[ŲMKį9*.ĩ´ÅŠ‘"ČîƒŌR7ƒá2Lr||’¸į–|iÅ5?~||œbķĩ—ßÎ[_^^ÎNOˇ”ÉÁéļƒÂL˛€,p' ”iŠ­Ũ7j–:„Ņŗņmã[Y€ĪqõÄpÉîZę¤ ˇįkÁō^;Íĩ—äōų€|āą} ĪZjøi6ú>SB\ĩŗÅų>õßč:ÆÆÆ:vįÔ<få ƒRīííyNį¤ķ­…ˇæ\$›[[-Ų8¸ÕvđN`B™eY DKm 7õQ°Ôu‡ÅąļÅ-GÎöŗAībõD˙3X:°Ô1_ –ˇ_ŸŦ&Oœ“ž|@> xZ>Đg-5ēg6zC×@;øpv9ß'PbN“ÛPr“–zwwˇøļ3ššažRŋyķ&• `’bËAa#Y@ø ”ÆĨ6œËŦDĶRÛfë&ēēÃÛĸžĩÔÎI×ëŋŒ‡šk+JœtĄ=7oížüAū ôÉœęøŊ4~Aũƒî´‡SÚĩÎŨNš››;<<ėøkI ĢSSH8ŌY¤9â’f2"l4ĸꔁ "L‚ÜÜÜĖöpĘ" Č,P—Ú‚QÛÔÄëÃ0O@Âą-ǘF#˛Ôޏ[ĩÔa|ė ÷ˆđĸžŖ3CŪoĩø^‹RųaCâ§o-vcccggįčč(tĄuÄšķŗJjbf(;†6Įæ&iÄ!$ö÷÷gffŌĨœ›īāđ0?xkŔAēY DKm§¸Šēx !‡…\ė,ƒÉ=iŠZ{,ĩ´ÔŌ‹?1Nī=;;;=9ųzx uô+đ@ĸõ¤ËICGQ>ŪëjÕd‘}įėÛį6č;ԃ<߇ō•cŨЃž}ÖRß…TĢՃ°7zvvøģŊŊ텔°ÔDđX__|#ŗ.“vúyee™ffĻRLkŌōœžr!×yˇĮ´žk•_xáčĻĨŽĮaŠsäPč5ŧ-jŌRMH@7kŠMũë—ôßēq/Š#ąáĘáhUĪ4îŦŒŅcņņt˙ā 4Øë“ĶSē.ŌčAۆš?|$ívƒëĨ'#¸,Ü͍Ŋ-öŦÕŽSų"QUŌ zŪ2J~;?ĪŪ'ōsœ+ŋ}ûöŨŌR˜Õd9ĪĪĪĪ}rjžĘęs|\¸mđsŨš:ģ/˜.îëcË}åtŦ§×A~.; ­ôYKíPÃg"ö¸A3ØqŖ¯rÁ4jė’hCž}ûÎ>Eq%w܇˜Ö”`GŠøÖžîąbĘ& Č-ĐMK{XXę8ĢШé¤×č Ĩļ_ `€).õŠũ B‘ ‘–úAšqiË]ĩÛŗeÜš0?˙ũûw{‚ ŠčíHÃ.ĶB6ŗ'íåp.x÷đë!}›“L-寯o0ačč˗ë?ŧĖ”‡ĪžœõõädyqqooĮļk××_ŋr– ųíãîæ&Ŋ5Xųä$D¨í^ ōön ģ˛kĩsŌé}ŪßÛOŸžž,,.ÂŖ§ûJåtŦgKųũŒTžÚ;ų€Ņ;ƒĐR'Œ+D" ČĪĪŨ´Ô‰ĨNũģIûâR§6 @úWŨš7iŠÅû>-ū˛eÜ —ėĶ xŽŧ>wž4`zyy ˙fõ{ä\°oĨ2ÆįW°uhAš¸[‡Ú×ĩŲ`˛áŧSV^āWJ˜1Dķ~\_§ĀÅŅëëšûUânũæá-[ķd0IDAT†Ęx$Ŧ’úĖX…;čG]/nû Oŧ5´7ŖN™Ÿ›Kŧ¸ÕĒ(§c=Ŋí÷Ģã˛É0øoî@´ÔwbПāĐÉĪÛŨ´Ôv<õ‘bäĨ8xAČŅ).uЄÔaŠŖ–3!⇴ÔŖÁMc;q3â˙Ān-ãΛúÁqÎĸ§Ύ ōįĪÆ7‹oU¯ŗįŗkGû#eDėÁ^âX1 ÆÚŸ)ų Ũ‚ek×ņ\.Pđ‘v1OīmoßÔëŦĩļēŧ|~n zI}NļûLŌ‹›jüô„}â­š@ö\@ÜöŊ¸x‹S9ëųvĪĒwšŸ>0-ĩXęį¨tw/ÜeZę"ÖGȓé:Š8x-ZęMÄ ŒW,@ˆáî€ĒcÚyģ´õ´øÚ—ųŧZƝŦjVô‹Ļ…J61œ]ĀÍqūHßd‚Ž™ 3”đsãđž330Ö{ûģ~.ųQq\‚qÃ˙ũܠފsJƒnå_ŋ~}ŋ˛˛šé‹8TēÕ§cJ´ÔÜ (› ´}šî+•Ķąž^‡’û}™ž$› Īs~-õ G'ē}YāÉY DKb}„øÆĮ…NŌŌükX"ąÔá#ZŒ(_Ė\4–Úį0Fp“įæĮåjÖĪ%˙ņŅĮ/ĪĪ=–ķâé~Sz}sy7õdÚž˙Ęž[}:úL‰–z~aá ‘ŧ‚(üͧÍt_МŽõėÃsyėįŽōûÉ÷ųZá5Ša[|5~•zœÎ\,õãØUĨĘCa-ĩE¤u ßĮŒ‹-O˜‰ØĻĨļˆ”Ž|ôßI‡ŋÃQKˇŦi~āŋáá-T=‹ņģųhZ&#ęÎũ6įŒ‘m0͞tŌROUĢDü8ųū}ŨļúüÚĘÚuíšWôĩ(7xÛļŒfļ÷ânøŨü&Ūzmmåââ‚ûĸė™ĒŅøĘTROŊ×íŦ÷kHÚØĐ5uū^Zŧ ŌCKKũ(fUĄ˛ĀpX DKŗÔŽĄ llčš-.ĩë;ƒ€ŋĒÕŠ‹‹ŗŖã˨ŠĖLMl}ÚÚ\_!d=4ĖÜ/¤™jŗv6 wĨe‡įä(ŅKč™>§gĒ{yū<:>ÎôĻ LLTC/û ĨƒúŌôČÆûÆz(ėŠYą…áô÷“T\|,bLë ķ1*Ö¸ÂÚãˏ`$í?[ē%1§‰”ßÛŲ9ˇ€úŋŪy~Ö!6 "1Ÿ']ļ°ų ĄKËOÄŸžœ^ŒŽđ‚Ļ…C9<>æ ôÚÚ:Ņ0Ē ģ¨ā ëDĩēŋŋĮÛmˇöi‡7ņüōÆũ4M•ĘüÄ™ūNōé ÷|Ā(Ŧ^ĀãC ÎfH8qŠÄ)ļk”åķōyųĀ0øĀ@´ÔŊĸs˛T8ÛU­vvqä-ÂÉ[W›âI_]]-.2‡bÅKöÉųæß—ŋėī¯Ŧ­ņšéęĒF`Ÿđm zzPõÔÔT*¤÷J*§, ä(ŅRGŒQŦáb3 ö/f´Ôņ•Ž ęŋ’A ę{‹€ -ĩˇxŦũUų}Öe–<ĶvōđÔM~ĸgņb} |J€–θG˛ŊŊ…ė}˜ļAË"SŸ?.:Ų&ä Ú.‚lFĄeSūø|p@Oīī)Ķõ'čk+B™Ė,Fęėą‚Ę& Č­(ŅRGœ¯˜˜ĩBĩÔÎG‡ũß&œ)›§č02,ÅØŪBuö°>@ŧŽ86՜3„õčō1Ųaā>āːŖÕŸ|T-uœ“Ô!˛P7Ú¸Ĩ†č7ŌŦĮöōÛyk&{ĖNOˇTˆĻNöđX”E(ŗ@‰–:t€ÆF[‹Ōa5ņ ˙čĻĨļŦE\j‹BŨ‚Į –Ú‚ˆŲû"8¯ZüŊ|@> č“ô9.5ü´k Ų'ŽÚŲâ|ŸēkbĀwS6SķP˜•399é’čØMˇu÷qTqœsl†Ą|›%¤BšdYāĪ,P—: ŊĪį@8Ē; cŦŧ48.@7,u„ßaÚaPQc­¸Ô6<áŋķ4ǃeŲA>đ} ĪZjæ˛Ņ˛'íļÃų>uä ffđwė×[´ÔÄ *›V˜ŅÔķ~Ú×|õ -5ŧõæúē´Ô†ĸt–,,Đ]KíMlKCr8aŨ—Ú ´v=hŠmĨD<í {--ã‹Õ2F_Ņ7 gdŲA>ĐG”–ē…3.Á"„{gEՎJ"gW§ĻĀĮé,Ōņ?I///CQģ~:ß@äĖp<—–ZØP¸ŸēkŠō Ą˛Īå(`3 –Ô#KŽ…1€Ņš–ē2õjö‹âR‹m‹,žXß+äō>ûĀ ´Ô‰ŸžĩãŪØØØŲŲ!(‡÷Ä$VWWũŦĩ7ˆ™č{ŽĄMãV‰BB"Ŧ$ՈHŽ9Ą|ToeŽMūØŨ´Ôœŗ77ÆúptÃxĻÕÃ[bWęvn:˛ÔÎlĮũ¯8ÃZZę80/üŒžWČōų@?} ĪZęģvŌÕj•uXŲ:ĪÎNƒ}‰V녔°Ô!vX™õ‡H;'ÍbR”033e ė Ãæ ™AíVūô4øĀ}×J*ŋ, ä(ĶR7Í-tÚņ¯Šh”kŠŌví{ÅĨG+ž^>  ôYKíũîVO„QFûÖ™pŌš`5v ŽA™ũíÛ7čpö)ˆ‡+šãŪ’VhûëׯVū••˙¨ĄNŧd—`˛¸ÔŽ„ãWĻĀL'ˆ õhbŠÃG´hŽ&-uO#Ĩ&‹ÅĨŽütÖûÉIčZâĀäōų€|”–Zę^ŽŌ=žX ”ÅĨÎã{83Ũ`Š aßĻĨļÜpdûŇ}u Ē—Zą>lhÆŌĘōųĀ@|`PZę;ąÔ/—čÆe'j’¸ÔY|×I,ĩkĻÃD°rXUŅŌmZęÆJütˆiP5ØēāĒ#B—ŽVzbų€|@> čĢ DK-–ú‰B%U[čÅ%Zę,žGˆNĨÔEŧÛĩÔašĸũ˙xÚxębžĸũÕžfÕ@¸ qĨ>’dų€|āEųĀđkŠ{é•G†ĮeZęB íy2]uÔnÜĻĨk%Žü )l|‡Ÿļ}ZUQēFéåōų€| Ī> -õđ ÕDx6(ÕRú頀.–Gôt/Zj8˙9b*jÛGlneĩ`yņC/Š/Žo2ōųĀ}@Zęgbt#˛ĀđX DKÅĨļ}†{ÔR˙BiÍ”ĶaīLŒ´Ô}æcÂC0Ëë瞃|@> H=QĮīĨ%QŸīßs÷ŽĨfņpg™››%n4+)@úŽîņ§{Ųōœ¨ž ۗ/_z9WydYĀ-P—:°Ņ!WXŅg(F–ē‡¸ÔŖFSŖáœq[71žŸ¸jņ˛áHĮįˇĘäōų@ß|` ZęŪ‘Įōōkžžž7šĩ‰Qíë Ūië=ÔtĘ ŒærD&a;<8đåĩɲ@/¸MK] h:`[o<ę5ŧ-ēEKíz´÷ëW€đ{X3FqŠÅ‡;qf˛ƒ|@>Đg”–ē—^Ųķ\_×Ræąą1čí­­-?Ŋ´´{͞tĘļšųqrrrzz:ąË‰q'Af?Nzš8Ũ3°ž" \dcŊF 0”–ēwĀÁōãŦnˆ#Í~:Ø„ {Í~ģĀ؟>}b­ņ‹‹‹Ķ““ŗŗ3Ī霋/-ŊãŦ÷īßķ'l÷Ōō§¯­­9LgiErúڊd~ķæŸ>55uy}Ũ{•Sxá(ĶRīcsŦâ=ĩąmi\jgŠmÅDÃĶa"ZKKŨg>ÆĮCėt]ŲA>  DKíp؍“väá+ŋäû„HO___#¤~÷îbŒôĶņņŅĘĘ ˛?*Žŗt9ŌÎŊ~ ÷Ėôļ(7>ŦŦ‚ũ EÍĪ/ äTlâŗÅIPą§/_üü…ã'Ũž,Đb2-ĩ#ąĀ)[2‹ûáÚ ŽLK$FúÚ`vXĻRųģXęŠĢ¨Ã>üį9*.ĩæû‹§—Čäƒō>kŠ쟤Ŋ'ö؊ų>īĄÁ°ČĪđĘč1@ØūëύđY ōÖj×H5Z:xîņôôôõÄĐ<ũÄ)” ˛¯VĢpÕ~c@äA@íoŠ´ÔØA\‘øBų€|@>0~-uŪ…ŖÄO#íđƒããŖN†ą,qŠÔ11Qm—ˆ ÆzoPž~åv@öļ%ôė Đö÷īß=ØŨ#˜P6Y@(ŅR‡ĄĒŗÔAE]ÄúTŗņ™‡á¸åtMG@Îüé,u6ŸŅ8mÅĨ–~ēXØ?7(~NוīŊp~-5sQe8fŋˇˇ—$Î ‹üÉņŊŊ}ô!Žc˜}ėĻūņãĮ‡ü s`°×°ŨŽa[\Xđ8įįį)'!ķœÉ^^^&3W¤¨p֚p’, ôh-5QĨCŧ¸VbÎO[úV-ĩŗÔ“ö(•_EÚ‚ÄĨņ•äōų€| ß>Đí{iR÷؉>R6ā,MŌ‹ųų9Hå$ü€rFÎAÄö)˛ŪÆÆ”3á>8‘´×*Ũ 1øĀܛ?r8¨ˇ''&Ų)NöįΟ ‚h›)Œ‹‹‹^§~¤Tą˛Āķŗ@‰–:éˆoeKŦ^´ÔMMUĪNĪNNsm|ffž(?[[ģח—ķ ωŽį×`K×SZ6‘ČäōĮķĸPŊ~ũ8MÚķk-ėß,ĪVÖãLžīōÁ˛Ŋ¯öōāWW˛€,đ¨Ø9ē<8ĢތĨļ‹WŦģžžÎˆˇrŖū9Bė‰j•īNÕׯßĖĪo~Úŗát-°Öá+ĶüT…H×ë„Ís uÜûŋŌR‹›—~T>  ĐĨĨž~T@ŖÂeÁZ LK=:Y’,îGŽŽžMKMØŧ ĨvÕûQ‹ûҤ#yáz>ĮH[,}š|@> č¯ JKíņō´É˛Āŗ´@Y\ę_ŋb8b…–`Ũ÷ĻĨv–:ÅĸFK ēâR›ņĀNuäō—ėŅR‹Ĩ~–@J7% ¸ĘâRÃR ØõÆ1gڍÛâRÛâ. –ÚŲD$ÍsG[ŒQÄUûXMväō>ų@ŸãR{{–ú>į ņȲ@,P—ÚYęFėģ CuŪ—Ú§°ZjĘI,ĩâR‹'öė ;Čäũ÷§¨ĨÃŨH¤KČ÷ą@‰–ÚZ9´E܏°îa\$ąˇ¸ÔÔŽW (ˇr¤Ĩ+í¸|@> ¸đŒĩÔtĐļŪ8˙ũüyzz655›–Nėtŧ_ŋëÆIaUsģĘÅÅ‹;‚DīZHžŋĐĄÚP+ÕßËŋOąųšÔųāā Ĩ4֒L@ŗŪ wÄÆ0&-ĻĶrË[ēU——ĪÎÎSũ{¯mē.Ø=­dŲûéĘ9l(×RۄÂ"îGXIą‡{ÔR;/Äĸā:ąÔŌRÛpÅWr—žXvČäũõį­Ĩ“ãÆjä,3qyvXdÁˆÂ¸Uō;ßz¤:ȘĨĄ]oŠæ„Å9ČŪ3ˇ=ŋĖČødé?ĨB–—…°°ÎÛˇo)„‹&ĒØ!ėää„áÚÚ7ܨ2œ.…PĻ_‚ļԓģ`ÉéÉIhcr’†iötĒ3šŧŧL…ō¸9Ũ zmm%$ÁFl,šģģێÛ0ŋVĢUްĄÔ-ÕŋcŨŌícC×đ˜å+Áōãã,:ÚkČmmmޝ­yÉ[Â^]Հš –ē Ąž@đƒƒC¯'ˇCi9‘L H\Î..¨ufEw˜fŌ‰iĻÎG鑮ėĻé^NOO@ęˇ)ÎōlfŊ‚SOõīX7ėãKčAo{}ōsųsia!:ˇÖA†ĐåZęŠGüpEuZ§ĨLKmƒĮ@g×Y=<ųéČRÛ įŖŠK-~ZÜŧ|@> ŦՓ(ĮŲe+7čiäŅą„‘ø`~rq98žK0ØHŧxíēF†[ą`=-eŸÎMõīX7*ã†Ĩ>‰&Īųø™šYąÔˇZ~Č3”kŠ}ŦYę–uZĖę nSv ī4ĖbÄõĨ6š´QÆO“ˆ‘ĒÃģaˆé#;{k,;Čōų@ō§¨ĨöŠŨö mäZäëkã›]õ‹P-!rsžu4`ҰxĩZĢE$Ę)ĀP° Ę Đ¤_(qÛüX„–N4-GL Ļĸ…öüāZĻ0ĸú'ôŋpŋ, }7-5ĮEÄ æ,öÔŲ/‘Ģ™;ϧ€Â}h]qü^|CūŌË$KŽčWĪĪmÜ~§ēâšũššYĖŌ‘¯TÆrk5ŌŋOÉåZjAÅčÔÍë´ô¤Ĩ6¨ũk”0,¤X%ü/ĖmlÁōg3XÎFö—ũåōâŠ*Fī–Rô¨zÚŅŽ#z?7į>éãÁĶį§§^ i—=$4I:Ī˛ėæ˛E4 |D> œæ æÜ6"<‘Á i/Á÷~iN„c_]]ĨsS” Ė~Ģ–ēŠžöSįˆnŗÁAūsū›œ ’Š-›ĶŪŠ>pķL4ŧÄqńŧĶšé* —įu[\\xķfæččƒ´Ô'U>.X}ëå•aX-ĐMKí3ãČ-*ĒãcĮųzŌRGQõčX`ŠĮF~™ØßÂđ…¸!-X>”-Ņ[ ŲAvČäčáSĒ•ßūŊ4g7ŧãžĪ—ũŪĪMœk,ÔögæĸĘ9č¯}.`QLOMĨ({PŅ>ņōü<Å  áWķå\rnäÅdsHA!üIJé°gãŽ|@۝ /Gür¨´{ŅR§+rĸ_‚Ŋ›žfI:]weyZŨ)ę`ĨX0c†OŽ$á^7w āé’x#ûD1Įē¯įįœ ¤NwZ­6,ΰ§÷Q̓{Ļ | tĶRĮ™ˆ>üĘâ~„Ų‡†ļ}"bôŌîˎŗZjĪäĶ|ΰ} §ģrâ‡^?”0Ũ¯|^> ”?ŋĖ#Į­Ŧ˜$ĄIĘü|p ôųv°Ņ gÄ Ë++ áų@dL°KsûrΘ‹ÕËĄ2̓B Åá}ŊÚüúöíE!)á'ņä q9&Pttänķ[Îyņš[=áÎĶ4ĘŽĖ´ƒãˆŗ‹â¸;pL‹ĮŠÛáá×ũŊ=Ē„0æėŦQx^î+‘ŲÉé*ëļĩš…lfŽį1==>:ę$7Ī%Yūđø˜H#=>beN ”hŠ›ü0‹ûs8Đs‰–ú¯ęÔ !uŽĪíĩņíÍÔS]ˇ7ÖŽ//™Ļ`oT&jŦĶ4×E4lĨŖõd7„ė ;Čä÷ôˆ"dБI›ëmËÂūÍōle}qę‘:lĐę3‘÷97ŋ_Ę$úH÷øBŠũđau}}ū ÷ btņųķį)M… Ę;G—gõÕą„Uxmy¸ŒEëõįžSŨŌúäȟŠV_ŋ~ÃØõĶC˛ķZā¤Ã r~Ē „ŋM;‘ąąŲĄ,û§70(ŽB×O&Č^””|/Íûŧī˜˙O[Īæ&ŪsƒE‰‘hŨ{–ö’OßŪŪuåɃlˆŅ;Ā~ÂUHß,PĸĨÎđ´!bį“ŊßéMKíą¨+­‡}ŌŽIK°´’¨• ^§ōåcōų@ ozÆZęná2$‚ÎûS†ķBøĪŽL(ęQEüÃiÃįWĢž´Ô|+ÖP,´ÔtĻTKũËE#>ûŪXjŸÛ˜´MŲ<ëő˜itīũ]/M6Ocbųž8iųĀ3ÖRwC*ßž}Cī!<ũüœîhH,PĻĨ.p¯ņĶĒĨ.Z!§XKãR‡ĨaSHŊ8ˇÚĨØöCûĐ-öÔŖ’…ŊGíh‡÷9wH‡Ē! Ŧæ {_?œuūĻŠ´/_öŖų_ö÷}-@JjûԌÎŅÚ+ኁGOéxõĨĨ–Ģ“‡kA!C{į¨= Ô š×ë\Ĩå\nĄZ}'7%PÕtkÛÛ[Ŧ¤čUą”zĮõV°–dy ę ˆgũGˇ›oØÖ/Í⑉äæBD-ä FvϜs¸ŽsęŦÃå4åtaņ}ĒTĸĨļb O+†y‡ŽÚgߎĨöˆ•Ņ€§Ã>āw×\§ąše,W!ŽHö—Čä/Ęžˇ–ē‰žšųôéĶüģw B d3ūB˜ęœëĨ;Fe[ĢÕ_â.ÃÎîėėpũéÉ Ų(ŠÕ‘SȁĶĶsČāķķī(9~ÆÉamz|NôåģÃŗ+fĀ~Ÿ:ĶkXėŧ,.u Ŧ ?IqŠ3-uäĒŠjiŠĨ#/rqöâīåō>û€iģŦãû¨qúŗra\Š€§ĪOOϐvD—Ö}g…3X0Ėųc‹Āđ‡ļĸļØŅ3[PCHķ-Ė‘™™G8ž`tnĪv~7¤¸ŲÕ;0ģ-O'ØÔÜÁ1ûXÃķcö“Ķ(j §’Ąķ‰p2ŪE)å[˜@OiįÚĮ‹_ų)]Cq-F#WWW@jŒ&ōzŪv}ũū4,PŽĨöŊ­Kĸ)?’kŠÃûči#¨]Km‡3]DũČâđĩ`ų`-qu۞ɞƒ|@> xD°^ŠË:ž-<čÃöäũ™ŽÖÄ×ëû3ss~#Aō{8ŪÚÁ%ÛôÔTОíŌ…Ëķs—…°‘đšƒ,sûĐQ-įZäö#9æjĩqÅ<Ŋ¸°āW‡ OW/cŠŗ[E¯âŌdöŽ]ąŪ´ŗKK@ÜĨTÕtœAqŋ(6!ĩÜūĐQŧ¤aI:7]|aq1 TŌĨ1ЧҞ§ĪøÖIģÄOāaŨLĨ ĘŨ´Ôv<õQč5:îYK͙ÅWž°*Œ´ÔŠs"]ĩ|@> °<9-uī(!×RAųüü2Åą˛bJ×?øöųāđįĶī`ŖAĪŧbe%qę>áHɧúq­Ÿči`w‚•ŽS͝)M}Ōķôf¸:!ĸÁží ė–Ō 1gFqšŸžž¤‰•éŠĻë# üŒt6 °…ĐĶ\=Í/Ė Îé‡EˆĀvŽã—>M3#)įí[æ+Îs ÷A áDP™{|nØņ^fFöūܕsā(ĶRą>œ{Îøl“m4ÅĨNļšxÔR˙UšaFđÁéM`ŠMEŊ0=öqũãîÖÚ5sĖÜjĨeų€|@> xl€nEFĖŦ2W,¤>ha˙fyļ˛žØ8ø°4ˆ­?ڏ’jûĘ&@‡Ŋĩg_ė5HŨÕį÷ßx °ãųlŅû—ŠnŖËƒŗúáĘXjÃyåÁēŒ`S5Pnœ›ę‹mlŒP<Õׯß0vũ´ĮĀō˛ÔáËüT…qõÄBõ¸j‹ĻF‡ŌRKK<‰]Ÿ5”îÁēŽė xÉ>`ũŅķÕR— ôģŦ0’H܁C'Tāīîn w˙jŖNéH‡ßŋd•0@ ”hŠ-"u„ĐđŧĩÔŨ ņ:[@˜ œ) čųãKCnm=¤fTķ¨Ēũ?žMx t×RŖÉˆŠ}ŪaŅ<{:ÎAsŖéHüt(-hBęuXꀲYá%ániŠÅ*.¯|@> xÆZęn˜āÛˇoč=„§īƒ™tŽ,PbîZj;Š=ÖGã[™éČRütÆR§¸ÔŦžhˆÛ÷lw›g-^Mą>äōų€| ?>¸ĸëø>*w؟ˆ=˛€,0 tKíÕ1ŅsÄĀ_ĖZãRg89Î\,´Ô! xá>đ2ĩÔ/Öč6eAY ›–:ÖĮŋŒEÂÚôĶQD]ŦįŌIKíĄĢ -õˆķĶÎUÛLFÅĨVŒ ņĶōų€|`Ā>0äZjBsáxnn–ÆŦđGĀĩ?@ i%ŋ[ĪÍsb.×ûšˇŽ ˛Ā ą@7-uŒf%n0ÖņÎqŠĶę‰IKMčĶOZjCÕ]ÖŦoôÂy# ČúãŅR÷,Xęhw§§g, Nül–ŲKkˆô^Hīēđ<įô´­xŌûšŊ×G9eįmnZjÖ?˛ofЃ^#RÖ¤GG*ÎR7CkÖR–ápbŠ—zĀ܌ôš}ĶkƑ¨tķ>"—d‡ĄņAiŠ{Į××ĩ”yllŒ0)˜46‹Â^ŗ'˛mn~œœœŗlDA„%FŦNf?‘Æ~~ē“j,‹Hb‚ÆĮųķęęęëׯiĀŪëŦœ˛Ā ˇ@7-ĩŗÔQ?lTÄú(ŌIKíQōqŠ›ĩԁĨK#fč[Zę`RŗLø]Kv–ČäÉĨĨîp°1¤Y$Í~:Ø„ {Í~{kË~úôi||ââââôä„u‹ũ sf———ŦÍYīßŋįOØîĨå%N'@ĩÃôÚΟäŦņĪĪŸŠ†bŠ{XĘ) äo\jgŒuī ąÔEŦĶôqŧëڍ0Ņō롤÷ˇ]K——–Z\8rų€|@>0 >Đg-ĩsĀa7NÚ;`Š—ī4q<ͲÕŠßŊ{‡ö#ũt||´˛˛ÂŸėŠã‡‡‡üIß<öúuZ–{<::ú°˛rpp˜–^§¨ųųN§äTl;'-–Z0Q¸ĢĘ´ÔE¤;_1ãŗCڀwqŠãr1 –ZZj{بEk›Éōų€|`@>Đg-ĩsĀa÷“´wÕžęJžĪģp@0,2Ä3ŧōÁÁÛũųķWš’”xåZí}H  kģŧž˜š§Ÿ8…’AöÕjŽÚˇsŌbŠī §”_(ĶRĘéĮæ)ú‹įé^´ÔŽĸ§ą‡öNk—+.õĐh íéHį:kš o§:č]x!>0üZęŧyķ<´ÃŽR˛—cbĸÚ.!ŒõŪū> <ũĘ) ė€ėmķ2ÅR Ę÷ˇ@™–:āæâ]ŗhzú qđzĐR/VO4™ÖŌRûđ„˙ÄUËōų€| Ī>0üZjÂ} ĘpĖ~oo`í]ūÂÂ"’ØÛÛGâ™kėĻoūņãĮ‡ü ÷š°×°ŨŽa[\X@ Bâüü<圝Í™ėtîũA†J^ŽJâR§øáûXÆR“ÎãR—kЉH]ŸOŖũ($i>Ŗ8Ą Ŗ1ÅâÕ7ų€|`(| ĪZęģĸ @0MÂëųų9Hå$ü€rFÎAČö)˛ŪÆÆ”3á>8‘´_.qĪÄāso~üČÁÍ­-Ôۓ€lל°}ūü™0 ˆļS=ĨĨžë#S~Y ›–ÚÔąáą>rÆÚĻ&ŪŽĨūĢ:5ÃŧãÃsč€:˝z}~jŒ‘÷öÆÚõååüƒé‘˙ڐÛE"JËōų€|@>đx>Ā üׯ_C§I{~­…ũ›åŲĘú━, ČwĩĀÎŅåÁYũpe,ĩ] _Áēl'ŽFGģaěŌÚĸęë×oæį7?í1čŊŦ… á+ĶüT…HUą/lāiPucžŖ´ÔŌR%ž^ßjäō>ûĀĶŌRßĩ_W~Y@ˆēiŠ­2a—°yďFÚg(Ōš˜Ŗ[\jĪ’FbJA›íÔÕž~ŒæšKK-Čäƒđá×Rčĸ˛€,p ”hŠŗ¸Ôå#†üp„ĮĨniR\ęŽÛâ‰Ļ‰Ë•›ģĐx)քbMČäōų@ß}`ČĩÔ÷é×uŽ, ÄŨĩÔÎRú-0´ĪP xēYKrhÚŽTūöeK=øépbÁU;‡2)Ũį9ī˛ŋ|O> xą>Đį¸Ô÷īā}Em˛€,0´hÕ_ŒlM—Áâ‰ۊ‰ß#ü‘ë5<.5ųø5cŠæČÛ´ÔĻqŊHļ^y7n ĪZēT+]W:Nų€|@>đĸ|ā)jŠStŽĄÅǘ,đÂ-ĐĸĨā7č¤ÃdsgŠ u#îGŌR˜ėˆ™ Gģ&Ä ĩá[KK-Zß"äōųĀĀ}ā)jŠÅRŋp¸ĻÛ~ ´hŠ™Žj"‚‡‹3 <œî)fKļŗŌ‘ĨŽ×˙v~ēØÛiåiŠÅ‘›oH?Úwũ¨lūĸ¸Xĩ3%íĖ“ĶR÷ÎRƚ-믧§'WW—-dm÷üVŧ|ŋņ°WWWۗoŧSąŲJú{ųw*§ėNĮĮŋ˙Ū’us¸H:Ȓ7ËË˓ac)œn7…˙xPp äö ûũõL×Ĩ(¨§<ĀrZ´ÔžöøØXåû÷û)H="nÄũ¨Ÿ]ŒĨčQéøi‡Õ-ZjCÕ–Ž––ZÕĀ9*QuĘ^˛88h)ĩ$ãˇø°P%ëŨ°qGl cŌb:-gąČ†[uyyŅ–ā8:âĶĶÉÉŠ’ņqVh"ŋ•5Á;BlĶR;?í˜=Ķ—„čԊK-ŽÖ}EväōžûĀķÖRģÜŌ7V#___ŋ<;,˛`D áÜ*ųoöÎdĖŌŒĐŽ‰7ĨÛ§ŋį {_˜-(J_‚,ũ¯TČōrŖpÄÛˇo)„‹&ĒØ!ėää„áÚÚįõĪ+ §K!”é— †-õä.Xcrzrژœ¤aš=ęŒA.//SąünN÷€^[[ca Ûâââîîn‹ŅøƒđkĩZ加%ĒAŨRũ;Ö-Ũ>6t Yž,?>ÎÂCTLڞvS?­#-ZꛛúDĩ:ķæMĩ:…ˇLŒņįÄÄØÄDØ[z‚ÕĻĻÉ33Cō7"ã…šŒ -ĩ̍ãėE›ÃhĀÛYjÅĨö‰˙ēÉōų€| Ī>đŧĩԉû4čysķéͧųwī@l`Į/_ž$ŒBzaa8ņÍüĻã˛ļ9ȒEŅ=3ęÎÎ؟GZ¸í€MãBčœč…,/¯ėėÄBāä6Ö×9¸ĩĩšžļæ%o{uUæ6Xę.0ŠzÁŊžÜĨåD2% q9쏠ԙŨašI'Ļ™:sĨGē°› ¤{9==Šß ã8Ëŗ™õ N=ÕŋcŨ°c* ŊíõÉĪåĪĨ……üéÜZeB ´hŠYõą(Žē°ÄCfË÷ ü437đô ų“Ō:Ī†™S\ęĀO˛:ė?öŌR›+ˆŖ•–Z>  Čžĸ–ÚYĖnû„0÷  ;†Ôa{{›_ÁŽ9įJÚÅ9ß ōŖĪį =}FîVNOOA̍;ļ Ö6qÛ\2˜ ûûQSå ŋK!uxYíŨΟįCÉ ‹×ĩš×–īß~šK]pˇIĢ황'dž§]m:öŠž”@9Î.XšAO#Ž%ŒŒĀķ“‹ËÁņ\‚ÁFâÅk×52܊Õëi)ûtnĒĮēQŋSę“hōœŸ™›K}Ģå‡ā_J;~/ÚĀ ŊGüČšOzhđôųŠMb#í˛‡„&cŸŸ0åøØ7Pil ‘OĀCç9ˆ9ˇ B…Od0HÚKđŊ—͉pĖ ķĢĢĢtnŠ’ŲoÕRį÷•°/5rDˇŲā Ž9˙MNÉԖÍiīT¸y&ۊá¸bBŪéÜt•Žu[\\xķfæččƒ´Ô'U~ÔWŅöd-ĐĸĨvLĖŽ÷ļ.GČa¤WÄĨŽZęđOä§Z—–ē÷ņĘ]Į7Ę/ÛĘäō[}€žĘų¤íc:2„}zīĒ€Äš†Ŋ~p°XĶī(č¯}.`QLOMĨ({PŅ>ņōü<Å  áWķå\rn%äÅdsHA!üIJé°gãŽ|@۝ /Gür¨´{ŅR§+rĸ_‚ŊÛōė:ŗÔ…iV–—ĄÕĸVŠ3fØŲųäJîqsĮ@˜.‰7ŌšéŠëGLËā!uĘÉŦĩdy†=Ŋš†Đ?UĨ䁡ļ?ö*uŅ)äíRHwÔR‡õ`l†ĸ´Ôw¯Üu|ŖüŊe+ŲJ>đ’}ā)jŠ{D-š–zvvúüü2ˇ˛bJ„&)ķķÁÍ™ ķí`ŖAΈ–WVÂķ)€Č8˜`—æöåœ1'!Ē—C!d&Ļ…@ŠÃûzĩųõí[›¤eĄ&&J’3Äå°¸šÛü–s^<°æVO¸ķ4˛#3íā8bîĸ8îŧŌĀâÅqęvxøuo*!Œ9;kž×„ûJdv˛CēJĮēmmn!›™ãyLOŽ:ÉÍsI–?<>&ŌHX؆ʹÔmsEø6ÂōĖÃδqØĄĶRĮ_=ŽuĨōWuj†:§üY¯ŒV˜Ą¸0=öqũãîÖÚõå%ĶėĘ¤C|đHĸ*ĨŨ]dŲA> <¸@ˇ"#†ŒLÚ\ooöo–g+ë‹SCØaƒt„ÅôĨL@ĸCxO¨J>ŦޝoÁ¤ÎĀ!FŸ?~ŌTČ ,°stypV?\ë†ßn~ü`8Ë;ȈŽčÔc¯[pīæÎŖ¯Ë,ûüT……–Úųébn"–´ÔâÆ^27Ļ{—˙ËîĪ[K]‚' GQb$ZwPČã\w{{ו'˛!FīûA W!}ŗ@š–šˆ–›[[V Õææ–…`læĒS\j;lŋuÔRÃRļ––úns?Ĩ‰ŧ&IįĘäōŽ>đŧĩÔŨÂdH>„íîLÎ Á2>āȄĸrņ÷pŪ˛juĢÚįf4Ú›Õ@”Iæ0Lņ¸Ų“fžüBŪF5âR‡YˆĩÔĻú–ēe,2pžFõÁâ å‡ō—éĪXKŨ­ã˙öízáé[‘2Čf-5­77LH…œ“ļÂäԛ“Ķ“ŧ*hkÃĖâRŖĸžzĸqŲa/I’|@> ôßžb\ę?ëæu–, ôĮŨâRsuĸßđ+ÁpĐß\’=iŽœ?w…‡ˇ1ČGšŅ×|ÉãRĮÕ^BÄiŠī§đerHâõÜåōGōĢĨî°ĐUd—inZj2Af–—™hč+°4¨šãAŋ™÷ĸĨ.VO,øi[ģ\kŪqMņXũįądsŲ\>đ,}āejŠ_&ĘŅ]Ë}ŗ@wũÅČ:)đ4ą<ĸFƒ4¨šãŖŽŠ‹Ų†Žã°6Ē]KV#/"R[ŧ=g‚PÄöÄC¨|ŲV> Č:úĀ3ÖR‡…ĀĮÃn|zzruu9­'Ō[˙N˜Ķ\šå”:'OŊ~“gcu˙“ēĨãyēĮjØŨe÷čéŽįrœŲ-?Š#ĪīQ''&Ļ''YØĨۂäÜ5qî(Љ.ÍŊÜÕnœ›ŽKQyío\Ų†Ü%ZęŸŋ~9?ã^¸jŽ7kŠc\ęÎZjä! ę¨¨&~ĩ´ÔfQéČc˛gɁņŒu_ōsųĀ0ûĀ3ÖRCUŲJāü÷ķįééÜK“”`‘līž Ļ@įĢ9’˜úĸƒžq„<•ĘX^âׯ_ũĪÅ-O÷ty>šÛ:įvwœ›ŌĪĨĒíĄîX1…×`°ą:~÷îĒV;ģ¸`ŪXZîąĨ@ĐĸzŸq/wĩ›õũ…F–'ŌíB=AŲ†Đ÷×_-uœs^ŌöOÔRKâR1B=iŠÅÍëģ„|@> ¤0xŪZę&øææĶ§Oķīہ’Í8ø aĒsŽ—n A”m­V#|uŠģ ;ËĘdzrB6ŠbudėOOĪ!ƒĪĪŋ; äë-{´>z|NôåģÃŗ+f Ŧ0(xrZjz ĮĻŨö yäđõĩņÍø—l ‘ŖŖ\œ×-¨”zŽL¨Õ"~å '¸%CBÜŖ#ĐėAą$ŲGRsd1H)ŦˇĢT(*Ĩ#˛ĖPRΊs•ėę†&FŲKËVķš5˙ˆâ*ÄĢķ% ēšt^wÚm>b~!ϐ%]xĒC˛—Käbē4čuuÕĮäädc,‘Õ?¯įBĒj‰J´ÔG_ž1Ų _+ą~ġ P50aI\ęāíŋŽĨv–ZZji({Ģõ2žSžÁō|˛ŋė˙D}Ā{ĸØ ŌīåÄahâ€+đôųéŠãŌŽ‚HāŌđe†P8ƒÜ?ļLh Ēa‹=ŗ5đ8„4 ĀÁ™™™ĄÃįĮŒÎí™!ËÆ%s ĀÍŽŪŲmy:ÍįN88fŸkx~!ŦQEMáT2ʐņ.J)ßFGGĶĨÛšöņâW~J—ÆP\‹ŅČÕÕUļpGc×ķļëë÷§anZęÚÍÍöî6/¸:ĩ™ûû[[Ûwŋm´?•Ø _áÛ˙W‚ņuMKĮ—ŌR‹—/ĨëĘ÷äō‹čZđíÚĮôiôäÍĩlŌR×ëû3ssž%H~¯'Đ÷;¸d›žbadĶE°AEģtáōüÜe!l$œÛYæö ŖZÎĩČíG:rĖa)æxÅ<Ŋ¸°āW‡ OW/cŠŗ[E¯âŌdöŽ]1ėQ€ŲĨ% îRĒj:Î ¸_›€Znč(^Ō°¤ÁRųĶ@%]šô4ZöÄgCZ'í ?=‚§čfĒsģēiŠ'ÆÆ\ÁëäÄÆˇŒņđŽŦŦŦņĢ{kôØ0ÛĐævˆKíĖ´ÅŅsvVZjq´âéåōųĀ€}āÉiŠ{G0š–šĘįį—y,ēp” ŽđíķÁāΧßA¤ž-xÅĘJŌ@û„?"U<$ŸęĮY´~ĸo¤Ũ V:NMŋĻ4õIWĖĶ›áꄈ´+°[J‹¤(Ũytj~zz’&VĻ+˜ŽÛ€Áŗ§ã°éT€-„æ˜æęi~anpN?,Bļsí§¸ôišI9oß2_qžKP¸!'‚ĘÜãsÎ÷23˛÷įŽœˇ@7-õ/‚šÜäÁã ;;Ÿ˜%Ė@ÎŖĶl~üØ—:ĶEė<ĀuΕŋĒS3Ė><į/ĶŽPâüԁˇ7ÖŽ™ ŧ°`f1Žãf“•6™Œė`~ ;øÛ ;Čō÷čVdÄĖ*sÅB*a˙fyļ˛žØ88đNúÁ+ā+›ŧäį] ė5HŨÕį÷ßx €Ē|ļčũËT ˇĀÎŅåÁYũpeŦcß †‡Ī“Y7ŨúĒŧ}ÛÜŲÛÚÜēŒ’+ûe~ĒÂĮŋ‹{ŗ3ÁĶ ę€ßÞPŠž,>úÔGŪLU>~ÜÜŪXšžžœ˜°!>ûAL>FQZū ČÕ˜CĻßÕ|]ēÖŌŗfЉ×Á‡aĸ<@ØĄKËĪÕÎR¯ŒuĶ\pã•ņqôEŧƒ)8LŪÖmîėČüŌ™k$\Ļī¨ĖNĪūõē:syqvŒđÖyąÅ^f&*›0ÚkË ”Ąž)1H°ÁÜ!,Q/YiŲA> ČÅŧߥWOË"Ė(˛/¨ô^›ĮÖ!ŽĮy{Īĩã×}ɲĀcX`iįœvdkqĖۓē<ļ-tX@4âéļ<?íņųâüú&΂­Tæ&,H| Km垊KŊˇąōŗ~Sģæ5"ˆKŧĶ^dY@xl @N3ĪĮόą6ôXŸtņŖžųĩ^™­†¨ ą?ô^Q{Y@ēZāėē~]ؚ¯T_[ėŽ?ûÎf,õæfģ–Ú õÅŲŲÁ9*ęŒĨŪDøATË DĩwËį{›å›Ō˛ƒ|@> <¸ŲÁĶㄎ‚Lj后Ü|99ĢĩqKŨ8'oįádŲäåųãđ÷ŗOßãŪKŊu~m,ŗcáųęČlÎRģęƒ=Š×R{'3ÍûMĖW} ķRZvČäōĮķ´ŒôVi1ŧ?æ“ŧ#û3.JįĘnōų@ģ$-ĩÁōą6-ĩŅ “ĢÁÚsÕ !ˇ×—;ëKîëī3&Đš´>zâäōų€|@> ôŅ6?íY¯Ģ–:ÄĨŽ2ŽšŠ A­÷6ÖZ܆|@> Čäōų€| ų@Sď@bĮ¸Ô(=LŨÔŅYŦžŠbQ+–ŗ|@> Čäōų€| ÷GËؚŖh?Œ–nŦžĒFˇÆžÕaŠY=Qc2Éäōų€|@> ČäÉš´Ôa‚ĸĮĨū› k˛:î‹ea8ĸq‰ÆĻōų€|@> Čäō÷ŸyâJûÜEŌļæKdŠ ĸCčũĒĪO}Üü¸/-u°“æēēŋȲƒ|@> ČäōÛ´Ô9K$!‹h<*Čäōų€|@> čĻĨŽë´äZęƒSHk´Ô†ĻKŊÛĻĨūūũûūūūÅŁHßŧyķņÇą×¯üøÁnj§§§œ™™!úaųon~lmmŸ_Áz~nîãĮ¯_K‡$ŽwØ|āâúzooīĮ{qéņ^ÆßSŗŗie¸äíîÃŦ8jŪncθų‘Ūâ˗/Ŧ§ŧ˛ļ2?ŋĀš×ŪŽŗöōÖÎߒļˇļ¨eōŌōЉ/ŌÍđĀų7oō÷k˙˗ŨŨŨ_” qãƒũũËë ná͛yŅZÚÃŖŖ#úŽ////-,tl+ęõ›ŊŊũĶĶ“Zí'K*ÎĪĪ­ŦŦņĒr_ËĢ̟?ĻLŦ‘VųŸš™ár¯_ŋîtīõwī–*Ą2ūēĩדˇōččËŲ™u|ŠkyÖ9îîŌžPĢęŋ¸Ø^ÖMŖđã“y`Ąã……rúĨ?|XŨŨĩĘŗPqKËķáÃ*˙rúŽV:8899žŠũ›˜ĀJ˜sŧŌ´væƒ÷#ąÍ÷““ƒŖ#Öī ]ĀĖæĮÍä'îKš§ųSģ<;kŠÛ­īæãųmûûup°4ayiiųũûžŧ_ĪųûömZęOcƒũ#Ŧ+ĶĖUĶĩŋ˙ū丘MÕæÖyĀĐÕjÕ’ ņâ,VŠŠV§8Âqš˜Í­Mo4Æ6¸žžĻģš›ĨË<8<¤ícč>:ęô䄯”Îûëׯķķķøš×ßgøæ÷’ÅÉéđm‡v™ ŅIėŪ÷öšថiû[ÃņíŊ=怿?ŋ¨hm%,ēÔvŨaŗ§ę3¨g„ŸāĨšũ†#íž:¨gtxx°¸¸xxxüõë ¨‘ˇŠÅV`^7˛ílmņ˛|=>n÷y éōō ˜ÛPõÉ k—Ođ*ųûhx瘃oonxĻĻĻÖ××[ŪYˇÃņņ Ų–i(Ū¯Žõ¤[\XäÍ|§>.¯˙ųų9īėÚĮ\tsķãŠáǤļŒm•įE“Ē)ŠÁëØØkęĖkîu *´9^ëgé=ÛlÕņ^å{~] ÄŌŌŌáá×o§f% Îã~lŋíˆmô<Ŧ¯‡‡ŗŗsƒ§å÷›úžíöÂü|{=o}7Īos?9>::>>Ūß? ˛ūîč¨ī׃ûÆPõƒŨ´ÔqĩķĶDüi[DŅ‡_XUŅö´`‹qš´ąąĩĩ5ŊU*ÖkkÖR°hėĘĘĘõ…ņėI“ã!'åxzn~žGûöíģÕÕÕ—{ThmONO:žú‡:>|$]ÔgdgįĶÛwloq¯ŋâÉÉÛXŋšņōiæx3῿؟œžxų4ÜË++ä„ÁÃŧL.ÎßÎĪŗ÷ŠpÜr./[Εrļ×ŋŨ>Ęķ´lG7Īx‚_Ĩõ¤“v?Á ŌŊđ+nfŽŪŽļ÷On÷ƒŖ/ŧ”ÕDīcMJã ]ÕߚĀŪqŧVģĻŸ™šĒÕjüÍ 2==]šJī |ėiųXžëÉČ-ų ä4cÅäĢÖBn†2kKiņÆwKKīŪžũūũÜĶoßž=9ąO.ŪŽŌözģJēŅn¯ŽŽĐ§5ŋõ8€ā-čÔwėÎ/,ĐGP%^:ī;Ōš¤O„™âEtĖztl]~{žĨĨŪ—*ŦmĨōēZ…âŲ)ŪYúŽØ—ī#%đÚҌ˙øa–iPĄôÍ}\‡z|Š<5į;W§ËkŠP›:ķâRĢš™7adŪÚßqƒbŨâë×Ŧ Ikđūũ 9ŊÎ< /3ô?ņ\ëgÃå^”Ī׎¯“ßbwķ팈nnøžđ.Ŧ=7BßN˜ã¤i*ņI|uiy™žģctņĪØĀC§`/רØâÂN[Ōķúڟû­īæãųmîķGĮ‡Ū ņîŦŦ­Hķčī×3÷[ķÆ´9æôyŠu"~˜åž6~Úöļ4y™–švs˙ā÷eLÆū˗#ŽđŊ†VcfnŽŌfæfŅ ŗ1*BûŅ>^áP/ã?đJüŽÆč(IlČ^¯s"Aö¤}üˇˇŗGŗË˜’ņY8ÂņũŊŊššYr.,.îíīûĩčBØxĶhėŧ“pNÜLNē z/Ķž™ÎĪ=9YæôŊ/sk‹œÆ:˙œĪúâÛx—§n|ƒæ¸#˙Ļã8*9ņķāš/šŸ„dL'žĄ‰/üų3ŽÛГLU ÷Ôũ­ų‰ŧ öÖĖØ[Ãņ‰‰*/Åųå%ė”Ŋeûû´ƒOŨÎĒ˙csl¸ m×ū_uŖOâ]`‹ôũ$´Č“Ŧ…ÄŠ’ßūüYƒ˛°ÁƒxšöĐËI-0Ú JHū‚ųŧģO_é]͊BĮđ”ßīåå%¸š%A§U8¯X˜Ö>ˆ×p~Ūp|ų;›sķ7?~äīW~.ÍõuÍpp—ļŊŊžp˙GÖņÍļÔáûų9Ā—‘ ŸÔO ĪuøĻD嗅]Ž•ęēéØžĐ8đi qËcûĪP•ŋžąÁĀ(Ų™ĄM"M#G°$ØšŽ™4G8ÎHƒī—ŲsŽÁ‰â{E;Æ(÷ĪÛäĪņōú§íæ{€xę€ßvÄ9åīfękĪoyž××?RũЧ [žûÞ_ŨlõlŽįqŠŊíō¸Ô°Ô–Š ŽÚĩŗĶxšÆÄ4ļt9—#ö‰äøØžRŒlnnš9B# ë ēm¯0zÃû˙‘Í_ŖŖŌö4ī "*^öt^ž:Ŧƒs~ū™žã\˜_0†`nŽ´_‹<|ļŗP įä8qĻ77č—ļļΠRaZāekAŠEģjíäĖΆ™Ũˆû_ꁡúũy8-vƒšÁƈīĄZō0pUqâ í mãwŠĄgĪBގŠũŨųúõûęöNĩדÎãđŜۭĨn´$Øņ„†ĸšhˇVĩyÅûëm û–6(čĮÃÕæéø`v†ÁĮúV§Ø `ča”Ķ7“` ŖÆŦI|÷u iīģŠŊųģĀ(ĶfÂs§ļēc”´ŸØ<Į6É7x(æ´Á‹:Úay^XĮŖĶ7Ėōw3 1{ƒÉoŊÎŋ‚ûyÚģĮ~ŋJėÜ7_zÔ:xë4ôÅwē0.âRZj;ØEKícÚYp//$­ÃænáÄŧų´\á ĩ†čÛ\YaŧČÂy:jŒxÆgīŸšISÉꥧūB@ÄΟ°Ģ“Ķ<ŖyŦONÚČŪZŽČgooÃĻđ-fuyųüܘį]˜: pY´ƒį^ÎĪZ đO+FĩkĢIāČ÷MũļļÆs¨Æņ~/^OĨ˙ØL:ą¯‡l˜kŠŋ}ûļąnČŖ…“N×-×RCƒËņ"\ž€ôáHxkøđĖ[ŗ´”ô öĩäđ+’“ŸL⹎Žö/9zîōųŽm)žA#vpp„}Œ=Cãû ĸĩŠb.8<÷aŸYe]mJ‡ã´Ģą÷­TH'˙gž iwfFz¤‘…@‡r˙¤í—ŧ_\´ŠˇÍīĖΏ†ŸŸâŨl¯QN;séį翏ßåHŌRÛK´¸č*ØĻēÕëŠQbt|§:Öôv||Dũ 7՟.ok+čMčČWÖv;ŒŌĄ¤ã>O#qŌŠ ÁāIKmTŅÂĸ÷ŗ/ęŨđĩĒcBW}ų‚ŨŒžŗ¯"õķīß9Ô#1H]‡…ŊFˤžöÅ/ųj;Æ(é/Z°—cĸŠ@2’ėx.C(t¯Ũ8l˛%īfîáˇ^įŅņņ ŨōFĄî¯W‹_=äûõđI°žĩœ-qŠ›ĩԁŸŠęZę4ļĀMßŋz8š••eø_žž ’;?ĩąãųŲiĶRWL*—­\ ĩãAŸĮ“vę×įYĢZ4ũdđņ wĻĢŽeŌZq‰ĶĶ3¤ éZ¸8¯%stŪ¯Ŧ$^ÁôŦëŧĢÔ 6ĐËŸ˜`(lį†˙s„ãäôˇúƒå¤EîĀez=ŋŌOÎôâIOßōs?lš/ü*ˆVãsŋ_ąËī áH¸"fjjÆø?k‚–ÚۚKķēŧĐ]tŪĖsg ™ĢĢåoz×:ļĨxÁd@c×ĩ¸Ķô Aqŋĸũ´–‘;´íß[ŸÎ"g%¨GÛ§’“aĸĮ`Ęū\ŪōĻđõęҁlûsä5988<ųĘxrÚëߒĮ@UøÜŸO÷˜ŪŲü^ėũZ^NsŌš°$|ūĻĪj¯sI='Æ^C{_ļiŠg§§9+Ų'ŒZûžą>ēĩ3ŠÎ‰ŗôonjÄÛuį/Ĩ¯ąī!3ģÛÛ&æą™oÚL bn –áņļ9bą\*†j´Ž!|ĶIÎûļcŒōļ:Į6äDôņ#ßr֝ īx.Ę"Úęní’?Ų’wŗ~KŨŠnËŪËKD,Ķú~Ŋˆ~Ēā ÂûČÖQKíŅŠģÄĨÆkŋ?AO†›"ev-5~ŧŗcė~ŪÜāâÕi{Tėņ3û†W¯ŖęŗĪ[mãl-Qhî$TÉ´Ôi“ÁI(?>oLĖēmtpū}0Œ_¸4žžŸúôɯĩžšÉ@–ƒP)~„ŊĢĢmĐZ=/Ķŋõp_—įįŠLÄj°;ņ%~]/Sé'jē+ŧ”iŨÁŖāë˙”ûaËķe0†ņ—gvyųik‹#%>@™|$áœáDã‹ĪĖÎN3+ĀkB—| ȎčˆÎsĄĢ̟¨õŽÚŸKĮzBžpœ ŅīXiĻÖhj˙yU=ŖēĄ!A!` B[ŋĀAÕHŌđx įÚ™vŽUÉDgGīųØū3Tåī|úDŧØ ŌŲRŌöîŽ)íšâR߄¸Ôį%qŠ-fŲæ&í,ã'Fĸ É=pœW‹Æãæ†v–?iSÂgĩ1¯6^ÎåxmP/Ņ–ŲÜÜđiœƒteÅcšōIˆˇ’/Ä!b:öĘ?AĢĐ"sQ4jŽÛÆ4\ˆ9ˆcããܔ“(4Ö4g–sb‚Ā#-1_<.æ âÅžđëž]žŖđÁĮ äææßŦ­5âR'?lÖxÚ´__OWĢ+>$ß@ųw`sŗâæļEÜÁŧąĩÚëę„ģĮk7͝o#ų[“žēRÜØ{ŽE'‹ō øšō=Ė";¸“Ĩø¸šū’ØįpŊĔČc¨˙ŦĶ 7ZHīPķxęĶôëy §Û—Ɲ-yäžŗøĐų3jāëī¯Œ“n´ˇŧS++Äyč7—ųîû—ēW2UZ^Yöļ=õ-qŠy¯××>z˙âeÚ§ükŸ?#oØĐĶëiīû—/ž /;dSųûîõ§dÆĖ‹īßwôO~MqŠũÅG¨ā}P˛K\ęÔĪžŸ‡›Ã’Ė|õčŨf0įŠgGwâëY¸^Oģš‘ųĪŅŖž Ú!P5yl’@'Œ‘â@įĪްÛäĪ"|ߡ¯‹-ĪX__ ß[}ŠĮwķņü6ŋH{Üwîĸ,.õCŧ_/ÄWŗ¸ÔøÆ@r~Ē2;=û×ëę_˛öOoh$ĀÚėįĻ+[›[Ûë61%sĨ]ŌX?D ­j¸æ#”¯2e[ų€|@>đ¨>€ÎĨ)Ē&ŲųQíüāũīKx^ÂōÉĮķķÍO{0šį×7I{6WĩąqW–zcíQų°œįÔmŒĢãō ų€|`8} ĐŊ!•õũäQûJų˙Ÿųŋ0ƟŲMū֋ŨKí™ŲŒĨž-âR{TĐBK]ĸ%}(ŨUŽÉ{¨2UÎKĐ0éåįōû@ˆacVë:4ˇDķj†Ė„1ÔN>rģd?hũâŒXû§’Xę›$ԜŸ#.nģ–Zc—^Æ.Ę#?‘Čäōų€|@>đ\}āĶÎūÆÖæeˆēĶQKãR;žöy‹a Å o÷`æÛD> Čäōų€| ų@dŠ]ŗU'–úô&)BæĻĮŽÎÎΊõÉG*Ŗ•ĨÃęčA‘buâ”úh\mČĨį6ųēBž"ĢŠąm§æ{?Į f‹X0­ÄNųÅú‘aíƒøkLûŗZžúPq­Ņ_õ_Ŗ•¸gIw*äGú™Îęāž0đuHÆC~Øî%=qŗ^ašâYû×^xé°VĄŅ”Ø{Ôö^Xû^úJø5˛7ȚĄ[Ņķ›§ÖøĢLĄŪ"y#eiK†§ãåÛĒ^ˇŧM go7Í_¨g~$ΰsC{ÛéKZƒØG.†Ëå5iic[ËoŋĮ.ūÖ°aQ˙v;ĮöĒQæ--X|^ŽhFKI˛jŅ%<ۜ§;Ė'+žZęĩŌs|ÚGîĢ čÔã ŧˆ‚Đ{ųžŗ÷üíOäŽį>§üĪÕ?YŗÅâ/3qv~aņēæÍŗ9™kŠSďžÂ ŋMŒTĮ*4(ŪÂZo‘ÚîøÖ>l/ÛŠüÆu­ˆ]J¤ŒS÷Ņ­õ(%x:GØ)Yxëžnô(ÖĶééÂZüô•Æõ~¨ŅđFđŸĐ|Ûu#žīũx¯ĶbM +ÅēY=C¯cŖWÉįĢŖ‹Ĩ{I}OŖ/ņ[nę}›úæØOģoØķzX¯x&ĨÅņĢųņg1ĐtĪxn8ėī/UáceéŅŪîąžĖąf/é&ŦYôn‹Xļ­=iÃŖEĶĶ@É~N‡ gąĄpėí7šæˆ­â ī`žN(šåîš[õægXŒqšßFԞZ€FEūëŧŧ€üąáLŲ}lŸą$-¨ˇzûĶ~õâxķ;}Ûmë>ã-^Wv ÍVYÎØúÅžĪš›üņ4ؙĸ™Ė{„ū§ÃË1YŽ{N;ģäõŽé;ÃŅöęß^ÍXĩb•`pķq¯ĒP@EŽō}|ĻŽ™œ}ë’~Œfæ>×ęvn~üŽuîZfcĖÖåe~ō>đGž×“¯ļ9Mķ¸Ģ›ķENē~ķĢBŒų„ į§FXŸÖ õæÆæÄėbí§ˇt‰qi4æMLâc VŖĨr\Õ{ÚŅsâx ŐtjyCŊY"—ũΟ–ßZ1į§mcšMū°”q-9\Ünūodmc™nģQ´ãB¸9áo]ė!âšŊh=GãUÍP­_ąCûNoëŊ-MĪÕpĶēÁ-ŦIō€PĻcč`1”ÜÔoė>Į˙Ģ@*˜-1ÖŅzŽųü~}Ëēė`%¤}jZzÜÆŠŖõ–5ŌŊ?w¯aŌ%÷€xŊĀĀļ<÷ŋß[ž) YŗđžYÕßë˛tņōõl˜ĸŸsķ>¯[ēS¯ŲŪöp$ŽĀÃ5ŧūŽdMnŦCņu+žŲaÕJ†ąŽãŧfô™ãN˙5Đ MmˆˇĢÎ4[ûc˙„7—FĐÛC–<Ėč9Öą÷=don´XĀŠV5žNĸ-´ŧãā7Z˜bÍÂPZŅ0sdÂØ&‡åąb;KH-’yÅĪX‚]ša‡ŧĄéŌ˛…ú§n!˜ÆŸEņ ŗŲX 2ŋ ė~öÎ ĘnÅ{Ö`(îĨčWšŸ{or1^Jhļ;Īũ­=íGâžh};ü!Pø%˙V™ÎÍËšWēđmīƒŌKĐ{ēä.îôSŗ+Å÷Ôũ3ŋëĻy§ ŧ¤ĖŅg‚uđˇæãí>é-Ū->ā%gūĶ”nU 4yūŊ<ŗ§ŨF†A‰›ūũh{{ëīų7s{;ÛSņkZnÛņtøØĀģážÃCą]Ķ‘įyē¤ķ23<ŋN̍‡Ū<į_Wíhhĸ0¤`dĶCëäëĨxšēčQŧ_IŖØPĻ÷4Ž?á ´ˇ3õĩūMÖÛŠÔiÚGtԛ|¨SēhæcNŋLÂÖ~ɖ}qĄÎāĢ›]=ŪQA§ķčŌâ÷Ō`Œü*E—¯XXĀíŅšŖęđ0"R÷¯ ņKwĀĶôëVÚ-éāĄ1ĪäoCķ OsŨū¤ũ*ƒŨûįOīˇķ3*ž/m‚a¸øÄü)Ow4<Ęũ*í‹Q\pąˆ†ģĨÍ3›ŪâünBŌ‚įĮ’=ŨĀ͆ĄũŊp4ŊÍéā˙é j4ŧ[;6 īší[Ņ^öĢį0Ū+đĻķĐáË!˙„}¨īŖŊdOĮfɞ”-ūj$^Ĩaąøe/ĮĶĄCŒ™Vvn¯§C‘V˙Œeˆ<Ž#(ŋ`„ę>ö‡Bk`õ R>ģL2O„´nŽŠûíųslÜŪåšĄSĢ._÷ō=OËž¤ŗ§āĢ „‡Ā"ģöü‹įkĶ8ĘGDEeKŌš9#VČ*•WŗkÚëæ×jN'$5~á1tŲÂ}šcžXˇxī'åˆ3O×ēģdgu¸b~õ.é’ģčå§ÆēÅÂ>?ąeáŊpđIm%ū­Ô4IŲģŖŌ;ø@bSiæŊøj7?ī~Üߝôų›„5Ē#,ĮÆÂ‚˙×˙ü.˙÷˙ŽūņéŠ˙ûį˙÷jdä÷ĢđĒŽû˙ūĢüūõth´~įéW˙UFâņē§ ũ=âéWŖ#¯éĘ+~m/3ÕÁŽõęU}ä÷ŋ•ęõß˙ŧ"˙˙ ŋ:É2ō/īĀĢū㇑W•šnņRĐR5߲4Ĩy™i?2ōķĶŋF¨§5ĸ˙Pæīß˙xڎ„bėøČīĀ<ŲģGņ¯H˙.8æWÁēT„ãMięoĮķŊY#áŠwMû¯žĮžĄAíœö_‹gQÜ~˙ķĪ?˙‡ûųÍ>XodôŸWŋņ™‡{üũģūĘīÅöV>?`Lģ‹ įųãÍŌĄũ­^ĩ};˸j¸IōD~úŸžŌ<å”˙aŌŨŲqzĘW?Ũ‡´_qxö‰ĢžĶŊw{.Åŗū]ņgŨ´ų¸ępŧ=mGÚ?Iū3jž×´yeˆ㸧¤ę˜nđ͝ĖĢãŪætÎa›?ûûnyšŌ¸ūHũ7īBũwŨ^ Ū—“ųû^ž€HBĢš {aŦ­¨.ŧãŋ+ĄdÚ(^GËųę÷ŋ˙ūˋöę_ûãéđ/­ą.ŧ‰˙b^čŅ߯ūú÷Õa˙í/i;ÎĘu˙ãemđŠvJũ7W˙ĮĒķOxŨ˙ §Ģū7ú&äWk?­VĄ>–æWËÄY˙ŧâgV;ëŋ˙ūûũĪīĘŖ¯hBūûīÕīŋ,Ī?ŋ)į/rZžß˙ü÷o8ĮÚĪ+”OėéTŦ…ˇ°’ŊÕũ‡ėägŊú—ļéŋ`€QŌÖĒüKûdĮC+zĢE9xĒĩ9ÖōāG\-å åûm${†´Ũ—ŲŧHĶGØ!ūĄú^aGû1¤ídęų_…ĢcĪ˙B“Ī>ĸąĸ­ļüŠÃÖNūQ:ôGt„wÛwģVėqė ¸bŗvŖŋhNķŗõa˙ĘúM;Īúũƒúĩ°GbÚûĩĻ}ÖßÅŪÍķ´Į˛ūŽįtĒŋßŝö Ë˙ĘŪ;ÛûˇTN:ī‹W #†įÂhĘŧNû`Ü&} =č?üä>ĐĀQˇøU7k?ŪîŊv¤ƒŸį>ß%íČŪo˙ÁĶ•“˙õåû˙{Vũŗ ÷üÜÜĮõõ‹īGŦū21Ö>¨o:âŦcķbĻ`käzģГšN ‘ PTŧiČĶ(ŗH‘Xđ0€ôĢģ28¤ãXŋ[ŲņP#]šáŦ‚a īxÚØ\đ4ûTr¨ŗÕ6 õĢGÅ­ĐÄ1ۑ˜1°/Mi?Ō´ŋ•Ÿn°_NPxÍ;ĨŦXvwVsXę‚ĩŠOÆR›… Ž(Ū]bךËəˇ|ÜįZ´†œ5ŅĄ\1VÜŠę`OcŦŲĮšXÕ žŗhĶ­E UąĢįûĘxS}ėÂĄœ˜Įb”P~lū­n°ƒ—ogYMŧæéŪc:O=HȘk[Í­įFŨ-i•dÃX!¯Uü5jdI ęū7jwáú™Æø$˜Øl’tzĄK)žÂ5Ō~¸Ŋßé|ܟHáᎧ7ŋūĩÖ!8ZrUī"bßû‹L7XxuäūŊåO~+l˙OîōwvNŖsđsОŠū.Ö-ŧŠė=ŨÛųÖš¸–_1xEą5rߚĪ(ž)öÍ' ‚Ŋ-pŅz–muŗ?¯‹O6¸L›Ī´|ĢīåšžŊ%\ą)ûXoéÆĶoņéV?ĪëߒN^|<ŧ5|/œ­VNŋlŽoNU'ĒÕę_ÛģĩZíæĻvrrÂ{¸žöqyå}‚mą]Î>Ä ęHö>ÄöÂûß ŧŲ^éŨîlÆ?Ŋ¯Ļ/‰Š‚CÅC/āwlëÚ۝øŲ2Ū’×ÜÚ ĸeė–îđA5uXIyãCî ü§ãu)VŊÉū(OšĢ­(ĀOĢ/ũŲņŦ§/iĶKė@Ģq­Îųũ]Č^­ ABé:ś’—ßžîÖĩßo^ˇærēųjūŒēV9žúEUÔ§§[Ėo:/§­Ėŧ´ģyNˇįŪģŨēÛŧüojģ÷/y_3¨ô`:úŨžûpŋËsėÚbôŌĒ (OÄEŲ¨čą}˛ŗī ›o<ôrôåhk{‹Āņôøøø_۟ë7?ūŧŠũ¸æßËË˸h7;ŪŖč hLt,(2O9ļŽ-o›vŨ6ŧ<ë rāЌ/ŗŖ@đMŊš_ĢPšõĸāéCžæ{Ls ۟@Š3ų›ģut:(ŅUd§gøRĩFįŒAöü‹\SėÎĻwĒ ÃīhƒÕ3v •loLģbúļ^ŗ+ž/īq}\ííŪCõÄY™ÍÃũ[ŲąAwÛvmŖzLtjįÛīą;0”íü}\+ Č÷ļ`¨ žf¤ū˙ĸúڐ~2IENDŽB`‚PK!–ĩ­âņPword/theme/theme1.xmlėYKoGŋWęwíüˆ’Ŏ -ĸÄPqīŽwĪîŦfÆ žUpŦTŠ*­z(Ro=Tm‘@ę…~š´T-•ø ũĪėzŊcÁTĨŧķøũߏą/^ē3tD„¤A}ÍŌ۞2ī2øJ”Ô >‡š5ą( 6ÕôCNd‡ t„YË9?î“ģĘC K-¯j>^eûbĨ bj m‰Žg>9]NŒę†N„ƒ‚°Ökl^Ø-øS‹¸nˇÛéÖ ~€},Ít)cŊZ{Ęŗʆ‹ŧ;ÕfĩaãKü×đ›ívģšiá (6đÕõÆNŨÂP6l.ęßŪétÖ-ŧeÃõ|īÂæzÃÆPÄh2Z@ëx‘) CÎŽ8áߘ&Ā U)eWFŸ¨ešã;\ô`‚‹Mš¤dˆ}Āup<kx‹āŌNļä˅%- I_ĐTĩŧS 1ƒŧxú㋧ŅÉŊ''÷~9š˙äŪĪĒ+8 ËTĪŋ˙âī‡Ÿĸŋ÷üÁWnŧ,ã˙éŗß~ũŌ Teāŗ¯ũņäŅŗo>˙퇸ŽĀƒ2ŧOc"ŅurŒx †9x=Š~„i™b' %N°Ļq ģ*˛Đ×'˜åŅąpmb{đ–€ā^ßą>ŒÄXQđj[Ā=ÎY› §MWĩŦ˛ÆIč.ÆeÜÆG.ŲšøvĮ)äō4-mhD,5÷„‡$! é=>"ÄAv›R˯{Ô\ōĄBˇ)jcętIŸŦlš]Ą1ÄeâRâmųfījsæbŋKŽl$Tf.–„YnŧŒĮ ĮNqĖĘČkXE.%'¡.D:$ŒŖn@¤tŅÜKŨĢz‘3ė{lÛHĄčȅŧ†9/#wų¨á8uęL“¨ŒũHŽ E1ÚįĘŠˇ+DĪ!8Yî[”Xá~umߤĄĨŌ,AôÎXä}ÛęĀ1M^֎…~|Öíāŗoūņŧ“\•0ß~—áæ›n‡‹€žũ=w“}iūžåžošībË]VĪĢ6ÚYo5ĮåéĄØđ‹—ž‡”ąC5aäš4]Y‚ŌAÍÄō4‚a.Î…›1\}BUtáÄԌ„PæŦC‰R.á`–ŧõŧTļ֜^Õ˛åĩōŰ`cfĄš|N­iĢ [ģp:aĩ ¸ĸ´šQmQZa˛SšyäŪ„j@X_ûkëõL4d f$Đ~ĪLÃræ!’H#m÷ĸ!5ãˇÜĻ/yĢKÛÔlO!m• •Å5–ˆ›Fī4Qš2˜EI×í\9˛ÄžĄcĐĒYozČĮiËÂ! †q ü¤n@˜…IËķUnĘ+‹yŪ`wZÖĒK ļD¤BĒ],ŖŒĘlåD,™é_o6´ÎÆG7ZM‹ĩÚ¨…y”CK†CâĢ%+ŗižĮĮŠˆÃ(8F6ôÖŠ öTÂ;Ã䚞¨Pŗ3ģōķ*˜˙}&¯ĖŌį=I—čÔ nƅfVR¯˜Íéū†Ļ˜’?#SĘiüŽ™ĸ3Ž­kúp émy\¨ˆCJ#ę÷Œ,Đ AYh•Ķŋ6k]ÉŅŦoebĶ%‚D<—<.Ål4ŗÅLšf•IøˇŽQŗrįœ].Ž3tvq\šsöËÅŊšŗķ‘åër9\]Y,ŅJé"cf ˙:ņÁŊ Ĩ1SŌØGîÂUŗ3ũŋød éö?˙˙PK!%Q3īZ7 word/settings.xml¤V[oë6 ~°˙äyi|Ob4=pŌf§Cģ,ėMļ•D¨.†$'͆ũ÷Q˛'­7´ŨC[™ų‘E˛×_^ėąTDđųĐŋō†Ė Qž{ZĻÃŌˆ—ˆ ŽįÃ#VÃ/7ßw}HÖÔÔ(¸JY1î´ŽŌņX;ːēæn„dHç܎’Īu5*̐&9ĄDĮį%ÖFˇĩäiK1b¤B‰6&ŠØlHÛ?ÎBžĮocr+ŠšaŽ­ĮąÄb\íHĨû,€;G˛˙¯$öŒ:ŊƒīŊ#ŨƒåÉâ=áƒJŠ+bÔHxį8zCtō}žÛ-˜ûž=GŒ xCøåcĶ–c –į<¤üOrâ!ŨÅúÉį‚9#Pøcą‹CY—‘ĸīŠq=\"ŲtP[`V¤÷[.$Ę)„…@­6:ķR6ė|Ū@#˙)Ō Ë^3LĪŽ oHlÖi°LU…)ĩcĄ ŖCē•ˆAC;‰ĩ)ņÕT?Ą|­EJ{ųLŧiīŽÕsÛvĀ@qxÄ ^ėD…Ær]Ąŧ-×RP§WŠŸ…^Âđđļ[ÆRŽw¨Âˇcus-Rem$j°Oņ ¤…Kĸa˜U¤d^āE3Ã0îŖ8¤!4“į_‡yl#ŋņũJl¯müÚķōÍĮ+žKŠŖš0l&fwZ7ĶL8bP苉ú(Jl TKōūi ė%ûA[‹^GĩÂŋƒ6<ßđ JõŧZ öĩĢë˙pÜ&Ūå {Š´å0‡_áZĒįųˇņâîļ‰Ô âMágŌ‹ĖŧYö!~ä…ņ]/ō¯~‚8 ŧöŽ^!I<Ŋ]õ"Y˛\ļUŋDĸ(œ…Ŋ~âdâO–}H&ËUoÔÉ]8í÷3‰ƒ(ëzÆĢEīíd3? “>d‘MŪĸYNŖ(ŗ]?>ՑĨfKšŨœVĐŨÖX,Ë%AƒGŗGĮF#—Ī ÂžcøYךGŖP Qē‚géÛK,-‰Ē ģí™>"šíx[ Ų+…ō͉ËLG,”ĸŽô QuĪKÜ%áGQkI¸~ ĖÉU¯‡}Õŧüe/í=u×sH54 6÷ķ€lķ5ãnÛrTŽMcáGTUMæ[>¤dģĶži) _%üģe?ōmĐbÅ‚ŗ¨0™v{čd“é…Nv˛ČÉĸN;YÜÉ'KŒ v–°RžaT¸Ŗ‘oĨâ€Ë¯ūFÔ\‚]÷ŧ u‰á5”ĸP÷Ü,,ÕÁY­…ÛßHĄk8XÔ.‰ĪnV›ĸŖ¨õ…ŽÁŒruÉP"Ü„ģ0ļũņ*ŗF oy}dyˇũޚŦ)Q0•+X”ZH‡ũ`1?‚¤‹{ŗâŖFfĢd„M‹úņ Žø¯Åb˛ˆ—ž?JĻ3e‘?ĘĸÕt”ÅqúŪ*Z-âŋÛ.v˙ņßü˙˙PK!W%ŅRŠØ(customXml/item1.xml ĸ$(  ŦA à E¯" ēčB’@ ËRnēčFíĸhn_)Ĩ'čōŋŪ …L{1X™Ä€†đ)é8ōĮŧĖŨ]^9û€›Š 6ÆŲ+†­ =rG”@5ŖĒ]ʸĩĪĻĩYVHÖzƒ—döˆÁŠīĪ Ŋ>­Eew|eQMübĻ7˙˙PK!ŋ ÂâU(customXml/itemProps1.xml ĸ$(  œMkÃ0 †īƒūŖ{jwéGRâ”Ļ] ×ąÂŽŽã$†Ø ļS6ÆūûvęŽ;‰GBz^T>Ė@îĘy–Ãjɀ(+ąŅļãp}Ģ“ ˆÂ6b@Ģ8X„Cšx*ŋoD> S—  ‰ ëåĖá+cõË.ĢĒ$Ī7ĢdovÉņ”åI•O5ĢŌtģŽŋDĩg<‡>„qOŠ—Ŋ2Â/qT6[tF„ˆŽŖØļZĒ3ĘÉ(č3c[*§¨7īf€rÎķģũĒZ˙ˆs´Éé˙Znú6hėœûO eA˙¨f~xEų˙˙PK!eˆˇiword/numbering.xml´—ÛnÛ8†īØwėeB,+BœĸIáEģmŅf€–h›ˆH $}zû’”(;QcȲtcZÎĮ™_z|˙iO g‹¸ĀŒÎ\˙ÖsD3–cēšš˙ŋĖo×ŌŒĸ™{@Âũôđį÷ģ”nČqĩĐQ *Ō]™ÍÜĩ”e €Čֈ@qKpƙ`Ky›1Ør‰3vŒį đ|Ī|+9ːŠķé ˇÆeûn´œÃrÖĀdkČ%ÚūŐ ¸IôŠ ŋ /FÅ@GÕEŊ@*ĒiŌô›äâ~¤ Mšö#…mRŌÔ:N¤}ĀY‰¨2.'PĒGžō×MyŖĀ%”x ,ŠéÅ1}í‘ōj$Ė/&La9*ÂÜRØĖŨpšÖū7ŋ=­üëÁzđ.ųW._Xļ!ˆJ“9ā¨PZ0*Ö¸l*œôĨ)ãÚBļį’Ø’ÂŽÛ•~ĮrųčzúRIyv ŋ֟Uäį‰ž×áhDãŅ%„ˇ{ÚHˆ:…Į{Is"Žßņą€ ˆ3Ôņ¡Œ¤f€ėXĄšƒ;–†åToEsđQXŋã=ö>˜€@—!&6q '•ĢëŽíߜmĘ# _G{>^B;Ũ\ĀĒ˙iIŠë‚ųš†Ĩē›H–>¯(ãpQ¨ˆÔavÔytĖПęĩęÁ|E{G_îƒę_āBH3ųuCœ7OĪęŠ>HARŽTķÃõdÕę|^JÄ9‚¯z‰ĻPĄņé37¸›ĮņĶäŗ ´…l ‰˙E[TŧJd×Ŧ Žķ˙´­Đļj­$eŅPæq=†O•ĨØjVCT*ËBũ y‘7õu“ęŪtŽ6zŗŋĄ|Û"Ž:NŨ€Ú.˛‘õÄv”ˇÚܚ>ĸT{_K Ą„ƒPĒ×R&ƒPâA(ĶA(Ʌ`ΧĢ˙Mŋ˙˙PK!%ÍI%Æ Æ‚word/styles.xmlė[sŖ:Įߡjŋå§Ũ‡™ÄšĪÔɜĘu“Ú$“3Îė<Ë Į:äœËųô+ …aZčd§ļöe&ú‡¤wK1üōëkĪ4ËOGãÛŖ€Ļ!Xúx<úūpųáhäI#ķ”Ūh>úõË_˙ōËËįŧx‹i@šNÂãŅŧ(ŸˇļōpN’ä šŠ3ž%¤ŗĮ­„dOËҐ' R°)‹YņļĩŗŊ}0ԘŦ…Īf,¤į<\&4-”ũVFcAäi>g‹ŧĸŊôĄŊđ,Zd<¤y.:Ä%/!,­1ã=JX˜ņœĪŠĸ3ēE %ĖĮÛę¯$^öq€8é+Žq¤[ÂŌä°Į9¨9,28n19Å!öĢväo‰ėQ~ž~LyFĻą 1ˁËEkåęOqøá°ĪéŒ,ã"—ŗûLԟÔ—<-ōāå3ÉCÆDģ|[ĘA'˂ë“(@ųŨC.bTDė¤Lb/Ũđđ‰F“Bė8Šs‰ß¯ī3Æ3‘ŽGŸ>鍚°+E45Lį,ĸ?æ4ũžĶhĩũˇKāzCČ—Šø{W4_5".^CēéBėM‰åNÄōč%[\™˙ģ‚ĩmösJdÎ ÆëÕ|bGZäFoۙËĩžĢŖP'Ú}¯íŊ׉ößëD*ŪãD‡īuĸŖ÷:‘Âü™'biD_Ë@„§ÔMK4ĸ9–`Cs,ą„æXBÍąDšcqt4ĮâĮhŽÅMœ‚‡6/4œ}×âíŨÜÍs„wķ”āÆŨ<¸q7'|7îæüîÆŨœÎŨ¸›ŗˇws˛ÆsËĨVp-Â,-G،ķ"å ú:œFRÁR…¤žœôhæĨ“0efĶņ`ZHÔįÍĸ‚Ô}>/d…đY0cËŒæƒNĶgķ H žG`F‹efŸÎčŒf4 ŠOĮö••`.“Šß\Go,šFž‡¯"zI ĩC‹úy.ƒ„ypꄄŪ4Nŧ凖+ N—qL=ąîü¸˜b ¯ fxi 0Ã+…^šų"Mķ4RšæiĀ4Í͏•ūékÜ4Í͏iš§qĶ´áãöŠXĨxsÕ1îíî,æōŌ˙āvLØcJÄ`øtŖ¯™÷$#YĖyYēkö{žSŊ>洚äk]¯\äLôšĨËáÚ ų ޚį)ŧjž§ĢyÃCėV,“åíĘO=3YN‹Ö U¤^A;!ņ˛\Џ6R ÷°U\˛,÷íX|'—ŗRN™oÕĘá [ą†‡ÕzVōÚ<ôĐʘ‡O~ŌđÕۂfĸ,{LēäqĖ_hä8)2^úšō;J’^!‘,æ$gĒVj úOõÕMÁ-Y îĐ}LXęGˇ‹ aqāoqõp{<đ…,3åĀøžōĸā‰7Ļžøˇtúw? <EpúæŠˇ'ž.)Øķ0ɔ$y"‰e&K™—9TņūIßϜd‘Ú}FËût ę‰8!Éĸ\txˆ-‘_Dūņ°Rŧ‘ŒÉëBƒiƕž|9ũ†ÃŗĶŧ\Ėųē,Ô%Cĩ:UÖūpÃgönøŦū ŽōM˜t9mā†wļķÕŲŗ˜ä9ŗ~ëéĖķÕŨŠįģŋÃë5Íã1ĪfËØßV@o#XŊ !—Išûėąâyė°âųî¯G—Q<WŅī‹ŧ‰Ą`ž”P0_2(˜/ ĖĢÃoĒ1`ÃīŦ1`Ão¯)až–Ė—Ÿyū=}1cĀ|ų™‚ųō3ķåg æËĪvĪ:›‰E°ŋ)Æ@úō9éoĸI š,xF˛7Oȋ˜>×4KÚ}Ægō7<-īģö€”—•cĢãtęÁiNIsîé:Ņ*+Kã"Øū§fęg ƒ›p“ÎyŅĖŌ'ģ­($'åO ֛¯šŅëŪ {œÁd^_š61Û-ĢJļaļų„mc~Pũ6ŖÍė–Fl™T …? 8ØíoŦ<ēaŧˇŲx5Å6,÷{ZÂslļ\-–‡=-á9zZĒ´Ų°ėŠ‡s’=ĩ:Âa—˙ÔŏÅųģŧ¨6n=m—#Ֆm.xØåEP NÂP^ų†ęô‹ģ}ŋāąÛcĸČNÁ„“Ō;ŽėˆŽûFŸ™œō0IS¯ž`ũtģjuŲ+sūļäå5诗'ũ t-ViNƒVÎn˙/aYÆ>ŽŊĶŅ;īØŊŅ+YÍQ)ÉN靛ėˆŪIʎ@g+8#➴Įe+hī’­ Å%[ XØŊ—v:P!¨V v*PšS B :P!¨T¸Ã*´Į*´w THq THA*D "Ё č@…t :Ží­æN )č@…t B:PÕzq@ B{\ B{—@…—@…t B:P!¨Tˆ@*D ˜;*¤ "Ё č@-6į¨Đ¨ĐŪ%P!Å%P!¨Tˆ@*D "Ё ¨@æN )č@…t B:PÕˇhÚãÚģ*¤¸*¤ "Ё č@…t B:P!¨ĀÜ)P!¨TˆčōOũU í–ņ1ūǧõîķū_]éF}3–lĸvûŖĒVŲYũīĢ?åü)hũŨŽĒ7úAØ4f\]ĸļ|ßlrÕŊ¨/>ŋžu˙ZŤ|€ž¯_}g ā{}-Á5•Ŋ.—7-A‘ˇ×åéĻ%Xuîue_ĶLƒ{]IWÅeuˇ†˜Ž€qWš1ŒĮķŽlm˜Ã!îĘ҆!áŽĖlÂîĘĮ†á~ “ķēõ~Īq:¨oŧ„.w4‡vB—[B­Ēt Ŗ¯hvB_õė„ž2Ú (=­ŧ°vZa;ĘMjfXŠŨÕNĀJ NRŒģÔå,5DšI #VjHĀJ힜í'ŠÆ]jˆr–ĸܤ†SVjHĀJ XŠNČVŒģÔå,5DšI wXŠ!+5$`Ĩ†'ŠÆ]jˆr–ĸܤU2ZjHĀJ XŠ!ÁIj€q—ĸœĨ†¨.ŠÕU”†Ô(… sÜ"Ė0ÄMȆ!.9†ՒaíX-Įj jUiŽĢ–LŅė„žęŲ }e´PzZ1xaí(´Âv”›Ô¸jŠMj÷@ĩ°RãĒ%ĢÔ¸jŠSj\ĩÔ)5ŽZ˛KĢ–Ú¤ÆUKmRģ'g;ÁIj\ĩÔ)5ŽZę”W-ŲĨÆUKmRãĒĨ6ŠqÕR›Ô'd+Æ]j\ĩÔ)5ŽZ˛KĢ–Ú¤ÆUKmRãĒĨ6ŠqՒUj\ĩÔ)5ŽZę”W-ŲĨÆUKmRãĒĨ6ŠqÕR›Ô¸jÉ*5ŽZę”W-uJĢ–n… ķđ8ŖIB˛"đ÷ėŗ+’Ī 2üA{ß͌æ<~ĻQāˇĢ7¨^nŊ4^å$ŲęmpâøBŒ™|šˇņsĨ¨|šŠĒ¯Ŗú•KŌXļ$Đ/ˇŌ›Uƒõ×ĩęī,5ĩ>f{{|žzq^e{y•ųęĒŊúCûĢĢ,/;‘˜M3õ/õn¯Æ–0?/+ĄÕKˇTo7ŒO="ú î1“ÕÛĨT‡§DHņUJF,•O&lŲ.Ŋ¸Ú^ælN˛rī*žĒctą °s°t~Ųā‰Ōŝ8ŋÚ&?§ĸšú´Ōf*ęEe–ĶŲ…—]ēyŽkxaĒWŦ‘ß[ŪĖfîŧĐÛj[-W/g“›OY¤ßÍ6-˙=+{ĘÜTĩr÷`˙ō“ōheĒō–đ+•ĩV›å­4{ĒGĘxכÎōwŊŠmŊgĮę=:qúņžŪĶ˞CÉWÕYĒĘY?ˇCí]OĪmĩî>zĄŌpĩmŗû„B9ę'×Y’ŗ~uũŗSõüéuĮ˛<ĻÚâUęĻŨGėí.äĄŖÍj Ņ9Ģ”Ģ Ģ×jˇŨÔBŅži\úøã:•Nüĸ§›˛ĨŅ+)Qb˙ã[RÍöCc:“ą'öގÕĶOÖöOËgoZí3ĩ°ĩļš)?vûIų6}Į•u—̎–áVˇ˙ éž>.s14j-˛ŪžÆ\ˇŪJŊSTĢdļ–[ã ÔĢeFŨås~ĩŲsß˙§ģZ—3“MëOZëÉôRëw›‰Ú22sŦžw°.¤ĒWģۄ4—.P ]}O$Ǐ`*ËË-ī]nŦPąŨĶRĪÎYī\ãaA›ēfzĨí\˜õžâė#b)”Č\Ô?ĩ/šT™T~Zsžq‹ķ”ÛüĨ…õĄéķĄÉÁÔ'5_üŒCßîØå-äëÃ[nŨäË}*EęōPEÆâ4R˙×ë+u¨|!Œ~ķĮŖōitĸä}W—™Uēė‘(eŽĐ|pybˇ*XBų¨ļ×bIbũp,#šüwሯŦ0>kĐYŲrõ}Zëü_÷Yô-åO4Ģ}`ĩ.¨Ō3Ģé—å6)aåI­Ū:4 .owԟ~đŗ‹_ũ•ų˙˙PK!‰a™ word/webSettings.xmlœ”ÍNÃ0 ĮīHŧC•;k7*:¤ !!qâã˛ÔŨ"’8Šŗ•ņô¸]Į ã@šÔŽ˙¯vžnnß­IļHŖ+Äx”‰œÂRģU!^_î/ŽEBQēRtPˆ¸ŸŸŨÔy Ëgˆ‘gRÂGšU…XĮčķ4%ĩ+i„'+ VF†UjexÛø …Ö˨—Úč¸K'Y6&ü…‚UĨÜĄÚXpąÕ§ ŅŅZ{:ĐęŋĐj Ĩ¨€ˆûąfĪŗRģ/Ėxz˛Z$Ŧ∛é*jQ,g­gÍp9 09Ėŧc\wŒ”•}Ž.‡qf_]ö8˙+Ļ †¸<ÔA;ÛtdUū°räŌ0‰÷(áeNZpķåjĶē<}ΧļÔ[ęlRįÍ:LŗI6ģēšfmžOį#T‘s[i ‘‰´‹>éÕú—đ úĶācDû#Î\”ĄņâQãøV ĐG3¯qŧTĐų ōe›ˆ{„éU6LšüVŅ0mčw>Dš›ŪģÛîú¨­ū€{ ‹€5AØKzīĘü˙˙PK!äs‹Ū2word/fontTable.xmlŧ’QoÚ0€ß'í?D~/qB 5TjW¤Ŋėaĸ?‡X‹íČgHų÷;;21ֆ‡ÎųîËųË=>ŊĢ*Ú Ōčœ$#J"ĄšŲHŊÍÉÛjy7#8Ļ7Ŧ2Zää €<-ž~ylæ…Ņ"Ŧ×0W<'Ĩsõ<ށ—B1™ZhÜ,ŒUĖá­ŨƊŲ_ģúŽU3'ײ’î§”NI‡ąŸĄ˜ĸ\|3|§„vĄ>ļĸBĸŅPĘŽ´æ3´ÆØMm xfUĩ<Ť>a’ė¤$ˇLáFx˜ŽŖ€Âō„†•Ē΀É0@z˜rņ>Œ1ë1Vö9r3Œ3=qäĻĮš­™Ä0Ä䨔?‘âķī[m,[WHÂwĄæ(€ũ/vë˙ÂĶŨŧFÍ\3…+ŠD?Dũ4ŠéP3m@$˜ŗgUNhŠ×”Žé„føMq•‘Ø'ō’YÖ&Ō6\0%ĢÃ1j7lÔŌņōß3+}ĶíČ-nė`MsōJ)M_—KŌF’œŧ`ä~6yî"ŠVø3nM\ya•\[yÅÄ2đW†ŌA& ‘ÃLd3‘f÷˙ɄBėŠ ? íLøŲfâļ™ Ķž‰,õ&No"=Ÿûß&šXą;ž"âøađ*˛ÛGBˇ˛;ą:Ôbˆ˜ŽûņYL˙ĐŒČĮbPÍbē,~˙˙PK!ęYį8eŋdocProps/core.xml ĸ( |’ŅjÂ0†ī{‡’ëĩIę&Rj…mx5AX‡cw!9j°IJ’Y}ûĨUĢs˛ģ†˙;'šOvNJļ`4zŒhBPš!õjŒ>Ęi6×G번IX%Œŗ¸)÷Ë ~xģā<†¤1$aãĻėSōįŒEŲã â‚Ú–:­ ƒHCĮ\Ûj‰WNžh;ŽĒÃ÷„VĄ:ō“`9*žŋĨ˙Š*'ŗŋøÔė=éqhpđF$ä÷yŌ,”K°‰…Æ%a= ?&z°ÆĖ<ģ "_VČ%l{„L´A~ļ>6ÃpéŊŅR$Ú-ŋĶ2¸čÚT<|.ō<°ų 5Ę× ĶžWĀænĩ%§ĀƂœŅá{˛“íMj) n)>o…‰뇀­ŧ°{^_îČęČÚ/ņŅ7î*/ãkę79ËųŦS_{!ÉÅj}:O<ë@M,*Š0š˜¸ĄW &ëĶŦíP}ßųÛČ;|ŋ'_ŽĪĨ}s}ú7ü˙˙PK!t?9zÂ(customXml/_rels/item1.xml.rels ĸ( ŒĪąŠÃ0 āũāŪÁhoœÜPʧK)t;JēGILcËXjißžæĻ+tč(‰˙ûQģŊ…E]1ŗ§h ŠjP >N~ûũjŠÅÆÁ.ŅĀļŨįG{ÄÅJ ņėĢĸD60‹¤o­ŲÍ,W”0–ËH9X)cžt˛îl'Ô_uŊÖųŋŨ“Šƒ|Pũ=á;6Ŗw¸#w åE…v §°üd*ĒˇyB1āÃßĒŠŠ ēkõĶŨ˙˙PK-!…6Nšc[Content_Types].xmlPK-!‘ˇīN Ķ_rels/.relsPK-!T>kŲ ^?ķword/document.xmlPK-!Ęc^`'word/_rels/document.xml.relsPK-!!2–ą{Ē Įword/footnotes.xmlPK-!Á Ŗ{¤ rword/endnotes.xmlPK- !ķ7ŧšęięiword/media/image1.pngPK- !’j•¤—¤—9ƒword/media/image2.pngPK- !lļ—AU™U™word/media/image3.pngPK- !5|ÚLdLd˜´word/media/image4.pngPK-!–ĩ­âņPword/theme/theme1.xmlPK-!%Q3īZ7 ;word/settings.xmlPK-!W%ŅRŠØÄ#customXml/item1.xmlPK-!ŋ ÂâU§$customXml/itemProps1.xmlPK-!eˆˇiį%word/numbering.xmlPK-!%ÍI%Æ Æ‚Î)word/styles.xmlPK-!‰a™ Á7word/webSettings.xmlPK-!äs‹Ū2Œ9word/fontTable.xmlPK-!ęYį8eŋÔ;docProps/core.xmlPK-!(ŦIŸzÎp>docProps/app.xmlPK-!t?9zÂ( AcustomXml/_rels/item1.xml.relsPK_&Cpcm-202502/doc/KSysGuard HOWTO.pdf000066400000000000000000011642311475730356400164100ustar00rootroot00000000000000%PDF-1.7 %ĩĩĩĩ 1 0 obj <>/Metadata 85 0 R/ViewerPreferences 86 0 R>> endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/XObject<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/MediaBox[ 0 0 595.32 841.92] /Contents 4 0 R/Group<>/Tabs/S/StructParents 0>> endobj 4 0 obj <> stream xœŊZmo9ūŽÄđĮŨJ¸~ŠĐJ ¨WEę]#Ní}@„&¨)äXĸ*˙ūf념f ”]E°ëąwžŸ{!o?’~˙íÕāũ°ĸ įÃų¯Ûa”áŸs–F´×T â§^å´Ûųû ™w;į×ŨÎÛ N„ĄL‘ë¯Ũöf„˨ŠXe¨täú;ôģüdÉmí™ Œ ŽŸŌh˛ŧ r—’˙uŲí|Î>æ=›-s•-đb’÷8ËĻyĪg%Ū‡Eėō/šūŖÛē?ģ2ē˛e&oĪLî$å/ĖLX—4Šė+Úë)ĶMĀ6>~DŋĪķžËVĶčs¸ßg‹lĪá 垉-Wy¯Š¤`Æ ¯VpoŗEĩg‹„ Ōž‰-?ž-€UÁŗģ=āu{ā•Ô”7ŋĪĶĻ}VâšúŖ°~؃Õļ‡ÕY`–&X?íÁęÚÃę=uæxŦ¤Ĩšž|ΞĘ=¸}‹Œ(ĸ/÷ĨšS*WŒĒ&`g‘ö0‰Ū ÕŅ}øÛĖ•ŪQįā¯#ŠƒqAĮ†­ķ*(€aT3ŸVņÁx 3ˆgĻ1<”6Tk{íŊĪüú&@¨uŲ‘YN€ŗ|eŦ§ÆDŸô9ˆ8ų‘9jcĐ#?#šž›•ūĮ¤ŧËšĀ´Ēŗå*‡ÜLËÉröˇšå›‚lžËŒû0DɈļ^iĢö@gŊ^!GBģVrŋŽäČ*§‰s‰S?!!˜i… ™–7›YŖÚ[˜†„ęSÄ.glMéû´/WäCûZĨĄ6Š´}uĐÕ$ŒÄų”Uë*§6TkČz.eęå ôYę“ņƒ 4^Ū´¯Õ:Ē“VļOZÜqTôûV l0Iéûģ— 1͍B‚UČՐÎcļõÁíåô(ŗņ Ų˙éžÄ܎ ōģ†üžIôKŪ~ž’Šjūûŧ&a•ûCõŊ&ô#ˇ‚ģ]l*įZ>§ãķÍM]ŽvĐyē>c\0îŒķã Žũ^DL†&ø.z˛Ī¸5~ÔÃ׀G‰ęĮA8˜]ēHmÔCWi ›†[a×ÃG˜‹đ ÖËN樕 ßöZ÷-ėĻ`c•ō­Rš1Ĩ ŽúŅŋžv cFĻŋņšB§Ā†SØ~j¯Wۑ‡;j e$åúבy<ącŨ+ûžcá˓-üZÕ',äúÎ&ØrØl”ĶyųŧÃ,ߝ€ƒ )Ëcč˙&‚ĀŖS>9C0Ž4)Ë#E¨B#Ŋƒfcča$Öu;°t d7(8¯ÅęāŦu¨$HŊļ:†FžÂæú,p8>ôč K}Ņē¯qĖäņūú%͑Į|;V‚äéđ—‘y`ˇ#Ã4RokŌ1S0#g!5đ0UaĘa^CŽf¯šßí¤Œķ sm~šũ*.„[įb>°Õ¸JtŽÃø\g v—v Š‹áķcĐAģ´ÆVĖaũu|mVđĻ\€ār1ééQ ÛjqûjD9*7ĪLšĪ5ˆ‚8ę<ú9¤×aO[iŦ‚ŊrDđ‰ ’čK ōĀ3UeŌ Ō[OÅÖXmüŌdičē#å(w‰0Č ÚŸä\eßņQ­bc)KÅ_CūĢÕgžÃJčÃĖŗh˙´OyØ^¤tž,Ī (-´|Ĩvx:›Ü?†×2ŪŪžØôM ^Mâ{ŌeÎĄe˛ĘMFÆ+”ĶĪ K•NAū§z'’%lV'Ëi8Ÿ 'Ėp_Ū-īoá|Ÿ?îsīøŒ eġ`Ķũ Ŗø[ŧŊÉÅjã]û›Ká“Fíeų­_)Š€*ÁJj,áĮāŽCƒ¯g$U{Pe/ĮMđīŋo§‚‘á‚ÔjŨzŗĩ‚ÕņėXā/ @3Ö-•ĐôEÁË[úøļž˙Į÷ŧ$ endstream endobj 5 0 obj <> endobj 6 0 obj <> endobj 7 0 obj <> endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> endobj 13 0 obj <> endobj 14 0 obj [ 15 0 R] endobj 15 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <> endobj 18 0 obj <> endobj 19 0 obj <> endobj 20 0 obj <> stream xœė]-ܲ>}ã?ŪŅzGŖŅj4­FŖŅj4­D#‘J$W‰DßkģØû€āĮũl?~×sž9Įggn÷û=žŨ.—Ëų|:öŋËÕīīōq‰xáĀ!ūĶqŠÆÕRĢ5=øp8œĪįÛívŋߓ$‰ã8: N(sģŏĮŖz<šX ĀÕh\MŒ_Ÿ9ŪnŦö˙ÆŨvķøŒ„-šÅņjĩ~Ν×+09˙ŗXĀG´Ŧ ZeE™‘bÎ"Í\ąˆģëŠ#íÄÎ-õÆuĖ ėõzŦĮˆũVšØ'šße‘Œ|Ģ\8Ÿ—eUŲĘ<§IB“;ÆRÄRÄĒŽwŒC7ö­´Š­L•Ŋ¤õ~Ë"g-Ф|@Œã;đüx?Šĸ÷ķĖÛčúã§ųJë&údæ‘<Õö8ÚųĖ6%shƒqSqA§Â­xcbä›õƒēFë5¨=‰o4žąČ˜‘ũ—"ß*ŽĢ3p;ŖŨaˇ;@ŧNe|mįœ+^R~ˇ‘6{‰ų~Y„6D›5´'įj´ˇs‹īëõŽ”atđ\qž.­ŗčy‹{"x܊fųəīˆ47pî į‘Žä|ĄFj`Klņ<ÉXļč´§iJn×âv%ĘF•­QŨĸh/¯F_§VÎî K˛īÆJ…0žšŨpŋ4KŲTÔ>įŖoąáĐö–6ū;æôļÎëgPôŅceä ˆÅLZ¸=úœ#ŅÛßuî×ĸ^ZZXDŌ‰‰G>‰°ąGŠöĢĐž¸œÉõR\.,^&×3…xa‘^ëX^/%€¯Į#p[åysOfw€OeIĨļ‘ēū‹˛Į ´Z"Ŋ=j>$voķw ų-žsœæärĪ—Ûæ™Ë•ˆmŒŠŠ‚ßh샎b9žķcGŠhąā!œoĮļ&;ö^)ĮÛÆzųū<˙„ˆmŪŲH’ä—s~>ų‹ķ‰ÅË OD¤"ŌvdĖW^ōōՒĸ†3u¸ņ=æCĸÕRSûG[íĩ{—۝Ręsk´,Ąpė<ûũ=VŽÂîH¸wS,"9<¯ĘS'ãZį ÁĪÄĻĩĩ@§írIĶ$ļŸO<"ķŲEEdąT0j~%ĶųўjN~Yۙˆúįų!BļĢĨęí!VĒÚž$Ī,;¸ėŨnˇ!mx‚‹ĀP Ë2hØåzk{›âũOKۘÔgJ9›Yũ/ēgNn“XŠg{'žüw3_kœüOΰģc:–˙Ĩ˙ŧNžĸ[ņ„n—JšØŗKŨ{j~qßBî92RōûJnäˇZŧ_/~ōë%ŲnîÛu¡TŲ2žå|+"qøE@uPŨ’'Āį(j•á1xUuÜđĘ7|w¸ß ´aŗøņQ{ÁŊđÖJO@{ā<0jĀhoxZ~ŪSčđKB[ũŖâüÁųČe~;‚ŋmásĘ0Ģ ÎŌ|sDįô”zÍ ÖG–ƒĢœcŦrÄŽž7{īy į]UĨ"‡Ũ¯žsAšäT)鏊‘/ÛtZ˙ü”ģo8įë¸b´ÜŦ˛í*ÛŦ8įWlÛŦ8Ÿ!ÖXåŧŒsaĖ_ņ ŋ[GV!¯9ûē‹m€–ÔjOŊŧ=¨=Đy.Õ^j>Ę>Čķ\Qû˛Đ¸Á:üœ<¤sĩO2#_”gjĪĻ ÷qxč\Āڌ´ŸĘķ¨{ŦŒ~đ‹™ū-Sį]zbĒũ˙ø¸kévK퇎,}ŽiĢũāŽ„‚ųĢD0ŗf>ōvŗBōĨΜķfuJ!j5ßÅųtŗÖ˜mÚwĒŊūwCT{vNxܚQ ŌūÜR{…ˇlfjO…ÂāLŽšŗ=ŸE6°ē}î(W{ŌÂ|':ņī°ķ(Îé˙pūeûeįô|–ųįۃĶzã¸JÜûIķc0û°’ÜŠ`Ņ丐cY”´ģ)“ÃÜ­‰yM9aę‰Ļ9(Šī+ÅīEŧĨ8´nX“ļ>ŗĒ.1+žä˜%Ŗ$>ŗŖę(¨'V†ŸēËIfVP'Wž;]í)ŋ΃j_ÜŽ÷h“ĀļŨ¤‘ܘ-gÛcm Mžk9XR|—mX!֟ˆÛ}ģ6Ŧ~¸ÚW]Ūžhy{¤=0üxÜœĪ›Ëeĩß3ڃŲ9×°ÁAĩ×î9×üoTŽTÜ r‰ą1ãįŲÎ1#›ĸųŠÂ#ķ˛f/ģ"(ķĀĘ3ōđ1’ņ“…û帜Ij‘ú[[ؑØ{ÁÛ¨=ÉŞؚ?'üX2~ĘŗâžáßĻų ŧŪámū_ī0Uc,‹Úķ†djŸĐ"áũse•Ô× laɖæķzRVž×_Ö5ˆ’Ŧ%ÂąķĒøčĀkæ*Ëė\ŸŖZy8Ûë­ĨųBíÁQįˇk˛Û&ŅcēÛĻŅ6ƒ¸Û@Ėv›|ˇÍ!îY,8.öÛķ.â™Ú3Îī"Č<3s}ˆ"ĖŲ{ų­-ÄŦŽ×Ė÷’°]D¸_hƒŸˇ/5oĪ/]7‡ÃzŋgÛųŧąūŸN@û%ˆã¸ĻŊíĘN÷öxĻ„€$¨ö‘ôö\Ĩ3uŽægD¤zŽRĖ j푴+VĪIԓãUssEŲÔĻ^Yã‚1$ŅŋĮ‚Ÿ˛ąĐō˙Fũx€†ōįbl6{‡ŊlųÜGX˙0ą­šÔx{UQÜs9ä…{Į’\Uõ˛õiÍs‚{y°Ŗo_$?Øē<áŖƒpĩ¯į‰…ÚcÉĘîíW›Åĸ¸ĮéaĮļũ.c1‚˜ĸ\Äü‡]ņČ"a˙­qĪvŦŋ5ä,îx|/÷;Ü5´ZĸxûĒËÛ+jŋZ­vģÕvģŒĸU-÷{ üŋß/‡%¨ũõzmŨÉq{{ǜq*tLđįĄˇÍŧ ”MŊ.ĒUģä-^)lų, ÖSæüTåĘŧ#ũ<+ŋåŗ†æí…Ú3bœ2Ōēf)Å,ö?åģ%q˙BWl‰œSRa¨šĢ€ZÕŪCRõųz7ũ)|#īéšÉI…į<km¯GZĸ*rÖ ˛$Đ~[ĐÆįˇīŖĸÚGËßü~ĪNĮô¸ĪŽŒŲqŸŸ9‹{ˆ…ˆ°‘ÁņsĀ#ÖÉęĪNl_)Ûã!;Ÿ  Đī;9ǎ˙]¯Ųļíö7Š . FҝyUo_čjn,÷ĸ¨Ō\E…Ú×3i%iîįÅۋüBá WÃé|ūŗũ|iĄ>­G ĪÍ­0ŸŽ$ļŒÛs>ãåŲØĄHęķu„?œžšĮJĨõüŌëø¨ÔøpbMį}ŧ}VP|đėtž:â1t<{Iäų˛ioá‰Äė(ƒSĢÛŅã™ĖŽ'ĸÍ'ûžF.ĐÖŗ‹âJ<‡Y nÍ ÚĄáŲžU3įkûmKë¨å_ęŗžFžíˇōSö{„†Ã…ō¤úwXUÛMÜũLNßoBõßR5O`ļž@ļéŋŽÕįÛ¸‹=Īíwqĩë9|ķŲ{‹×Į;đ—\][ũ{(¯gõĩū4uƈĻŋęÃú<ĸ8MÛ5%ŅÚŦmæķ–hÖ]ūjĖÔđ‡ŠęŽØũLŽęķm¸ëwU’ųÎs4&ļF“;õVÁ[ÃĪAÜÎÔhũŊ­ãw jß6ŠįøũlīX¨ąmî0įW&î`ž†ĩ}Ė÷ŅyˇæˇzŖÍvUíûtq÷39}ŋ71Õj°Ž…‰6E?Gi¯õˇąbSq{í÷ē ŠS#jÆõô¨Ī¤–ĩlLpũV×>‚zįŸX8pÃp5j īâ˜Síĩ2ŸĶÖy/ÍīSû>Íī?ÆÖ¯ˇöÍŨoĒ–öąÚÛ+–(œ´¯+b˛ēŽ9Mr"såĶD/ĪęOæŗÃI}W1Ī…ĄŸæ|á˜;zįglŠzåf~ ĢíėÖ|į<(9ÜÖųÂĒíô1ØÛ ŦkŽ%k&8~o;B‡-ÎÁļĻGjā{mv˛ˇá*Š™lÄDÆĖĀ<Ū3=G|ĢŪReŌ ʈházŒ /n˛¨™Â=cîčs&š^ĪJ>6įŦķPŽ>žü<üsjß{eŒ(eLėˇ2‰įÎįšn—-Ŋĩã–>'RÃ5ļwōünDû&Ø^s>/›hÛ2“&ĮÍyuĨ¯Áį×Îm×=ØķėzëŲQĸãĄQšī§īĢį¯KT‹U'6biā/æF;ąä|ŪŠöđÉ-žīĮhÜíytã'>|ҌŸp^ėĢã‰÷,ž,ņ¨â30Ye~‡ˇ‡1ģĐ~Õ5:–ų-ŒIÅCRĨâĒ’95+×ņs’ŖcŽ×ėˇq§A&å¸4Đß3›j¯yûĖĸöū:īŌü'}žcd čį:Šg§OķU<|ŧ¸ÆÎ3øYģ˜līŸÎÔŲ˙ƙzÚĪûx‰.ŧSŧ=S{â|Ū^ķöCŖķ¸ę'‹FMÛ|Ž4\9pĢĖ3ŠģūIÔڇĪρįũi˙#ÕūØ'GS{njĄXŽüáãČâÆŽõlVF~åÂßģq\—Édö˙ĶüôåĖ@ö{ûÂ~'gbÍ7bk.põísŠRã æ‚ÎyĄ—ŽīNãsēF‡ÚKS&ķ<įŨGëÆņpŧˇ˙TÍ˙×õßŅKûĮu‡LŪÚ>Îûčŋ7nŧŊ]í'ķö^\uõgwŸ?—ZŦ0đG0ųõÎĮĮ'ŸķõÂqäéíŗįŊŊ§õĸĢ<ÆŅÔiä\0vĩG–ö{Ĩ;yô˙L:OĮņã{ûÍ~Šra&cô8Æîūą§Ąķō >DíßáíįŅü ÆÎqäb‚sæÆĪ&ūéîįyãĶÜÛõ<“S>éí)_“öQmuE3šōũRŠâ˛´—qÍÔj4ĘôÔ3Ļą]ÉŌ‡ 6{•øegy3uo¯ũ–ļëž=íÃĐ"-ĨĪB it*ų ÛĀ7NÖMNÁ˙>kWûąŪZ‚­ĒŪ‘\{}¸đCŏ™đcæüŠqÕāʎ+üšԌbˆÚ7Ūž°ũ–6ŗĮåčĩ?Õhöséȟ H6`po˜ˇë‡ØÔ~˜ˇ‡”ĻŠt‰jÛF`įWŖO™_Ÿ8_Cš!qÅÕXį­öHûÎwW‹Ú×õ[ņ;Ôž;ų´ÃGĮĖ2ŽŗæÅŸō™fi§‹-ŽctœĩO9—ļdĒ}?ĩkIéĪääŖŧŊTûēUj GaW”G]–~ėšK|S˙”ÆYķWO\ņĢÚ!jŦi/ÔūŅ÷[ÚîqÄ­ũĮŠũ¸Ô=ú|ĘøœeŸüqõ”FŸũv§Ī<ŖRí›ŋ^uj~­öeãsŦŪ>§öe+î‰Ēæø”y ^,???Z„üív‹e G–gø35ü™>/›4!ëí¤ääˇĒũ÷{ûĄi¨î Å@ųŪ|W50˸ŌĐōŸ–Fzû˛ņ9Vo?Rí§ķö.< ē´čšŲ(9ĪGÍWgĩ ôgEŋ‹Emą“?BįŊĩ}V—`¨ˇz%W{ŽųĒÚį6ĩīõNÜÛ§BíŠŌĖšņ›“K'Í3ĖVķĨˇQķ%>ˇÛúö~ŋ‡î} mÛKŌėįí‰Sí›UšÚŗ>§ōūŋKĩ×G“$—>´ĘTCq50ßU†ųv5Õ^˗xš\âC}ĐąĢÕę‰ũmŗĢɧŸ_ oßÅĪ’{ûFí˖Ú?Ú3æ?įí_Šų>ú˙æ9Bõí%W{3_âs>?Ė˙đíe´ĨĄ}û",Ô>äí%įņ’ļQ{eqáí{ƑĒöoŋĸõŌ|ûčžŗ|åĀzųĒöíM>Ē}ŲΗÔēT~ˇŗmpļ߁K{ú—ÉæíŊ=oŊ™Ūžž¤ũo¯`úĖ\0{ŌD[ĒũzŊÎķ\+ŪūzŊĸ˜ė÷ûWļĶ‘žéķķö íĨÚĢīú.oī“\z5ŲŧāÖOÅÃŗ(ŧ} œ÷žŨnË2ė4ívŋ‹|ĮņTmč?FūĀ4ÎÛ3ÂWēÚĢŪž _ęíML|Wúˆyá…ÉŋOĖ>|3í퉗ÚÅĶ›ĨŦĮÄTų+mHSéį—ęđ˒ÅÛģąæíų“h-ĩ—ˇĨzûŽÄŧ}*ŧŊę÷>ĢŅ?šę|WzĻ=Ž~øllzûîäŠöœöЎīSôKŧ}H&UšÚ÷ižôö´šTíŅۗ6õøĶķ˸2Īė÷ûc­öi:Bí‘ųšÚ´Vû*xû/MŸāģæOãŧŊUí t8ĩÚ?Æ{û2Ōųæ‹Ąûúëqœˇ—œWÕū!Ԟ4j@9cĮŽŠŨÛĶ€ŸÅôÚ𑸲{{'VŊ=n-ĩ¯NííŖŪ>ũãŪū%ķÂ?Žáūqœˇ—jOÛŪužVû‡ęí쯔ĄöŸĸ ˙]ŦyûÍGo/ĨŪP{ä<ķ9ãŧŊĒW<ŽøĸXā1üÕžĒj—´—Ūž”\íKF~ŽöÁÛü‰¸ėíkĩ/Ūžö9åSŪūC4!āŋŠGxûĒŌMŽĒö¤ĨöÁÛü™x¤ˇGĩįW´ÚKÎsĩŪ>āÅãŧ}ÍyCíiÉOŲßp…ˇWžëÂTS{Ĩ…2Bķ’$š‡’_ļūPąQmĩīæ§ĻöĨMíķņQ´aj/GĨ‘ķEQBōKyž#ķ­ŒBk‘ôönĩg‚Ŋ}ī8ÂÔ­öøGäJŧ‡"¤z°…đG}Ôž—Ÿ öPuŪĸöeŖöÕoŸĻõëØ„‘8ŌĐœæXUņX ģ“ķhÔžÔԞŨĪgö†NęíÁŪŧˇCúŌĖ™ĘÛ?8í+ŠÚs‡Ã7ö>.ÕÛĢcJĮ¤ĪÛڇ4.y{û.~RÅÛKŸķPÔžTÔū1ˇ×hŋX,:Ž´ûSH???ÖoÉõ$—ŋŋûũ^ĩU×"āéx8”â-ĸPßēĨĻëõÚۆ^“<ÕŪßÛģÔ^2_xûžqD=ŧŊF{+o=?…´°Pŋ…:°ŨnņŋЀårĮ1JÄ=އƒü–Ä2m6›Ū6„ôšTöˇp:ŧŊŖˇ¯æ?Új_rÎcū3Ūžz¨=° ظä kPׁpŖJGQ$ÕÛĨöZίČÁՖŦŊ ß:NDŧQ÷Áä|>ĩ˙„j¯r‰>ííq{(j/8_ĢũLŪ^ōø ƒ/ųWĢ•ö)¤ãņá?ëo-zÔËīĸņН­gíUø4‘ĖÁ.ĩ˙4ĨˇīV{åĪ8ooŽÍĩo2åXP”vĩZV‡[Į…Zgãí—KPx9;tH7~ Ž hįá°īūJH¯LǎWy5ÎÛW‡ÚWŠÚ?f÷öĒnKŦuí_™iöO‡>Ã(×°Öo%I‚S –<Īģk é•ÉåíËįŧ}ÕV{ŧÉSŊĐÛËĖĢÚ/—fWxz{™ĀÃÜn7ëGō[ëÕ :P^ĩ˙4­ˇīS{Æ|õá„~oĪ×É­jö‹¸ÃÛËL‰×ëĩŧÆoŒ–ˇ\|ŧŊš 6¸(žßīhãö—Ë&(Ķ[[H¯LĩˇW¸$pŲ^9Á×ÛWVĩhjīõp$~÷ģRŊŊŠ=å¯đˆÄ)hõī/[ûŦˆų-ŗNk‚°ˇAá}ûãQŊoräĩCom!Ŋ,ŠjO5ĩĮWļų%Mí1)jœ¯*éíUīäšÚcRg™đWڐÆĨÚÛ+\r¨}?ƒÚ?Tĩį•wx{ĪŅ$h/ÆŖhâ@ûÆ%Ļöm.QĄöÕj¯y{žúeŊž=TÉ;9ǃ20>Ũ8ewĨnŽ{)!…äJœNDį’Â|`]­öüÄ;9Î50ĢGV”iASŠ÷ĪL¨x´Xš×55^T>øŸ_C Ņ+ūëÔ^.ácÉ> ÔhŸĶæĖåŨUiÎ9Īcũ6“žqD ĩ)¤š“Žö}žԞrÚ#ķsÚR{ÉyĻöåojO=ū^pĀĪã’˙Š|œÚcŦĩOį)2Ÿ ņöōČuōģž8āqÕž(x{ZvĒ}QûŽöžŪP?š€­R> 8ā9pÅ˙Dũ’ŠöώGÍ'Cŧ}Ŗö~^+ā€ŸÄĀ6\‹Ã§|ËÛSˇˇĪ‡y{ų×´BzM*ŸS{ĶÛ'ÃŊ=č}{?$ā€įè°DĒũķŪ^øœAŪž´Õūŧ_Ā—Ĩ!ļÎ$Õ^2˙aUû|˜ˇoøīđ_Į*ņzĘĢŪ>ˇyû¤ĨöžŪž°Į<ˆŌ øæš6UÕŪåísĄö”{{áŪ)Ĩ.Ė"Ę( 1Äšc-öũüŦÕžėU{Úöö꘲cĪßvđD3ˆgų.o_5jŸoß;ŽL=ĘđTxߤÚgvĩ§Šˇ¯ÚŪū™ąpĀīĨö™ĸöšˇ—›ŋˇ8āĮ}j_JÚ+wr^1x>lzûĒííUĩæíøSqŖöÄĸö5įë;9ÁÛüG°æíķāíū0Ē}æđö*íGy{ÂöQŋšĮ€ž3šN˛ÔĨö‚ö÷ŒŒđöŧIa…„fOü!4B†hž—ˇĪ{{>ëÅppÉ}\É$ā€'ĮH3å7&ũüŦ՞ØŊ=ęü}¸ˇĮŸ™°‘H•Ûxüā‹F˛Ÿ™ õö¤ž{oUûģĒöDúv'Æ7@ąĄČ›U6)ā€įĀLcëUŅ<øŠzûĖđöwáíīmoßëõíqÍ1*C qĻČVæVŪfŌ5ĩ7Ŋũ]¨=iŧ}×8☴֡ˇŒÍ€ž?ęõí‰?™ÚĶĒq8-Ū^ÜɉĒ}ų:âŋŅZŒVûŒĶŪâíÁ4ų{{ĸŠũiBĀ ĩ'¤ŸŸĘœĖęíŗāíCü‚øŒˇĪlwr탎ˇ`õ}ˆŋŋŋģŨ˙B7ß~a_ãËCįÛ×wáqŪ>sxûģĸöÁÛkØ.1õũo7›—í1DGz{Ō0ŋ˛ŪÉ ŪŪ†Uí-y˙휚ŸqŖ C?GŅ>ŠĸHLŦ$€9bŗŲČ9‚‘(j—,ą$ä¨īdĮ|ܯ_ûM’dŊ^Aæzĩ‚¯ŋŊ¯fƃŊŊSíĢæN39ÁÛ÷h/¨ũč]Öü˙š\.ˆ‡Ãų|°üîņČúŠí÷{YōzŊĸ§˛äõz1÷(ąõ[0 €ų€“ä.ë˙Ģq¤ˇ'4xûá¸ũŽķ_`—”hȑīļ€0"ž ŋÛΤŧ䒈%†DI6/dY&ąlƒÄâ[Ĩō-öĻÅĶéT¯ÁûĻūy%áíSˇÚáŪžtāĐÛkFBƒ +ßĨ×ãčįG“š+Ŗ¯õ-t üĢÕLČūÛûjÖhķö]üė÷öÅĢZĸyûލy{J›Qųˇ°t×fä$âZíy×jĖŋ[Ģ}Q9ËZíq ¨u˛Ŋtb¨“}ËŅNp>+1›|HŋMMoß[ŪžØÔŪâíģÆĮ…đöīׁYc—Ú/ĩī ~DÕÛÃĄĮĐđcÉãáVŸ2Cž€e2÷WŠyžiųęˇâ[ûŨŽ5āl@&ŒĻˇ÷Õŧj_Jo_xđ“0ÚõöãÔū/ÆF{¨Ē=a/=įwrļ["^xÁîĪÜnpášŨnp.(ņNÎn3ŋŨnæ^ĀēĀGė‘’ßÚߊãÛzĩÂLfrŪŨWķÆQjßīí™Ú—Īy{°Šų¸ø ö|5ļŨÉéņöDU{ŌxûŌáí‰2jŦ˜ü3j˙Lė˜)B…Úsöõđ“jj?šˇ˙øXŦŪÉų„ö|5ŽĻ÷öČ|ŨÛwŠ ö!ž8Ūž[ķ5ĩĪ‚ˇø ņ8oŸoâWĮįŧ}§Úođ‡âj¸ˇwŠ}÷_iƒˇņƒâHo_õĒ}ŧ}ŸŠŸôöédŪ> jâëâŪ>u¨}ŦyûēfâÄEÛÛSelđ ¸’Ūž/ŒÖÃOÍÛŨÛß§ģoO•ąpĀãZ퓉Ŋ}ŽzûŽqÄCaņö  ˙U\™wr:5ŋÛÛß§đöôs4!ā?ŠËĄŪ~/ŧ=™Ūۗ ˙ø9oŸzx{å^=’ļˇWÛbw„ŗ–$IĮ÷Ú ē fí7Ąö‰Tûî¨z{—Ú÷탡Ÿœķyž“ŒŨŌ0ßčÃjoŸYŧ}G$6oboĖ2če2ōM6=ĢĄsp ŗ÷JÃÛwGéíĶāíߍáœB§Ŋ›_oNЧãQžJMĐ9 ųö>|ÂÛ§ĻˇĪ‚ˇ] R_˛å}–¸ū‰UpŲŗ÷ĘIŊ}ŧũ ąUâūŠ´Ũn—Ë_HËå2Š"ŗĀ¤ŪžâíÕŅdĮLí“āíƒÚŗTÄU#pÕ¸ŌũÂÖĶé´åé~ŋ#8ŸOZ™>ĩ×ŧŊ÷öŅîíYë}ą¸ä+cŗˇËĩËüŗøOĒ=P=x>ŸĖÖbqPvđáp€|ĩXÍŗMoß;ŧ}ŲōöڝœŽ1Õáí[ë)ņUO73¯ŒũEņOĒŊ–Ā˜™ĀX`8°_p‰ („ĖWKö¨}"Ŋ}æ7ŪŪĻövo?T핱Y¯&Æ)”k62­)āĢUo žØēyŧ$`/8Ō)_Ũđũķ…ŦÖëõívŒÛífnMž˙Iĩ— øpŊ^a~ˇ~šņԛŲööMVƒÕ^ņöĻڋÕBĻōöÚ{ø˛íõĘØæjØĀa¸6‡Oo×+ ˆ€!F’˛+(×Íûęø‡Õ~Á_Šŗ´&Ün7-rL“ķíŪæ; ˇŋ`õEà ĀPøâĢĩƒDą!R~SōĄüjĩ‚ĪŲbڟĄÕĪãŋ­öLžn7P9ķ#  Žķ ́Ėŧ2­ûŅîdžÔÛ[ūJ;ˇĮ‹ųŠšvÁ×{¤\ØŲXāžrŠ"ĮOĮüwŗ^§J=ß˙°ÚËdõö~W)Įë_˜ÁņîŊv3grooUûXQ{ÍÛeėčØĻö˜ĐÛËv_˜e Cp,CŒ€ŌpŲ‹Ž"ģ^¯ĩ:a\Šōjū×á?Šö nR;ŧ=$˜úņŽ=[ōÛ÷ÚõėCx{KjßÅĪļˇ÷Sû~īDZŪžnú xpĸ7› ˛Ũļ6=ŸNĐp5Üķ>s úķβfŪž^āŊŊ¯¯ÃRíAŊņNœ#8­ŨO_G¸ cˇė7ĀfEí[}8­ˇmŪž{1kŪV{ulÖīøPÆ)”dˇ_øˇx˙ü@dGĮ?…šF Î*†FqŊ^C…ëÕ ˜åaé:đ=øOĒũ œŅAƒčz8ÁڇRíĄ@/?IËÛWVĩ§ņöuÛîöΙ$šģnøpĩˇôá‡{û€;p }wÂgí}8ˇˇWG“ÕۋVŅæÍb–(˨å˙5ŒāŨäúÜTōŋõXû°ÔŸĀėâ'„{û€}pĨ¤Wš€‘ķŽ~›ßÛ÷ŒŖZíĩ_W‰ļđX÷ö}š_{û"xû€ŋĪãíã)ŧ}ĀĪ„Įzû~ĩŪ>āÅãŊ}ŧ}Āߊƒˇø_ÄÁÛüīá—yûŽŧ}Ā/ÆĻˇīNÁÛü0Ē}2§ˇ§ö!…4_Žöŗxû$xû€_ˆį÷öęh˛cŽöIPû^–jīăŊ=QƎ#ķ×UuÛîÁEXßŪ‘ŒUž[ũf÷öî8ÖÛwŠāíĮĨ°ž}GRס7“PûD¨}æsĩîí;#ąx{îÄa}ûŽTņߘ€œZû°´ū•Öƒˇ˙œÖˇø­ooĻāíŋŠ/}ÖˇˇõađöߛÂoi=ס7Sđöߋ˙ĒÚ!/— hxw1īõí-}øųŪ, dčÔ{ąžŊ+áē:˙HúĢjį8 žĨŖĖ õíÍôáŪ‡­övšāč¸ŨnģŨŽCŲ*jļü?‰˙ĒÚcęPûjĐúöļ>ėí÷¯ņöĩ%ïh“ĢVyŊž=_ 3ųš™ŦĖO-û- sĖž¸^"Č…¯ˆŋūà  W”™0‡ŽV+ļîôjãëÄåÔÔöŧ˙UĩĮÔ­öCÖˇˇôáäŪ>n{{u4u`éíëV‰Vō724÷ T$Öˇ'āčp LJĩ¯×ŌicøôČov{ œ"㒘X @{Ž×+.žLųŗ( ĀpYjŪ‹˙Yĩ ZßŪևVo߁{{u490SûDxûúŌ›āy…c'¤Æj>ålğư÷˜ā ƄpoīV{˜ HŊÎ`Ōāæ×%ųņJ ü‡‡Ũ6Úđ^üĪĒ}5l}{KÚÕŪĮyûî1Eo¯kÚRŦæmŽYäj)FzNå§"I,įļÎ9[˙¸Z-ÁØ$É]mÃ{ņŋŦöūëÛ[ûPķöŊš?ÜÛ÷Œ#ĸßÉiM¸€Ŋđ7õ˜cVŦoO”õí=güį Vu[Î.l#ä~c´C ķˆ˙YĩĮäģžŊ­kĩO„Ú÷iū‹Ŋ=\ęÂŨnW7žÎ?~$Öˇ'r}{Ēč6ˆķõz/7įļΰGl¤|+œÖΡāYí1y­ooëÃ÷öä:˙ėž=ŋZ—+ëÛįX^ę6ģPåRpcĶÛģpãí UÉ;9wöbãNNđöoJ•×úö¯ķöņDŪ>āh˙đXßŪڇcŧ}ų:op´īNüŲãbRo_yĒ}ī8Âäōöƒ°ēúũ3õ|&.• é!סˇõáHoīĨöĶxû€ģ0˙oĨ¤ĮŦ-˙ ˜SÉŲoŖŧ=ã|ŧ}Ā_‹{ûķö‰KíSā< Ū>āĮ#Ŋ}QyĒ}ī8Â4‰ˇ8`O<ÆÛw¨=ržk~đö,éíŊÕ>xû€?ģoŧ}Ā_ĮyûdfoRHŗĻįŧ}ÕéíËāíūLüiŪ>¤^žķöŌ>xû€ŋ ņö4xûž;=éíuĩŪ>āoĀãŧŊŋÚo?kJŗbwÉV§ŗ_ež¯ I^îŽl*´vŨoj/}NđöīÂĢcvMáNūûŒiŌĸoéƒIuŦoŨrMˆ“y{Tûŧä&§G탡Ÿ/­OųíƒißģâĮķŠc}{č–[ZrÚ[ԈįíAíīšCíSf냎 ^qڃŧĖÍŽq Č8÷.:ÖˇgjŸ2e˜ĘÛ´ÚKæĪāíąm4ān į”™œŠY|¸^/ZĮņjĩZ¯×ę*”€×kČ^ÉU(įVûîõíšÚĶļÚ7}D{ ÷öNĩŸĘۋ¸øųŅszŽ+šßW•Ģâ8>UWי$zļm5ÉáËËo\ZãK˛— °Hrˆ2yø˜ŲÛ÷Žojŋ’jߎ#ŊŊˇÚķöTM Ŗ”qúĶzqC—šßWO/VÖj˜FĪ=Û6̎wiõvŗŽ!$pŗ!WØæ™ŗ(Äīb'÷ŸYvģt D՜ƒ’/Årëf*õíÛŪ^īÃQŪžšŅÛ÷éžÔpčÕ8ž¯ÛđŸBU› 4æ×ŋŒ0˙B&ü—/kãwųâ¸?Xžņ…×āôąęšUĪĄ<_!_ŦŠĪۃõ`V÷Jŗę>/Ã=š¯(^/w€k˜›mSą{–™ÕÛģ´\ōĀī/[˛ČÜÎü}Ū2‘Ĩ%9OĮ#TĮĖķų|9ŸĄ8ÆãQ_ÖSīúö玎Ēũ§z{S÷”•ũĐ{đ]č%æXbŋÛA7B&ވ+3ŲëØ˛~×5[ĩ^Ÿ @8`˜ĀˇøÔy0õA]3j†ŪĀåF1“Z—‡¯_/Pœ2š¯ Ë$h(U`ŸļŠĮŽá™ŧ=&—Úkdƕ*ĩõ*ņŋZ 0ꥴ…} åxY­–Ŧ´Û2fę]ßū-Ū>žßÛC¯ĘõíÅúö ÐAšęZŧŊēúą20áåķÔĩ’k[Õū§ĩBžéíëu˜ųąÕ゗„) œw6B ČBÛ:âLŪ“ËÛj˙ëĘÔæ h0(PĻ0ŠÉbE/œ1ëąĶ{-\yŦo?‡ˇ÷Wû'ŊũÂĐŊEkub9j,dšĨ™ĐÉ`qųVUˇ›:?rE5ū ]dũގE4Ū^ÖŖŽĪ&÷…#ŽWŽRŪÛļķöÛ­‡ˇßvÔ3ģvPŗ€Ú÷Žo?ŗˇ¯fõöШ“˜˜Ŋ[ĮP{0ÛX†fĄöB3á+āsūĒzSˇņ[Døpk´ę°UíųtC´Ô}įžCôl[G|‹ˇŋ+7mzîä´į všĻîĀ%ہŊčUøģ3jĪŧũå‚ŪŪ\˛žŠ°s}û¯ööāxŲœČ™œĖ_šVë*žgJ}w˜(™đ‘ÔLv1Å_Å+ß×Vú_xûÅŋ úA\&8ŊŊĖ•˜W•"ÆĒ(?"^•>GđšxqŊ^:ÚöŪ“¸oŋŌîÛ¯V]÷íĄC ŖüNŽœø=™=fâ`Á§ī×}'G-æZß~o/ÕžÔÕ~´ˇo*$Â9Ëžîúųˆo$‚ĐÕĐc0jgUå9üˇž“ķSŋ °” ,šÃnŨđfāí—{…ÄÍlōp–ųã„w]āXđŽäĀ ßâĶĐEÆÕ6Yë4ÛCæ÷öΧ<“Sš×ˇ—ŪŪÚ{@ûĒõ^Ú~o¯¨}9™ˇį´ƒ˛~§Ę{ÖūŲžÉy¸×ˇoy{ŗ÷hM{âÁOÅÛST{Fûjo&ĄâG•¤sî)ķˇņîšīoD>xü!ĪKĖæŦėĢz”|ĶōŗĸÚ]‹ũ5ˇö!P ø–Iĩčí!Nâí3›ÚkwEZwH‚ÚC§åLđų ųˇŦ Qû[ą>įYî¸SÁÕÄ֓ŸŌÛKæOâíÁi—Ríq”܋ųûkąV~L0n‡[‘å…ŗų=L`ŨXoßŖöŊã4Žh…Ø“Cœ5rĩg´WIØÁÕÆÛKĩÚÛCĖ™Ú Î+û 1ÄY"ŋ‡Éū\2ÆÛ—“y{P{qĶž4Ŗ2ā€gÁœöe.Ôž—Ÿ=Ū>éíķ<—¤ÃØņ_‹|šo`Ũo›ŽöéoĪh_?™ ļ-ā€gÁ”˙Ž˙8(SWkoŸŅ{‡ˇj?ĀÛãķÔ˛Į€žķÄh˙VoĪ՞ąūí:đŋ€)>ĄjßËOáíŠéíKCíũŊ=čŊT{Ī–đh˘Ī ėí…ĪQÕ9?ÂÛ#íÕV…âŧ‘6jO”|+žÉۋA'’ú߀žĶúÅooŋ“#žOäíņˇ!äí âŋšÉ!JNžÃÛ9čēËđ”˜˙wZojO}Ŋ=Įí/pĀ“aŏũ{ËĪâí‰?HūŖxßēŊŊäüPopŸŒįōöüÁØĮÛ×j?ĖÛđįânoK FûqŪ>ā€?Īæí}ąˆÆ*đĪ 2ōgrķ&æ,æ2eMĖdd̃Ö1k"OiSĶTFyJü'ā‹ūŠŖč=Ĩ?e?׊u.ZgĒ9ƒxN-ąÍø§(tļp*I.Šėę夯ˇoÔ>âíëXīĨŽõ/øķi2ōĮ¤›Č™RōšØN¸üŽĩÄ\aĢŦ¨1¤Š“ŪÃæ‰Ā¤Ÿ/<ÚyUΞƊ&)Ėá\ŌYŨÅÉAŪ> Ū>ā?ˆ;ŧŊŠöÁÛü7p§ˇ¯$įĨÚķo}ʘ 8āqØĮÛc ÷íū3¸×Û3ÎođßÂrģˇOZjŧ}Ā÷z{ésžÚÛË;ĀęŨāĪĮ¤ī¸ÚĮXøÔų÷0ũ(ooY'įx{ėsÕčoIĐrõĖē—q ĒĘwˇ÷=ŠÕKCŊŊõ¯´†Úŋ]ˇ`.Ø3æ_¯ž%Bb'´>köã•/Ÿz{kßÚK9úŧŊˇ'_æíq‰ĒGķ×Cõīš_ƒü€Í 0ÆņæyĻæG´ųõ¸éĨŪÛ§ü…bÕ÷§%7ą/_S]ęü€DiļÛĨëu˛\B„-ßīKBæ9‚W$ĩ—ĻņöIqû6o_đwã"Äs;åh 5^Ž1_MúŠzÆáĒã|E‘ˆÃô¯“Ün÷ŸŸtģ͏GršĀVœNéfs_,ŠÛí-Į8ö^Ú?éí‰x7nÕJcæPč°åæp¸Ü×ۃĘügę‚šŽšŪÁĮߎ×Vûž:‘ķÅņTOÖëxą`‚ŋŨBNļßŗæŋę§ņ9ÃßKëôö@øoôö…x7n%Ô`\„ēVÛÃé–@íĪņvwę(Ū2ŠĸßßßÅb\ˇüßĻ˜RŸ^¯WĪ6T]:ÖRûŪÚJJ⟟lŋËvŅmąČĪg06|ŌīoēŨíĶ(‚€nĮU&žŊŽtŋßņZØ9ĸÚoũŊ4ęŊ´ĖÛˇfÕ38đåƒĒO™O8į—û-ͯI~ŧŪ7ģŖĢž‚ŋE_ŗ ôēņw C&–’`ųĸȡ› ´­ˇ=wéX!ÔŪķ“Ũ.Y-Ūˇ_ÁmB˛õ:‹"°úÉv´ŋƒøī÷Žzøa˛×âÂqī÷;īcŒeŋõ”īž ÜãíQíyüvoßÁē¯ĸ{ģŋ8ķ‘ķûĶí|K@í—xąŪeüîĩĐŊËåĸæœĪgČTU ØģŲl@-=[Õ¯cÅ0ĩŋ¯V`l fįŗä|žßåÛmēY_ūûīöû˜ĪjåĒáp8œņ쯧ėÃ› žÃĮ5ä/?ō­â÷û]”§üÍ AXžMxģŨâƒ÷ÕPĩŸČÛ#į˙‚ˇWûĮ†×ŅÜ۟o éĀüÕv/ĮK|8Ãvûåœī¨Î AmųĐČgįø@đnI_{5ķķö~uŪ~~@ŌQę9įWE´-@í×ĢëĪÅÆöĪõwÅ\õ°Ã„C°ÕcüĮâøļ^¯+ÁÛķéDŲ čoĢÕËCΉgKaa=  āä~‡A!ûÍ̝†z{ųōoj˙ŧ}éÂGÆķCt8īN7ā˙:ځŖÂÃ(āœRūîŎzœ*Z>œ5ÄpöáT™E{ĐÛo3ÕžĢÎëĪĪåįŋÛĪ$ËŌÕ*]üä‹ETÎߎ0âŸ˙Ž˙oījĄV…(‘H¤‰Fę‹F#•H4ZD"•H$R‰ÄUŖ‘čģÛ`t€~ū;į~÷đ`˛qwš›Ibu;ĸ~…ŌĩÄvœÛ?ßī”3QĻŧĻ“Ų~fķ €ĒSÛĘ]›@ķļ…<_‰ņ)­‰ˇČ- †ŸŽ `Åæŧ|˙$‚’õ?āŌíÕrøÆũ LįtĢ×Ô=ņGŗŊÚ>ã|lp6 ‰â| ųņcÅGÆgrX”eI^Ėäˆ+îĘũ !ĩuWmâ|&|lčöeâØu3×.,‹¸öÎļ1ļEÂŽv;˛\Į.°JŖÃ"yEĢf–ōW“Q¤q>“ÃuģXˆˇG!ΊéN+šĖöØ_fû O€ØHĘ2âSIƒvO87ÍÛ{ļ?ŲÃ4_‘ŋĄųŧ(¯:ölŪž’û°+ŽėĩˆË=Y攏uŲ~öy’$Ų ˇˇíŌą s$üzáÔļUBķč|\9R:ēí`Ū^T [Đ)ĄÜöÛlĪwÚFõŅLr.ôĀ7Нp×ČøĐøŧŨ´üŲūėŨUMÚ>™Ā”˙ÁŲžmUKcKœdĨđų+Ŋ;ŋ2ÛĪ*ÂŪÁķY¡+×Ļ ΏŽ°uĄųÖ÷6Ūđgö’íé§eû']Õ øË‘C$ęrĄž|Cģ/Uį/6iė<RĮc¨{J§|ËĨ´Ō=˛Ŋęö/÷đ™œō7Ą‹īGÚ GũābūA‡7—/ú,jAtī׿O`ŠŲreķ:6ˆˇšį˙ĮdäžÁcĖ›Ĩé;œķķyį UĢíšŲūø}ŲžvI¸æ1 ÷ËĨŸÃqâ#>ßËđŸW}‡s~>īĩŌ=ŗ=ķüúŖ˛ũĀßĸŪÆ¯ŋĮŊūœ_ĀGÛ䗲Ŋá†ëø7g{à ˙Ílo¸á—ø/d{à 7ŲŪpÃĮŗŊxöŌd{ÃŋŒ›loøōŠlOMļ7üûø)Û׍Éö†˙ĸ­’íMļ§&ÛūMœģũąįö&ÛūínOĩnßf{áų}œdP><ß ÁWáú|{´épËČ(†Ņ6Iséöb•Ųžéģ}AhEŊŋ÷tą{¤3øÍX?‘ŋģë~ģ~ÆuÅ. _œ?Ē\EB™p¤ÛcIĶŦ¨ö2ÛĢ9_}VGå§y~åí?C$§7›Ģ˜“}Éũ1ŋ‰ëđ|˙ĖxŊ.!}c~-jËL%ęTĄ`ÚãÆNŧCųŽpÖÂ{u6RÕķʕĐcšåžī¯×kĪ[ĸ ?ë”ŋ¯ĢãEũ‘´}AĮķ‹üUØ?ŸÉÚuJxW?wŠ_‹Š|y÷kT}É9'°¸ üôFå7aūH8ĢՊũžƒįĨYFęŊęķķü'¸ũ#<˙Ü˙g{ū;øų§yū”æ™’ŸŗÖûc–å0ų ļÛ Đq](žĪŌÎß<¤ŽŋéųCũΧĒĒuÎ9‚IQužŌAÃeåįgØn¯m˜ĪCķ˙]ŖÍfĮ10 Cä|į|Œp‘ûīŒĮQūHlĖįâœvøn~6ôĶo˙gŦČķËĨ/|jßív=[yė÷É=6ąiĐāsĐĶđķ}ZdūŦ ?ĄmUķ˙dĮVķ endstream endobj 21 0 obj <> stream xœė]-˜ę:]yåĘĩH$‹D"ąH$‹D"ąHdemeeemee%o’iŌÉoĶRvŲ}™¯ßÜsŗišĻÉádԟ$IŽ×ëår9ûũ~6ŸĪæ‹čŖūübŽ~~Iũü ęi^¯ķ,āŧfąĩMŒ:H „v:Îįķršüüüĸ|<0ÛõvkšGķxD¯úFÁM‹ë§đCâg|ˆÕãÖOâwņ[|íũë@īŽÃÂí×bÄu0k ~­§ĮuāíjõxÛŽYM.×'Ŋ9¨8 ¸Ûívĸ;ŋžf×ë Zž¨ęŧl˛Ōãë`,|áÂU—"pfĮ•‰-žđâÖWЧ&ÎeŠĀ9û¯†ŸņâX†/Á××ã_qĢė>70÷Iŋ¯$n7Šs žÛ<Û2Žŗõ‰'zzŲúŦē1_ļ>mũų‚¤z˛—uãĮ*Š…O(ΊĮ­L9h}Ąā°žÁĮŒšĒ!–WķÖ×^ėō!ųģŖ4ôč¤ uėÖĢē€sKĒä^§ā¤aūžá¸IîŋO:Üp,÷Årj^~ŨĨ=E\õ)köģvŊŨŋžž€âØÔ•+: ēĒnܤdā›ĮIt•At#Ŋ“úÂč΋­™ŪFzŨ•.tÃ[‰.‡]Ņšƒô*+öPœšĩä“û(Ž“‰ß5Ŧ“˜ —d_“čŠ!:ŧzĘŨę‘čRõ™4˜—&&”Nu4ąđ:Ũ1ÂÉTÂÉ éšpaO§û6fÎB%4Nt‚îT oŗZÕyQŪīŒsZĢ…¯…o„ozš/+‡oĸ|zÄ{įP¨ÖæĒ@q@wģŨ” WË9Õ]nõ⚤ŗ­¨ėx’- ÁĪlŪC§Ú4͉’´–ƒjļÎp7Õ CRÚ´Sh(aք k…ĮĒģĨ×ú–č*‹Zŗ¨ģ§ô<ęÎBq¯‘›EŨŠJ/u+=qåčkr• Ukî<…%ŋFzŠēŲ2ŽËįÜoč[:ēģˆîūáĀĢtGʗGDŽc5iē:ž/›ÍfÎct”¨;Lt*ã3b {}Ųų܋ĮlœŦT\ixV„ũtgRŸŽKIÉí?€ú:"dÛá@õčĄDƒî*7Ņ•DŅQm™´{ĀŧuÁ—ujŽ÷ę:]Å Bk ęĢ֗÷ûŌ‹Ŋ>ˇãÚĀšuaÃô;ŠHIē}ĢŸwéęĀøĒH3÷zQ4ŠOÆú´8Ū Ô…ôn†ĸsy]Ũrķhŧ~ugNc•8žŽîēiŦ5^WP]7DŨųãu枆ĸkŠÕÎŒO¸Žã\wŊ–× cŧëĨžē^jáká›Ö_€Ö䞧쪖ydūš”V‘Ŗ”Â#×q]×H- ԇKēŦß5Ōˆ“îTĸķ{“}˜!ĀšĐ+>Æ}4X¤gxæ6ĸSk*äÎįĄÄŌĀĨ [Ļų=Ū­0Šb´ĐļlĄĮ֞Pw™BqcԝÛÕ]ŅG}ŨT7$Žg*:MŨ]§Æë,¤¤îŒü.u''­BÅŠ×ZÎ`nY/UšæįSq9įS)|)|u9WîךãšãË᠑ÅđW™÷_rŒ%—öXp\8:ÔaŊ˜sũVK- ‡7ĸpŊGņœõœ/ˇĶåz:_ušž9Æ —ë2s"jˇ’úšQžļāJÁ…OęuĘĩĢM ú”¤ÅÛUąáÎÉžVz ņšÛTĨUé¤7]ėÎâUęĶԝ“ô•^ā*­[ŨŨmąģÄTtšŠî0ÖÚ ×iņē‘ąģĐüMA]&bË4āŗā–ÍbQ& pŖ;ØNÜņ ˜ ='.Ũkt×Ū:ŌŨķTÂŗr°ØK{Üđ¸P¨ ÎU%Ũ=¨Ž3~ĩĮĒĒⷕõdēS)#(7ŅŠ$Vi¸vbÕ~Â×,Ôc)‰‹â`ÚbŨ;UeßtۘÚįčíJŌĸUŨk<…č*EŨiĒĖŽîB•ž™_Ņu™â„rģ’¨Ũĩ/ŽįQwÖ8ۘUZę‰ęĶâu’î2gŧ΂ęÎÔuĘĘl7u햴ôģ pšéēųx&;ķĶ1G:§cq:”§#ÛÎā[\q\ū˛gt×cŠnčō}e9ŧpv ô°ÁŅĄPļØÚqŖąY§ëô™86ČąÛí6ÄÖܖËåjĩD—exyŠĸkŠėQĻį˙ū}||üÛ$yŨ@‹”ÔWÁXõe0î<ĢÉ9éČ6?~|Ķ^õ(°N°éŠ=÷›kŪŌ—P†ĩ“¸,Ĩu­jĮĻOĪpÜõ­TT_~_C}NY¯2$8;@ũOyûߒäąé@ô4uGWf_ĸë,‹ęÎą<Ņ{?ž}#4ŧJĢÄî,+ļ$^×é:ē Qt†ēËģIĢ2U&°”čōî6*HaˇúúĖĪįdŊŧ¯–Éz å[&ŸÔoŽEMÕ&&DÔįĢ&9}ĀˆÖ•!ū§¤S†ŲëF\ŦūčÂ^XÕÄōņqHßÖĀ0!:Uũ|šÆz=+ėt'åāåøhkŽ[u—^7ë{jđjq?u…×\™•Ôd’Ō¸8žšoëS!íúŨĩWŨĨn ã9Wi­÷āŲt]­LcMu×§ô|ņ:Ë={ŅÕÅĩu+e pËōķĖ"oËÅ}Åļü’yØRļÍŗÕļ|ŊČWķĀjQ|ÚŦÜ*aĀuā!Ōi~Ā”ŗfĸOWŲ!–ü ĖßBMP×ÉIˇĒëôxę:¤8Ttšē[ņGBōŋą‘žÛ&ÔvzÔ*ɐ*:æy´Ŋ,ęNÅvu‡å0;¤m~ThĖŽQęNPåøCM×IO‰Ũ9ĸvõ$ĢąN]g!Ō€xwyÂĄô:3âxUxO{ÂÂr?Ū“Šéēž-(vįõ”NiØo’G]ˇüúĖÎ'$œDxAtŒš`ãÄĩ`ōlDĮ<*ē˛l‰NÃ'Pw¸ŖØ—ĩdÅ2ŋdĸŽ{~č%đ-×uŒÜĘÅtŨlîŠ×ĄŽņ†Ä&én-lÅnNn€ë¸ŽģŌÁXļ͍SĸŅķåuũ!Œ)=HgL˛9ÛôÍ­”u>ĩ9MqŊ„s4$¨ô$“îˇSW8/GÕu} ]Įh°Û‡ BAn"ĨÜŊb}@b;MFĻfƒ”ôˆĒOœL&q;ũ,Žō°H2ėÜ7ĮĶĻĢI[s֌˛E…épMÖL‰IĨˇž_Ž\׹A}ŧmī<Īũõ,Ōš&ŦJ¨ÃęZdZ ”Ŧõ>HNč0l÷ãéĀkŸXĸvęšŦK‰RzŽü&ŅųņEŋŌyžšJÛ{›J§ëŌŒ׍SwfēáKC׿­Îí4¸…éēË&’¸ņy%›Ŋv“ŲM7Ĩž“[kHnZŠœēædęÚN`7|ģi'°āĄB×uwąr]7WãuĩĻëßŽĮõé´ē\–ģŨ†Oj×Įãęp`sX¨ rŠÃIÆčPË=*NwlċØãg⟜i@˜#w•lTqU†ķ0Ž Ylm ­˛Ķ?Ą™nÜÂO!ŠU\MG}@YZ”OšˆĄũÃÂY9˙é1üĮ(”cEt{šÄĻÄZ˛ôX^ÖHbœâ€ž8wąPžœŽ]—°SÍJA8ŦVE˛æÍ”ąŋâš0šsvĮÃrX~¤Áļ–“•ÉnZŌkF‰˛Á ūCĀĢÚՙč:(íŸÔ‡°;Ļß9ũË5;zߝî^­îBãuW {o3ļĒ;GOWwÖ8žga"!ēŽ,O˜ Ã՝‘nÉo.OtˇščØdõõ•_.Év}ßŦÁ'›uē]eāˇĖgÛUž]įÂ;îˇëŖ”=Ū< RÎ$åĖSØ^ģvßL”ÆJæGIļíÁCDŧŽģ‘žĮëæūx×uĢŨnąÛŅ-`* ˙=Ÿ‡ls˜Vߨ{QxŧNLmp¸‘IÕu¨Ļ$å\hČhKGœĢ”Õbĸ|Z‘É%†.žĮĮ~ŽOEuúju69íârœŽrKŧŽ[zøĐĶ”ģc ŠŅE‰tqƒB€Öˇ‚ũ5ŋ¯˜.ĸÚīt/5ŨÕĘKl•ę1Ŧ[ĒĮë 3qTnXÎæ’‹üė¯ëk.ūZJ—Ą:]ņ5ŽTĒD‘ŋĀfŧ´ u]ęŠ×Ĩ$^—XĻ™¨;ÜzbwDŨ(ēu—:Õ]īm*îInŠĒ;At6ēŗŠ;˙S.’t(=ņŦDĻŪ_§ŨkĮtŨ×g~ģ&ģ nŠđ°eûMÆ}žÛ佋=E‡×ËôœįĪE ŧ´m*|ēßōƒ‚ߡëį°AņēZęēårąŨ.6ļē;Ëí÷°Í ÃårQtTw˜œÖZ]Į}KnH} N<ĶuN8u‡[ʒj°sãž-ÆëRoŧN(Ī–$;ãé”úpâöĶĻF‰Ž%ŒÚšŪŠ5$™DR §8NāLYĩŠ/;JZĒ7)괆Döĸ¸č:,;Y¤ÄŖ˜ēļÔWYĩå˛VyĶū•' lĶu"[ˇQ,ē{í*TJĀ~šÔuūęâ{5!%ŠÜN¨ëR—+‰Žc˜‘Շ؋Ēĩ–¸JuĒë:6Û]] u–—ši×doüė’˛–\×s;ņĀxŨPĨįņҍģbŌģw4Ø­ĖößĻBŠÆôĊm{SqQytKé _™ĨôčˆŨásaj p L?oįsqģåįSv:° ˜įtȏ‡âtā÷ÚņíĖ|y>”§ũ _œö¸¯,-įĮî%†yæų 5Ņ^fbģŋŽÖtŨ~ŋÛn×ģŨfˇ[ī÷­ßī×‡ÃæxÜō {š6ŅŋhÔNÆĮN9'ŽãÜûNķ9EŊY˝§cž$U–ViRÉ7ËĨÂgüĨsÜ7ˆŲv€…—eV)?V–Âq¯§ԡć†Äböį& ]×Õ,čØã1^'MRWcÛzˇûīŠŗŨ ūTEm™rZĻĸĨ2 -Œ'&ŠĒīî8͓įd;ĖYûfy~ļ͓ë^}Հå9Y˙KÆ?&Öú˛ö`m3§ŽŠåÍ'Ö7“L¤î<ųĩŠĢUÅ% žĘ”Ä‘ĮC€ö8Ū §-¤ŽĢLēƒŲëu×÷T…I‰nu§•Ųų‚ßËnģ_Ė×ŗ¯Í|ļžÍĀŗm1ŋU}ģÍįĻÛ܎e X&; úŲŽ^ázA%üw>Ûųķõ–$IY–H ¯‡l™Ü‹ePœû^8“ ŊDį{ēAŌaôŖdæĶ Ĩ{_ ?†äÆdL ;~°ŧfJ!@Ũų^đũ>ˇĢ7yöĩęÎ=ĩ­Āz'­cÕ]_OLęmXmyBŸĀvŧ4HŨ9•žëw!īžĢeŨŊX~Zūš¨ƒöū=ž ëĶui^‚Z;œÎĮĶåČ}‡Īm ÛΘíâķŋĸĐn‚uÚ`" #ĢB*4;(ËEbnúĸJĖPeEįÛW6Ũųēí ī=éEՏUŧ9Ÿ]u`į–y1Ĩ¸>E§ß2•ēķŪ_GßTė_ĩ+ē0uW„Šģ1īÄŗŌ]†ˇøöéēŅ+ŗūwßܖŦLrw§ ĮĪŊXžéē…S×uÔgbې!D×(‹nēëâ{]ĩė°îēËuėĄ5s3TY蛍C^éIî_ęöĩ^ Ëu1qö ly˛ãĩNߤčlęÎ @‹ēŗ.O$O)ŊĄęNĨ8c+ˆ.U&°]Ũ Y™ Rw}žxîņĩÛüį°]×ãõ_ųî!-^$ɧ´‘”FŋGŽįÍÆv‚BVYiR–sK•oR”+Úäîŧrwē(ÁųazO‘‰Í7ĶEuAaV_ÕŽ=ÆŽÆč^ŠčŧvÉAt¨âŌV˙>Lé=õ´…õšZŨQwA÷ڍUwáø9ī'F3ëēĨO×9”ƒĄ"t¯EuTrkēž™ųŊž–ØAeĄ›ĒZŠjRhĐEį ŊØ?Áãz”F<4čÍwš‘¯?Đč(äâ-ĕŽÂîwÛSaīĩķ߂2üÖâūûņČ-(7ĢÆ#“kO]Z4œî4Š{X}ģš•^ēBS“ŠĩĄ*ΏÅĻéēĖĒÜÜo*ļįą*ŊáŸ×qĮ…*ƒ úԗ›˛2˛ę4ˇP–A_Ũ+˛4Ĩŋšī„7Ļ'™ŨßÍtrˇ•vŖŠ”O­ĻRžÖ˙Ĩ°g°uģ+4ÔÆŋ+ˇ‚ˇ…ô¤OāģÍ}—]¨ēsŦˆŠģNÅŅ[S¨ę3u]‚t×.ŎQw“Äņ&SwĪ)ĀpRuëēq1ûw(z°S˛*.—Wžē•™´ĻR\jWeô0į÷^ģ.nėT—ãyFíŽ)•…oúũWö­ĶTTeŅ;ܔģŨĻÂÂß;tW˙¤ĸS՝“ú´‹žÛŒŊ^ÂBų.íMStũ’)ē“ëēĻÆį ģ!ˇ‘ÛHZ›.Ö÷”D]ˇ°é:Éd#•žz‹ō€¯bY?Jč"Ž*Õąū-WcRi#4û´ąÄ”;Ūí“ŌŊ]Ģrs+ÛÄG‹€™jŠLՉäxâœĐTĶŧU1ZŪ‚bãuøfÅC}bë&Āö‡Lę:đmԎj<ē=:ÜŌ`ØĶNÕם\u ÁŦĖRlŖYŸŽ#äSņOfW ÷-~ŧ“ōŸķ•‚›ãFqíÂíVØųžŊžˇ%÷/#ôÛö2ĩ›´x+#yúøû<{uE°+ũg0ļ’Šƒî ‡@Iö5¯ŠûŪQ‡×ŪÄúķ=Ž)Šköúڋ_ĩ=^‚5ug‰× ĸģŪî›Ũ~ģ;ü„?¤˙^üJŋøeø;ŧģĪėØî­ßSÜŊŪN‡zŦķ@_ĀxšG׉É&dƒæÅ× ×üŸ^\šōÔ]Šâkš§îr*žÖōĢfĻøĶÖ ÅíÃq+éøÆ…›.EņhŋÆŽô‡‡äqåÚˇ×gŦkĪÚlgãē˜×kRoöŊņ}x ™ãԅéwĨđCįx0А˜~ŨBŋŋ.ãē~>øŽ!ąg°Ë+yBČĶIĒ^lip…ũąHˇXUvuwéŠÉŗŅąmđz;INļŽrœŲíy”ú7æyéûNՒ^ī¸v5šÖ$ŨßO‚-¤¯vØB2†đp‰–q‚'dėOÄ3-Ĩ'u]îÔuŠëž÷ÃÕ`Ũ]Wz8vYHž^Ęraę]éĶ“X3SBøS6ôŧümõĸ+8°˙øûdŋ Ŧ‰éáœ0ÚK]—{âuE¯ēŽī^UĶā÷đŽÆ7ķ8;FI†›ŗ‡‡o RW9<ƒĘ¨ÛkŊãē¸ŽŖßús …÷΁Â#$˙xÂSņLU ]×­ŌÆë&#™Ąė¸p==¤3äW`Bšˆáx ķŠ]Ã'ú—SSžŌ\},œ”ž'ã}ČŸ}õčēâĩēÎߘNL/ĸ‘Įrqƒ;ƒĶĻ#.e˜éJ×ûÕG1U>^ÜøqđąBĘiŒ:ÔŽ:;ŽõŪė.z¤ŪA•öT— íįcÄ5î\cķUäāÛx]ÕŨvĸÆëēGŽ^¯ĢBō‡ü"ü4ņ:/ēÚ3ėéSíöÎáãn ~f˙vīhK{:ÚÜeũ9üæīWŪ>iöĪĄũ2~'â ‡ÆëاŋI× ™‹kvĻöJRB3ģ}c¤ ŠwōÍāŸô×Ëu}Mŗ§NeĮEČø 'Ėé‰Ôë{ãu]ײe5!Ž\yjÂĖLķ›'čÚ÷Š#^ģ;’ŊSšĶ{Ė54p$5FžÆČcɯ`īpnŪ÷ÔS?/ËšģÚĮŅVĩ+= šŋTž<¤ŋÕf čŸOøĄcÍ5N;ėûSã7Œ×=O2ĩ‰ü6Âqėëŧëۏ[ȰRqŖĨۆŗ1äīJ˙?ûē[ÚVo˙ÚĖcxĶ\} †öĪūūo/ž1å'Õī0ŋ¯{1WŽ<ĩŽ]ŋ Î_ŸÚąī÷’°Iļ!ø9"Es ĨŠŽģha(pܐ:×ã-x_Įut\ëʒî#ĸqd20ŋkܙyŒ1kîëã/Ä>]×üdŧÎÕÔŽ‹2˜pBžÅû&ĨįĖ>T-T`äW¨ŖõîkšîČĶ„äyRĮšģÚ͒§kék1 í{´ßúķŽ#?ŲŽ"RmNīˇÚsî¯ûFfތtķ—ĸį×ÄÕŧ!ųûpâíÃŽŽJģĢŪi_j!Ã–æ §_šĮO.bŽüŖčČÕæ~ÂqĨëdâ'WžĄ}rŧwŽ5˙Øü1ܯ{ņs“ø˛,“$šG‹í˙aø•ęA,¯Ë ]‡ö‰.Īķ2Z´h˙ƒņ.é.+ū€ŽKĶNšao0‹-Ú˙Â`ŧèĪŌt°Žō<ė´ĒėŒ ‰.Z´˙›Á¨‡ąÎŽ zŨūĮUœé‹ĸøéV-ÚŒũņēŽf%Đ÷×åo¯‹\-ÚÛŒÕũ~ŋ\.WĢ÷K‰ĮӅŽ×Yŋ7‘ëņ:žģwĄųëëKK)ũûJüųų˜ßä:Ø×Ķ>×ëÚyąXÜn7LIĶtŗŲ@%įŗŲnˇ“3bįķ™–˛Q˙äŊípŅzŊŪnˇ0ŽŽÜ‡ÃétÚsƒ1ødųD×õs…]×Éx]iŅuŊ, $ Ĩ3âęc] —æ7šöu5đ4~Í ĀĶđÛũ~o¸Aā=QÎ×zĩú"\÷åeŅhŅĸIËķØ FŒ)” Hz€“$ôŲlæÚ÷Ę­7Æ~ Ÿhņ:ˇŽkr=^Įww`SÅIĨuÛŦ×đ_8SÎÉ,OrŋƒĖ‚DĻĩŽWĖ/ĩäÅë9ŽG×QŨ…ø-Ī3y-āŋf;ĪfŠZĶtëęD‹M°PŠ:ĄHq8i‚a āMĮ>eļË傜@"%‡„đÕuĨ¯{ˆx].žģ ‹×™*N*Ŋũ~w:ĢĒ"ĄMđVWģí÷ũâģŒ‹×IõeŌå1h4ķ—JjUJ#*q>‡iî ijëŊÚŅĸũO 8 †Œ# 4đ@w0ēå_aÜAúršü÷īŸĢ˜ërļûfx0æŧ3ĸûü„Y0Í6"^W8u]ŖęēĐx×Â<ĀX=đ@˜xNĄ•Ŗ“ÆëL]§EØĖŲ.~šĩ4jŦéļÛ,Ë\+Z´˙­€A h Ęä_Ώ}||x ]4įĖ‰Ô‘–gDŧ]iÄëäÂÄTņē–ô8ŲI Ür8ā\ q€ĀĢ6įņ:ĢŽŖKT×áĪ ūŽ(Ĩ9•v­)ĀhŅĸ=¸ŽûĮí“čŧÛŋæ¯J€1 탛ŋ 7ØFx“čŖâuRוV]WņēŠ0§;B3âuL×ņęrĶöŊŨn( 3äX#âuRŒ$^‡ F…Ēč´Ō4ã1K¸/Z´h÷;›u"ãáŨūYžáT0ŒŲ^]÷āōf|ĀrÚÔUĶu}ü ą?^'ƒu@z2^×kRי) KĄÚ@ux ˜--SōĮZab‹‰ {ĄYBį‰×sâčF¤,\‡ÅFØŦ×sKĶg­íIįšģŨ…dį°Ņĸš F‡Đu+\x…I- vnxÉ'^'  Rר¤ņē^St]-uŨu]Á]Q‘xßËĪĸĘũu<]*=šËD”˜kƒâ]ˆuØä~Į}ĄĄāŋHAlÖ}\ŽK9yB9ĐŧRãņûëôūē/ī]sT׹KÆëΧÛúl7Z´hÔPŋä€°Ü^` žŽë-?pöWyãuŽëŨą™ö6L×}ŗÅį&ĸE{[ĢøíŸÍN§Ķ“…cŧ.ФŽ+Å4öAâuR×qŽ ×}3ÎmAļhŅĸũyƒą?4^'u]ËusS×5upŧîyÃ{iøtRũ™ßs-Ú˙Đä{Nęē˛Ö×ašĸÃ Ļ°4^Įũ{`|w_¤ģhŅū?†ī¯ į ŠëJĒëDŧ(Žęēw×U-ŨeQ´€šß‡ W‹/đ¸}]æĘķL9ĪāōC,āŧŠ×ĩš÷ēGėà b Ôu×1]÷hu]]ri×´ņ:ļכ(ēŋ†+_žĘ’ÛŦM\éé–üFģ|HWūĄĮz܀öÚn!ØqM#ūNLu]ÕčņēV×U­Žûļx]´'­Ā!ų]yžŲw*üL^Qˇhon ëšP\#éîAâuÎBy¨ëo¯‹Øƒ+KzŨĨ(žväņâųz(˙™}ŸŠŗˇūŽvváwč˙gŒē]§ëēx›ÆBļˇ×ESmXÃjõ@<ÕžSÕįé:LІŅŪʄŽëčŽÆë˜ŪcK´Ės]ˇu—‹ø`SAŊûëS LŸŠÆ#~ ēŽ!ēŽåē9Ņu‚îbŧ.šb!Ģ`WĘ^bŽ:ŧCŨĸ}ŖQ]WēŽbqŧĻęt]ËuīĀŌ˙^\ä‰Ę*âiąÔurŖņ: ¸ĒĶu1^-Z´ßj¨ë*§Ž{p]÷āMÄx]ÄGü[1Ķu.ÅÖ|ŖņēV×qēÃgÄÄ~4ÖáÃ%ÚĪßeqÄ˙ĖéĨÄER×IēSt{­Ž{ Œ×!ŅÅĮTŖE‹6šuĪÃpQ¯{´ŠŽĶu"^'‰ŽÍaÛx](‹ĘĨÉ÷ĖG}ôŅOâ‘ņđQâ@u×ę:ÎrĻŽĢšŽC˙¯ƒ:@üĘŅĸE‹6š!Ŋ„^‡ę:¯“DĮū¯ĻÍų‡™ÁAĨEqÄOüĢŊŠNÆëtûĐuØ†ÅëŌ4e\WG‹-ÚKŦáÆ¸NܯCE×é:¯ƒâčđ¯ņ:ĐuĀuX“gūˆ#ŽøobĻë š^E‡&u¤;S×5"ޝ“ēŽŽ-Z´×HŠĶuiāj,čēŅu-×ÍmēŽÆëčŗ„VtˇŠ°qÄGņ$øÁ?îÜQ——üēŽMėt]č3bQ×E‹íĨ&ãueØk¤Ž“›¯CĸãjãuônĻēN00ķŋ'†Ū˛ŲlžžžfŗŲnģåßūÖ:ā×â¯×ëgĘĄ_ Cü>íņ´¸îâue/ĄŽC*k\ēŽ˙ ãuU˜E]÷[,Īķų|~š\xŋ(¯×ëvģũæ:|2FšŌ€ˇ§-0ÚZ¯KĶÁēίkÄō„x§“9x]ũ>ŋ[ņnˇ;NÖ0!]¯×5_áM’DîÛęētŨ\Ôm†ƒâŽKÄxpŧΊëÔu5Ņu1^÷×,Ë2ėXĶÖa÷ûũņx„žŪ[ŗę: ag11kŧnš\Âĩ2ÏnšĻĘ` įĩZ­čžX āyYøápā´Ī*ŧßīüííĮmDŧŽĒ’čZ]gÄëŌ¯ûģ.֚¯x˛ûëv;žčÉŌ‹ĸĀôÍz-Ö/ėĘJÆĮj%vWmÂî ÎĨō¸Ā-ŗ/\‡ÅUÆ{@G¨žd™eģûɗq Ü×ĒÍķĸõá@™/wûŪŽWš‹éÚ snüņëą×Ããu•Đučņē:ÆëĸũJƒŸū%ģų¤’)“ßŅíûmLŧŽëē˝KĩxāHÎ4q§ëjFÄĩdãˆ#ūvŒęô~ŋŅôNžM=#ŒEŧŽŅN/Q]įˆ×UFŧŽ÷õ)ĨĸëDÅ"Žø›ņ’ŽÃŌtE×ŊA=#EŧNęē^˛ë:-^—wņ:?sšī9yŖ_ˆ#ŽøáZÆëøGvzyé‰xE-ņ:QŊˆ#Ž8âį1ÆëR=^įÄ=ņē"Æë"Ž8âˇÄcãu™Šëbŧ.âˆ#~Wü˛x]ãuGņûā:Æë"Ž8â˙¯csØx]ÄGü‹đčx]éŒ×Ĩ1^qÄŋ¯“ ™¯Kcŧ.âˆ#~?\ˆ×Õ]÷Pu]¯ë}}ŠĒëjZŊˆ#Ž8â °¯ëá%-^—YâuFíJ¯+Õ¸œËŖŽã5Ēĸ>úč'÷u¯KJĄëüãu)įē€x×uŊ,Z*ēNTŦ˛â ÷Ą‚Ķ›˙1=õĸüîZĢöĶõų›øôÛ_ŠÛxÔu}ŧ$tŧíD×åöx] ŽeG‡˜æeūŋČČ C|&]ŧšwœHIŋŸõ=ū/ĩę;{ۘĨĢ‘ßĄ†Ū×"^7@×Õ¨ë,ë°étņ:Q=‰Yh?áŽë9⠊Ö}ß “iÄštįežrüuX­L[­Uų•z‹vûĨXkdĪŸ:˙Iü‚x]Ģë&Œ×EƒXM˙… ø$†6”ÚÉs"OĒģĀúmU׹đ+ZŗŲlŗŲdYöŠvûĨxܡ–™į94īŒÛnˇŅĪéWŪŪ¤}^ŠÍķÅÖ¯ËņēDčēIâuHÂĸˇ°ßÄēŊ-PĮøûX’/°ķôëz8Q¤Wũ%jjäo}HũiĢúëãoUy,Čpš\æķ9ŌŨä­÷}ÕÍGd{ÖI’\¯đ´ũúšČs 8h^ŧļÛí›ôęˇõæųÖĪÅë2S×冎{"^Į˙^`g?ƒ–ŠŸ¤>Š qjô4Ģä~_.ĀöËåâvģbú—Kž¸ÄĪŖTüár9ð…Æ_QĢ”3Cg6~Ō]įÂĖĨĻ>ųg_ø—eÆ×šË5•ĻX´Ē]uhŋžįķTb¨äjĩÂOĪā›~ņ¸ O ę˙Ã}áŒOÂīå|{]Îį™Āx,Ø ōĪø^˛&Ú^“\ŖiązÅY{û{ķņüáp íŒų­å@“žO'뱌oōÚ{ĩú[_{ʖ?ĢWA;.Øaŋįfü›GíųâŏÃļæĘŠš3cŊąÅŗx\Ū‘–Ø‘øW,É՟kũ¤€.ģÃY›ŗ›flŧ.ĨēNÄëPŅ%ĶÅëĀ@'°!YwJÃåq`B~ÜQ+ Z p’Üwü3˙\;´?đrâō¤P1k5ÎÃ4„ę(ŋ‡œøÅyŗčŗō ­ãęĪ+“I…ŲĶǜ!ŋĩUĩ_OčíŗYûeX¨ĪZ:ŪB|ĒF~ú'~|°â_邁_ō/0Îf_ģŨ+ŋ_b…ĸN§ôđ{ū9El܋ååŋ•׎8˙ūŒÅ%8ļ­W˛Ûû¯8\DüF¤ŋũ%€Nnö ™Ķߞü*Ėđ*\YÛÎÍãBNėcPą(.(/ļbÅî÷ō¸˛Xāœ€–=ŗ÷¸ bĄˇ`¸ĐZŸĄWöŜP7‡Ž¯Ģ1^įŌuÉtņē–ëúĻZr‹\g– r:qĻ ĶYâéD;RMâ]üBLģŖ'p.í‰đBô2á"ō ¯ŋZ_{Ęa([UĢYž'ŠÚī2 =\čÕRvQōŲÄ p%ėĢŧíg˛á‡›‘n/܏~Ėz’ë5–ÜļįũvÛlÖ[=Âßī7ųWígš–‰MÚÛūĶ^aŊ.aíYyÚvÁJĐU¨üpyÉ>†>7ĢõĖĄĮĨ%Đ>Cû ū*0QŧŽŅuŨÂĐu9×QĩâBžį;‰<ĩŠŋîI ēēîô† ã÷+­œšGwá‡Z~Xņ{î˙å)}Ô>Ē>Ҍ•‰Í2‡āz2!ēžsiøG{­e~}~J<ŽūjĢöÔ§Ņ[UŠV>đˆŠļnYŋČ 7uf͍ĪO>™ú”ûНt91ËŲVŧëØ´(žčzM€Õ+ÎFL(ļۍôYÖ]qÅKŗū‘[eŊÖ˙n¸Ú+*ĸs†ĩ§ĩmåOMD¤`ũ¸î=nÆ;›øōNãé'_ŽcU-ŨaŧNęē^ĸë°ĩsëēFŅu_ZŪs"OŋĶ2õ{đÅŦNr(•+ų_ĻÃ/ŦōKD}å ÄödÚų]ž'R‹îDËT>Ü<ļū0' lUŦ ÎaÍr´ōiŧnšXĀq^öIyÉ2GMÎȃĨä\:7ÛÁÚ&?Œ+Ëŋ\·ũĻā[Žx]™eÂH‡ĖÖcéJI̓]q§ļ§ĩmHåĮeú|6cr‘ĢüÉĶŽkÅŊĮÆ>ŸO¸Z'ĪŅÕO„^-éą°œF‹×õyŒ×Ĩžx].âu•¯ķą¨-^WĄÁ`‘Ģ´ŦcÔÜÛ0Ę\Z9ØaxhĢ„_ē9‹)ąt˜Ā’û]\tį/‘Yæ ,OÄU˙“1ËĄŋhã杴Ēģ=­ĒÔĮ\‡VÄ<ŗŲ /tŅÃa/ëĖãugvœDÆyzŲ!įņx„ƒ€į{éí@ņT×ëyŦ\ņēmUģpîāeŠqÅõr 1ŪÎõHíŧÛá:,Ũđ_hdhy-x¯hãu’Ęā§'2`P{ZÛöh×aąx¯“ûZqīqgŗ/Ņ‘Ũ^Ž~­}ņHrĘ:ׯK´xËx]ęĐur+ãu”E­¸´ÆëD%ᯠ¯žˆŋŒ˜Ķú xģ]—ËEˇšÃĶĄe䊕ü*¨ë—Č,s†‹ÅÛšč<–E5lWåŒrč/ÚčúĢ­jЃ$:™ĶZŊŋnË&eYW7ž¸ļ˜Ī¯WØéđË Ų ÷ÂU¸^¯¤Ûû~ŲqŨđKŦz~؟ŧFĶbŧŽeQxŽ5úļoēR’˜¯kŦņū: Ų7@šąFf+Ũ7y­Í^Ä Ųøō÷°ö´ļ-g6\‡ãbžī†ģŲŦ嚸f~…F1ŽXAåņĮ´āqHW?Ąë°Z‘%¤Žëå%Œ×IĸsÅ뒉âuˆA*āōJí6ø+äÉ1ŽwĀ˙†žWˆÎov{ČcŦ&OŽKhĢĐVÍíq¤ˆũxČŋ‘+ū.õ˙c¸¯sč:œĀZâu>uĮëc(†°ô_FŪŖ@ļadOÛ÷­0*ØöDDW—žfaXöW|āĢë#ZõŠa"=R×ļęMļ꛴áī•zÅšTn¯;yÅ]Ę9⊰5^įÁ]ŧŽ ēŽÄëä6IŧŽí0|‡ŪízM’;r˛`€!‰õĨ4mgIꞈqՏŽūÔʝ¤ģ+;‘$T$ŋūĒu{ŗœ)돭Ę+ÃZõÎ[•7+`Ŧ ÆIĒĘ^ŽÖž?Ūļī‰õ+ΛŽģíŠ+ßÚū?~žo‹ëQņ焯ë#^—O¯“ĄĐCî|$ĸ挑ŋšĸŖ4^ņSž#Ëđį}žG§)ŅŊŧ>!­úíöĢ1‹đ+~ģĩ— Û+Ũˇāņē’ÄëR¯kt]7UŧŽbļÅü>ĸē퍠ˇÅí‰íŠpAUāĶm?R^lÔ ÕŨ8Ĩą ķF.Œ+^Š+ū.õüø¯ku]aÄëÄ 'ĶÆëLĖ|UųķüŒgņįBkōõųƒØŧâīSˇ?GÄ똎+ZĸK‘ëzâu%šJŧŽv†čŖ>ú)|­ĮëzxŠ'^G¸n`ŧ.é×Eqďƍ¸g{pŧŽŨvÂdûãuŊ,Z¨ņē>~Ž>účŖękŒ×I]×ĮKŒžHŧ.Uãu‰ĸë^¯‹8âˆ#„GĮë’V׊ņē,Æëĸ>úwôõčx]!t]ĶŖëčėØę­ņēčŖ>ú }c‹×y<]‡5ãuwŽëîY9.^×VŦĒĸ>úč§õõčx˜ÆjņēûH]§?7ũ7øĸ(ØMü—Î"ÁāŲkÄ;ŽŖ˙^‹×ę礋×54^‡ŠÕ]Œ×Ŋšį¯ũŋ$É=M“”Yô<´{ĄŌŨ\Íč{}ÍãuɐxĶuy-éN‹×ĄŽē‹ņē7÷÷û=åĪcâŗK%˙'â@Ü>Ņ,ŪũûûflŧŽÕu…=^wņēˇ÷wöb“ėM¨ãmqQäí{u,t—áëßájFßëëqņ:Ēë´x]KwøÍDŠëJ™:.-ņ:^Ŋˆ_‰ņ•š%ļ0~š‹XÁ`ø"°ÂŌV~Žô}ŽiÄĖ>ÖÖę:y(ûxI×$^ĮtStR×m¨Žķø¨ë~Âķ×ŧįīŖ ŪÅŊ}į{Ķũ~CĄGķ¤ø•Ž7¸šŅ÷úZ‹×õyOŧŽVuˆ×ų˜ŗ’ņēDÆëxÅĶī•ķīäâ›ö•<.<ágqū$æŊ*EžÅãx8¨QŊd<0ETHéß÷YeõƒXžāöÔp˜ČĢÚĪ…×|÷‚}YI| Iæ—?|RB:\ôwøey+ė×ąéM§*ĶUĨ§("mß_‡ .ؐĮ°ŖĘ_ųQ ’wĩ3dŲl6Z˙¤y `*Č´3î“ëPėŒ|~Ú/ĩovŖīûįņôņēLęēú™xũU‚ A˙Á<0Ÿ‰{ƒĮī/cūķųlîû&ŋ,o…Ũņēī ŖéTÅIugUD8Ÿ}…6 —‚*™.ŒæOšíŧßí€Äp2‚KÕōė0˙H눞Š=†ƒkŧ`ēkvWxš\žC?ü6\Š×ŨŊņ:œÆ–/Ž×‰o—ŗ{bég ŗöŗ‰1^׃ĩxbPk8E%ęĨ´Ē8SéIE„Ēx…6ãëčĩtū‚úŦËī×Éū Y‹…™ú3°Ą}õ‹B/ū™Q-F>ĖtÚ øO÷ÃoÄOÅ뒀xZU–.\ØâuhøĢdĩOō'™í˖Íj.õU°˜[ĒÅÜLg*=Œ_!EŧBƒÁ؃`×+Ž‹ĸÕē >N‡äųēš÷‹|áËÚņĶÕ2ķĐËu€)ĖzŊŌjEOaZ —očģq]×H]×ËK¯ët]ĄÄëä '/Š×Q,~7kĸë”üãb ˙ėŋŋ×=*NSz8uũŠ=œŪV‡Ô ž˙ī‚™ˆüžxôɲûļ%fāŖ°¯› ‹×Á‰€ĸƒáö>ũđÛđņēēĶuņ:4;öč:ãWIĖgĮ#ė žÆëd†år)ŋĨÍ4-^G×RÉ:ŦSÅQĨ'Ö^s-ĪīÅĐĄđÎēT|Ũ@Íŋ2)ķ§n]ˇßīAw•<>ŗÛmÍ Điylš<ž~ŽŲvģ•×ãuô¯f9TéÁŽ@tüģœ˙GĢmņ:ūūx‹ĨKžûĨŦs)ųa. ø+`žûķŋ,o…ƒī¯+M§)Ŋ;ķaŦ1Ũ÷—bŒíĀÉāeüįXÉķ_{W Ž, …oŧŅH5TŖŅH5VŖ‘H%‰T"‘H%~īv` ū{ŋŗĮįÜ÷Î1ösvvöŲ _'‹ãĖ- J;IņēĸÉą…cVíÊxZ“ô|Ė`%Č:I_Wšß;^tOĪŌØo’Ÿ ‡/ÄĶųēŧäëÂėlāëâÜ_ĮáyáûwKŸKsJãO†Ž™fˆ¯›ÎŨqxR(îã넭ģÂ×ՙôáŦûž“ĒlŒŸ‡ų÷°Wq.Í]VÉNą†ŊšũˇŽC¤(–ĸÂß{ˇ>üy _ĒäëÄæĩčÃaģ¤ķuĩ_WņužÆ×Ĩāë8Ëũcų\ĘÍ폅-–ˇđč˜X~M(îãëđ)4žŽ<:2wĨ_7h9ë}XÅ×UĨĘß= üyœÉÍSŧo÷ ž§’áĖÔNëgô)ã!\ķuéģ¤ņuĨšcžî{CFŪ ,^"%ã)8ĢžæđĄx_WČwu2_Į˜1ãÄįųēB˜ģžÎgžŽŠĪāëÂ_Į’%K–•åëü~žn 0_Į‡g#_76_˜ø:ŋúų˙|•Š%K–,*_Ā׹_Į‡ˇ‡_w5ŒāëJs×āëeÆ|K–,Ÿ-_—ĻcėRÍ×%#ųēT÷āŒXúuaå×éĪ,1f˘ņc0Ŧ‹Æ×]ˇKŖøēé~]ûw,Y˛dųPŲāëFúuНKĮķuCVÔÄ×Ŋ`˘ņ_Â_—ŽąKŨßMđ똯{‹Œ’tsŒ—‡„?üųį”DqÚĢķˇōuÁ5ž.ežîŗ1 ŨōC=ŽAz R–,ŋ]nN‰}æÎ¨ķĖ×ũˇŌ9Ŏ›† ūšĐ]Ƙņ÷â(-0w;nbÔųķu ]Æ|ŨW`øün˜å‚ÃāĀá/˜&7ŠmÔų{øē€ųēo–P /*ØÖqø3!?_ŧ(ˇfŋî)|ôûp,üē(g[Įáõ#}ģŨŽV+ÛļĨ°Ūī÷7g eöjŋŽ­ķ¯áë˛Ļ×ÂY‡¯+Ë&eĮ›Íf.Ú'I’nšˇcu0÷‡”g –~Ũ]ļnšXˆî7˲nĪvš¤lĄ[ĮAŋ/ķŨnGkũ$,|ĢÕŊ¯“ŅŲ^2čWŨP’‡ä3О)äō¸4:2ōx<Ø7’čôūí9Lßö11ë|Å×Ĩ•_7l—ȝ &đuŖ^gŨöëĘ7žf°lPK×=eō×uĄĢ­4Ÿ€Åšœī.ÃT|§_4ŗīۛ‡?Ļ]Ę6—ĮIûžw‘“`”ÆjfŽK0öŨWÅČ įžīŸeØíŦGØēáöü3#Ö ŖØķDĪÂÜĄ[aņ€ŅÅ4õ]K§“ˇ#]WEÂŋĒũēŽÎKŋîR)5Â.Måë†-g—¯“ÅĢ­1ĩCõ_ÃJŖ<õų°išË ÅvO§…đ0L|´äœ°įŅĩHHÄ Hp<*Lų#%ÜiKë‰ÆĪ›W!ˇ@ĻDH“dŊZ!M-NᔑåųžZ9?_‡æ!§šÔ€ÎĨĶ^(˙RdXĨD€ZBo/Ԙ õ2`Ō¤o‘ÍԊ)ĩH‘@öN#Û͆˛Å,ŠÍ¨ķ]ƒĻœˇŋ–¤/C˜>ąv°,HåYéwQ¸[/ũÄUĘ -ƒ!UVøuD˘öD€“ŊZVĻ^倛BۇKhĖŋ¯TŨ>ŠŖh%/Įrú?Ü>ú,CÉpTë"gŒŖ–›Šģ ^¸ō§HŊøj`E@íŦw"°jų‹‰¯Ķu^įëÆØĨŌ¯KÆđu…0Œ#+IåųéĨ_×´Æč…$1[i2ƒ™mËė FéųĖdô<6ú!:ÖŗŊ:ŋ+žŖO÷UÚéwųTXTD䲲ü}~ŨpŊįĻCKš1WÛĶČÚ!UÂÅ` ų7Kĩ¨ ž…A˛“B_û¨Jé>ĀnˇíŽĮi­´’Á–ĄåĄá.H3`ë.r:^H—˜…œ1Õˇ]žN7ōuɋøēÚ¯ëXéĘŧ4°Î›q™˛cRÃ2ŠîTŪZĪŊŦŋÁ׊a’IgUÕŊԟ<Įŧ‰H,ØÕŠĒmŧ ŗĄ0YEÛÕŽÄEú'Ŋ~]??6ė×õeØcÖnĒWi„gdŠģWhOƒM6O(ĻHcūeŠfŊĨšu.‡ERkęĢíŖšúģfÆv _ũūū’ƒGö‡B.øŸõ ÆlU€“IÎ!dkĶöI|ø<¯Sž0&ŠÜ4ŋ.šę×Í:ZF/ŸNGi`ŗáĢpĶŌ¯K͝õëÄķuJų1⠐šœĶģF}D.ž8ę“ũē8ŠtÕÅ-ēK¤îĩ­€*čŖ ĸֆ0ã~O†Âo‘ύœą›ũēnūFķÕמ(_'JXŨvķSĒn á˙¨ĢŽļ¤_ˇ3îŗ Hŋ2Ā ‰*ŠŌq6„ŅY˜āđ­1[аÆģíö(õúW:_× Î׌Ė´˜8qŊڇ­ø:T—Cę|ēֈšŽq;ōrŅY˜š˛~7Ey`”_Ë×é~­ĘŅ hR5kŖ˙CĘOOūÔä‡×R‘Þ’­l‰š zŧŊÛãQ\$‡#öǤšC˜úÁŽHąĩÚ>lÍGaJŊČĩ¤˛ˇ0´iģ“ũ;P/ÜEX4™hÔŲv¯ēڞ§Š¯ĒĨå@‘:cÖ(aĩKĩ3”Ja\N<.WËUcûŊ랎˛%ߍ&÷„{ļq6HLkŠĢ~ŨĨԇޗūDž.y:_‡€F@{¨įëŌrI+h:4Ô\îÃĻÚ>Ŧēđ6ŋΛbsœÆž”øJLnfŠüiö+‚zžNÍé´e@ĐjÅŨPûzPTDĸŽē‚=į“[xl/r`ŌÆ CŨkģ C;é(gk¸AO6›5éÆŦ,å>ãŧąĪHûžKšG9\/(0‡v$ĨåŲRs)3Ģ_uĩ=/jVš2•.D¤ž[–pŲ(a+UAŒRé:°Z.īļē#šT%Ŗ•éĨgV5õ)ęâlín+ŦrÕUŋn |5_Įøy˜7ņEáQ+HČ%Yôk jSõ†đLžŽx6_ĮáyûEážßĄü?áĢųēIXÛišņ yūa돉ãĻqv~ûĢx_ÅB{ß]†ĪĮQZlŊÔ9ÅF˙|žŽÃ“B”¤öQ˜;/.0˛dųí†Ģ•8Ɍ ˙pžÎgžî{0ĖŨÖU¯°Ö_ÆÎ˜ņ÷a,U¤Ą3ë<ķu8pø“ø:˙|cƌ÷áĪâër=dŒ3fü(üq|]ĻYc–,Y˛|”~Ũtž.~*_'‹Wƌ3~ž•¯+úųēLņu››ų:–,Y˛|¨ŧ¯ĢĖķuŒ3ū|üžŽöëvĖ׹dÉōä]|]Ōåë2ec˘ņįāGņuÖ|Qŋ.•†.…­+ũ:æëX˛dųō~žN÷ë`âŧĘÜÁ$næë3füøQ|ųu^˜ÂÖyä×eĖ׹lHņöæŅéīĨyá}ī—“J~gŲØJ/Ëų5zÕ-˙ãø:Øēŗ0t! ä× [N…™¯û°ūŌûĢéĩwAŋôž÷cYō•mŌŊ&áļ˙ŗąą ģå_7GFn˜×~b6…¯‹"ōë2iY>UĻÚ1âŠėˆVš$Ič5æ§Ķ‰i ˇ đéč1HĪs)Ū÷}ÛFä ’ÎāËěˇgōHqņ t$.§ņžt&éFž(aŦJŽ"é:ũ†íÜ,‘Ûē?7\U§Ž{%– ôĩdë}‰iZžF~SĐ-CˇMôZĐĄÆvnŨĢՃ­včÔ]lĪįčâŖ:WÅØūōŦ5­Ō*Ęļ\.ôz˘$-Ĩ7 Wzŋ(ŒKčHnz}ųįō8ėY+”­Û&Ōօļh_'ũ:aë¯ôëŠ |´u•ÎX>UîĒSč|™nqlĐé”És‹ęŖ!{$ô- 9‚­ãP<‹#"}_œ|T dØ:RŧOīKƇ°~úŽ:\iˇĢ"Q5mØįfl”A­žŌŅęķúhõåb  zɑ•+oá{˛2–ĄÛ&tHVŪŦ…ąûz§¯ęÖsĒ/0Ũ×ūtx–]}e ĻÔUĀÔzzJ§*­Q¯úŽ" ‰ míHzŊü45ûîUâxúNː_Į¯c—úų:é׹2w¯sÆōu(ĀY.`5;œ1~ĮMĘšžÎęęĻŠ‘oSķ=”'fŪÃ~Ÿ$ąŋ\, Īqëé뚈Tũ +‡5¸´Kuũ…Z¨xáėU&ĨĢ?0/X<ÆÂ¯{_įËĮN&=_‡JĪåãu˛`,Ÿ(áqN't+Ļlb]Zi0ĄŸ$¯BŧĮpn˜ĘßGntĒŦʁčФ”:]|uËÃx_ũĒō0ô4U‡Ąg’§*OHgŽn)Ĩm/iÔ äfl“–wÔÅÆ2Ā•UK­2Û, åYŪÃePXfģĪ$ĩĢŽ2ļŗ~/]ÛĄŅzŽCõE›ˇĒ:Ō‰Õ´`4–!— bÁ ĢØĐŠŠ¯ÃŒ0 Wt3LžU%)¤—Ģ•˛Õû‚šk–ŋ>žŪ¤ˇä׉%öøē‹$ôˆŨ¤įëÄÜ#üēĘĐi7eų )„.Θ&Ũ@ŧ8ŸJƒî ũ,|ĢfU:Ũž››įšjĮÔ;'JŲ^âÚrMĻ,gí6ĘÚcҤ>ŗc ŦéĖëÕ*­X j˙qVî?ʔ‚S’'/ˇsĶĘ`lŊ Fl,C$),jODbÔSã‡ßjIc›´wQûÛYŋWŖMí ßKnYŠ}Lc_čõE2šÆ2d2ÁLn‘×ZáRõ WŅéķžįŠūmIOŧ_§üĢrVkˇrVË_Éŧ^ÃfyžNēŌ¯ũ{Xá×U×eĩ5füfœˆ‚õ9åaüp,װ֝ųÉŸūMz%Ž×‘fķŋ‡-$žū{X@Yē\ģˡHGnč§riļ˛íˇ—‡åÃĨxfC¸a)š}ûH™$1ÜZäöáz•ËĮNJŋî‘ŋ‡ÍȝŖeÚMģ8“Îgõ1­xŒß„ąŦ ŅAo/ã‡czž] ›‰ÖķąEXúž7&ũģõJüLLøuų=Ŧôččm'ãß_—IŋŽ^ĀŽ(cƌOÅÕ6ģ¯ƒ_wöo}ô넩{•…g˘ņ†eHÕ˙Ģoûđ¸÷×M>ožōëÆƒ1cƌ§â<¯üēGđuøĶų:˛uu‘X˛dÉōąRųu8oĸâë4ŋn_Wũf° úŋŒ3fü,m]BQZü |ŨåfžŽ~-ŒĪ’%K–•ŠŦĶbĖxā|Øĸã×9ãøē,Mk#<2=cƌOÂԈŠä^M/–Ĩųžnęķu§ųõ4Œ3fü"üž.ëČO˜3füã |Ũ”ßÃ2f˘ņGá1|ú=ėžŽ1cƌ? —|]Ÿ_w;_Į˜1cÆ„øēî>ė4žŽ1cƌ?_įĮų_ŨÂ× @Ę´–i-ĶZŌoÍtŦÁÄ(“Z&B&m)C\˸ƒ…Œã6–2îāX…¨–‘QF‘’QG*„‡QK†Œ˙nJMLzRĘ>íjj`t-mh˛AĪ›cĄ!õąĶSŠ.i &†ą™&íņ[ëRf œÕ6Ąa1šöäŠũy-_ך.é1ã\ĮōEJæēl„B—ōu -Yč˛?ČĶk‹s‰Ī]‰ŋRî —ËeXrāđØPęÕ5ŨģŠZÃ;ú_‘ž+ŠģöxÔFkŪÁíņŽY€ļ}pĨJxŨ.]åënxcƌÃ×iī%fžŽ1cÆ_‰ųēŗ2t?_Į˜1ãoÆü|cƌ˙<НĶüēĪąŌŒ3f<Oāë2æë3fü­x4_7í|XˇžĢųrœf×ęËøíX„Ō™įęØg”ųÕ¸2etc_ĻŪtžŽŠUÅûDz|8Ÿ ųÔt|;0~5–Š÷Ŋ7ž‹Q:ö'Į×ø€ūMåi‰ãuc4_—Nãë¤ųUĨú3’BuLۈv`ürŊŖˆ¯“ŖtėŽ¯›[é|đčŌø:: ûR?ŋ]čkˇ0Ę`YÖl6ûąėŸ•ûŗvōäĮÃųŧŖR¨ÚøĶ‹ŋwīmzōT›4žū*ŽZ)ŠOâëĸ(jęÛPXmļŽŗÅæTĘŧ‹÷~˛:D#3|j  %ŽãlÜéEŒ_ˆSô‹ōvž7\ÕąIãK…sã„Ëe´XDļ;Î9ΟPƒW„ē•Č ŨÉב_'åxžîu†ÔúoKûđzŊ^ÚöÁvģŸųļü(l9Ár įķ*,9 ÃI\ã—ā4ŦīūžFŽ'×ËĢ_LŽŽM_„SĪ ,+Z¯Ōũ>;2×Möûhĩ fŗĖķŪ=ĻnÁu+ãΉ¯xĪÉ |B¤Š\zŨŅÃá°ZoŦš=_íėCē÷3”ďEaĸôė¸ébŸ,Éz įķŒjĄj˜XĢfü¯æĮÂH‡įrw_#ˇÅjˇ;ËÕV؟Ņąlâø:—†n–ėwéņÚv0Ÿ‡ķyŧYÃÜ%ģ­˙û s÷â:ŪÉ¯C+ĨwŸĢûu“ø:ŠraY žˇˆčōpØÃÖÍŦlŨę˜ĸü‡ ƒqÃĮ‹‹ŧ¸Āoa_ŗŲėjĖøo‡åyâlÂøu85øu7ČLēŖIļ=kg?k¨ÍfcYÖ%ĤҎÖlĻpžįøÖuŨûulęøB}|xtŽƒĢgYÉé”Ã8Äq4ŸGëuŧŨF›ĸæŲ@>¨Ę?—aģŨĸ÷´đ°ÔÛmT+[a=‰¯ĶUîŦģ&L~Ũlž\Ŧ÷^$Ėõ),ėcŠĪúDĻ/†­ëËšÖŠ1ũ÷íĻÕœ;r6aü:ŦüēËØ>u=žÚŠGNËõnīn˜xQ˛?+įЗOš$‹ÅÂs]zA‘įy‹Å\<ņĸMŠgņ IēZ­PļG騤ņ+,æÉv C‡ęI˛Åö2v60Ķ0wáf.Ąãô僖Õôž ͎õrāÍ1ēÁ¯ŖųÆmŗ^Ķkĩôo1ƒžŠ Lj’?c6aü^žn@ÂĐ-×ûƒŦļGü!‹ÖíŅ;xáŅ wGnoã8éËCūt:é1øWØŌ.Kø'(´‹ž…9"†ulęøō—ËpeûËEr<˛’Ņr™næweüÅ<°—ÁrŲ—UĶømļmc|-q—0Tã ,†,$PJ„ũ~'"įsåßĸ:åđÜlō<ĶÛmB+=‚¯S Øģø:Ŋx&\ųudëōõ)Ŗãæ(ƒōßÜ9F}ų ­ZņƝÛívž¤^Ņæ0Ē­ôčDÂēn­ĸWËĖ|ŨgbÅ׍ėG{ŊƒYƒAÃĮŪė÷‡Ķrå8û“ˆ9ųøją* ]_>ËY5ER<ʀHåŸxž‹Õ${u,ŒŅąlâø‚;ĀÖÍ­Ŧüēåæ°9å7‡S Âø8§¸/4Žx>Rū!LiHŅČËåBķë šA䷒ĸY,†ËyÛlÂøu¸æëŽ÷Ŗh‡ĶbŊÛėOÎŪ…e[ŽÍî@Öo{pįļEņp>Õ¤YĮ“R•îl†A õÆL:Ļ<#ulęøráeÍ~ŊŲ¯ā薋xn%ŗY2ˇ„ĄÃ˛4M$øũqK–ːhÃ÷ÂøŌ]ˆĒ1 …—åHl\‹%.' ŦĀÄQĩۘzĘ×)Cy3_§Ī,Ģ}ØĨŊ9lŊÂņ ȝ_$9V¯…Ё­ëËA5r7ƒAšAKŌtoëůĻĸW%ķuŠ;~ŨUšŨ0q{ŸíÁsđŲ{Ģíano¤Ąģ’ƒîבD–‹EKģ°@dŨčR]ÕąIãËŗmwöã[ŋá|YŋÉĪO:û=ũū¤rŊ“ē§Đúõa ĨWfĖA9 Ũo“$ŲnxXœÖԐ6ĩ‘8kĢō[›$ÛŖr\+=ž¯ËnäëĒĄčÃä×ÍæËõÎŨú°rgēStöâ3aHÄoŨ¤/}ĄøY5Š„1=šX˛mŋn ĖSgÆīāëŽ÷ŖÂ‹Õfŗ‡Ą ļĮpŗ;ÍíuFcŽŨ툯Ģ㉯#LŧS!i@EŲŨ¯cSĮ—ī8pęܟŸpūũü$šĄÜŨbūūxŗŸĀqúōq‡ļnūļmŖĘÂ& ŋŽŦ¯Ų¯[Ö~Ę&˛Zā×ņ*Ÿázåc'åëtŋîžN˛“_7_؛{.{ų9E—c(€Œ9CîÜ´/ŸĻ_Wh1Å~ˇŨ”įqi¤q=ƒ$‡€ū’ÂP9o›Mŋ‘¯ģĒ{ĨßĩŨc{đâŨ)˜ÛĢ ŒF^‹;bë”Â*ˇ! Í?) ~›õē?R¸WĮ˛‰ã í8›Á¯ ~~bë÷øûXēϧSŧ˜ĮŗßĀú=YVŪY`* į n›īûtíÃRÄĶoŲ‡ŊF ü:Á×’ŽKåH)å_XQT°ÛnÃmõq|]mŒÍ˜ü: ļn/lŨ!ö ļN‰Iîŧ´/åÅŠxŅ\ o0+‰}ĸÅÂ÷ŊVzĖ ~ĩ[ņŊå$|.ĻÍ&Œ_‡+žnL?ļđÚŲ-7Ž0tA8éÚ$Žîķu¤]Ä;U‰=ĪĨá|§ŽM_ąį~`Ķĸ_aîÂŲolÍë7ũũ Éúšîp>q­×ëšŦ&ŧY”â\VʛËMX˜Ŧr Ŗ9.„ŧŖÍYڏ(ÄđĖĘáš\"+cģõáÚ¯{ _—ŨË×UĨR…ėbōëËÕz{:JwΕ?jDI€)ļn8Ÿ°š‰Ļ\Ë|Ũ§â–_7Q\ĪÕfÄãtė&<š¯'ĖŨAzwLÜ|–Zŋņ\ü{´Ŧ¸22ŸQ÷ŅøCø:ŲM}ŨȝÃvŊ=îĨ㖟/qVûubaëe×ķš,mŌhåÂ|ŨGâ”|Ąķ8ÅûØ0ŦcSĮ— ōžãœ–K=˜¸#ü+Įĸ|g VŠĖ× ī.Â×ÅqLOŨˆŲ§¨Y#áØ+ vŊßȧˆËŽOŲÁOųčû8-9|ß֎ĪÕrĒU–Ŋ‰|Åp;0~5NSôKāûĒŗFõé­¸OëFęŪÍ:–M_wâ§ÔņnL˙ĸ¯Įŋqč|œy0+ĸŋ$}ņgĒˌ 7å÷×}$Fŋ wĐGßĢxctė¯Ž¯ņAĩRõēÎëēņ$žNö†Đ:t‡ëē(•”îˇcßķ°|˜ô6TÆ/Æčô‘ī{ĸ3OŅą?:žÆcôo…“Ūū žŽÄō}øđ0ãHúķíË öč>‹÷ö&ņ‡čĖŗtė/ޝņũ›Žö貧ņu-ná”˙qxBŨŋŽÎĻĖ/ĮŲä§ žÄ×1f˘ņGágņuŒ3füIø‰|cƌ ŪHŋŽl‰¯Ëîäë3fĖø0ķuŒ3ūđ˜ķ&ȝcžŽ1cÆß‹É¯ ĮœË|cƌŋ;_ĻŊ|ywĖ×1fĖø{ąđë øugæë3fü‡ąôë.=~ķuŒ3ū#¸ôëŌúÃ|cƌ˙vvƝ+—ąį>ž.+NŽsīŽ%K–¯—›nünPɃ:fY}\?„­Ģœē‹Îם›~]˜dIVĀâĨų=ō\âŧČ$2˙\™Ā7ËôMø=˛ę÷ZŪ.3N˛ü8›×äÕ1Rœ…*—R—\Čs^ã˙Męuo´ @Z\’üŌYÃÚō¨ ?LrÅ×éܝ¯ũNÖkāZRú™ųô‰kÄyĮ—ÁŨ¸Ov͎¤/k§c_Ģī§âŠr8ΞĶ=‰ûuào}:ö6°&“ą‰ÂSĨWĘūąwGŠA’ãb-–‘šQv‰2’˙û‡ZcXÆŲŲ÷ēÍfīsĮc”*RæąĄØíĐ#k3ppŋKšĖxKzá˙|Ŧß÷ž*õ)Ŧ‹+Š d]ŽM#ĨĢ™/W™‘Đ€Ÿ(Čdî\i†T7zÕÔĐhseîRąą+W™ģ˙ũŖˇCËÄŅŌ•$ü=˜¸õz-N7[,<ߏĶ|Ül›ĩ:¨6q=˛×ŦÕÆęƒŧģ1r¤w7Ũ#ú.īn¸^ Ŗ:Ŧ!Ûģķ4g,UßH ē†cšāqū˙ʨĶ2ƒ•ÄzÖķ,`Į9ö3˂šƒw'ŗˇ*R=kO÷îĖŪŅwxwÍrö ųæĀ§÷<īÎŋÍģëŸ:īöîjköqŪÁ^'‚šæŽ|,ļÎ`pŪkâôvčø{2Y&Î(„Yŗäyžûũūt:AnˇÛųbšÜŨų|)ΗŅō<*åeŋJžŸ€ĮĘ1íđˇņíōü<\<Ÿ'âkc§Ų†g]VŸËWÉķËą/Ü9Ø4› Ũņx„­ƒ†w'Îû^,æķÅ\îΞdÉōũrĄËĨx&ļ–6Ë ƒF†îp8ĀĐũ¤vBu endstream endobj 22 0 obj <>/ExtGState<>/XObject<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/MediaBox[ 0 0 595.32 841.92] /Contents 23 0 R/Group<>/Tabs/S/StructParents 1>> endobj 23 0 obj <> stream xœĩVÛjÛ@}7čæQ*hŗŗ­TŒĀąĐB Ĩ.}(}0ļ|Ąž$–Cčßwf%ߛ[ë’•ÎÎĖ9svV.nĄŲŧ¸ię€Ės¸ė´á>hH!ų'M‚›YĄ¤EĻ`Uī`4.{AãâJĸzŖ ÁŅP&"U`'¨BoNq×_ŒK* c˙–Öo×AãGŅOč}]Ēø%h@÷Ļ °' Ī/ÍĄœ—æŊRˆú7!æ!FŠT iJ‰i›&-m‰RI´2OšRgŠD—T+ļx;GMaŽ“Į–Ãŗœ^ĨÔ3™§„`×gĐkĩ)JÛ¨ęP•Vísg}XUĨ•ĮL`[9šĒáÆ)ļãĩš*„ŅŦĩáåø\’WųyvTēb=f͇Á(ķXív¤ōęĢNjBU31Æ]ĶŗļŠO—ļģ•ÉUĩnJiÍĸ),æV’Ģü`Îpā­°øä{#É#LŽL&s÷į€ĩ[´ŊōĮÎÁYŨ}&­lģíQlØē’˜ģYB•ū_Ũ?wī”:;šwådų0FIHĩĸX…åÃ,˛áĸX‡ĶE„´Q.įQk†&‘‡m8Ž ŨŲ,œM ųUđė"ĘUø1:w+*ąõS­ŧô Ņg˙–Ą’B§/ 9 _&´y%ßIãæüĶ’™!ŊI> stream xœė+˜Ģ:×Įˇ<ō•ÛŽY9väČĘÚĘʑĩ•••ĩH$‹D"ąH$rž\H˛n Ąíė=į|åÉŗöڙR.á—?‹ĐļmUUeY^.—ãņøōēyŨŧŊûôŸūĶú?Æß(ĢĶË̞ŋĩ}5Vežú|d]˛ëú:Šĩe|=¯x]h74į…ŦÅÛ#ÖšA­úŽũ&´PØ?¯ĢÚŦ¸ųģž{[¤éXŖ–<˛ÍŠséO˙ŽĨ6Ãķų7´9o[ĖÆÎÛčõ%ŽûJĘÃu÷ÉâīBå“mŽŲ…:ŋĩoyúĐ?§ĶézŊ*ŠŽĖRÅų|ļŽE9}éåi˙Š˛}šĻ¤“īö‚åm[ØWO?ãüáûö{í¤­I“ļŖ˛Ķ`ŦņŋŒoķfk×M×?’úįĩŦÚ€|°"(ŗh­Ėsõ6_ØopŸdīŸĖúÅßõMŋå!ÛĘŠ3]~˙šlã[_Ũ_el1Zgĸmi˙QíäĮžŸ)?vmNˆ’ëú´Ĩߞ_įšķGˇųļuũ>ĪäŠũ~˙õčårš<ļÂëįáąŪŗė>Ū•Ŋ^ ;x9Šĸ ŗ(į÷ËËĩ(ÔžíG:e'ãOČ˙;vD9Čī|÷™í"~~ųui~;Ŧ°í’míxĢS/ÛÚÛ^ŲÉښØNļÕ:;ykôoNe; ũ ۍŧ*ßZ’Đ^rû3ęßæŗ–ü(K’x$#đ;Ų_:ÆĸŽu¯ŌĨę.U{.›sŠlĢũĒĩ™:•Öīlaĩ–Jju[H_ž~[š­ŋ˜×ŌY×:(™ŋę2ļ¤ą,õķ_[֒°÷Ė^÷m†ŗŨ—?øwu:U:™=Ŗ"ļ˛ë¯Ü~ˆūŽtk×ü–Įl ÔIöŽyôg,ŠALąk„œÃ¤/rųč\ō%iũ /Šú|ëļļPg¨YŧÆ] įVakë™s*˜ojŽYÍB_‡ˇUÃméß>@ĢSãl¸Žœ˙ęËãVÍûĶ­[ĢĩzģŽJ•)P‡ũLî†ēUļmĩųÕĩidŨųĩ‚­gĢî­ĐÚÚĖ_{_ftIûĄŧģ/››oˇ5ڑ”AmC•Zƒ6-đÆ q}ˇ?Œf¤0ĖEĖĪĩ*].W;XģŽ`ÍárØO}?TÕPWʎΎÖÖÕ¤RU}įËų_Āwų%õĢŲˇ5x;‚úũÕÖĮŽSD­š4˜_Zåīß/ ĒÕ Â¨ĶEÚcGmÚuĀĖũ1šŸÕ6‡ĩ=F\”‡mĒy=ĖŪĖķ6S‹°y‚Ā,Ãsčô&וaÛ§üÛ¸•”I›W¤.ø¤žJB빓˙yā ņuE?39 q0 !ôâąK—Y<î–r-–nN…‡ęÎBĩlÕWÕ%cTöžėpē‹A5IÃ$6iŽÁÕ#ā}؟pßĻ}‡Đ¯ĒÁa‚ŲfŸØũSĄũ~Ŗß32ŌĶú ö„ßų]ŧž˙žm-’í%>äá=ogâšu5Āž ëā0Áã_~īŖ2{R?AßXzėpQāÆP§;"Žæ1§ĩõ nk ģNģĢfžËųĒėá@•B5ōųTkuå\‚ępO ÷G„ĶĀiįCĖhšÔ3Îå[ÕĩĮc‡ĶŧíZ’å¤˙:TĀǜC5AëHšnwûÉāëcĮe_ņäų|ŅĖ™*?Eü€Ķ@ôëa¯Pv¨ĘĄ,­õibéKĘL§ŅYėVĒy[ķvģnûމÚōžj°‚jĢT~~žžnôŸˆ:ũPĨz¸ÉÆÖÍTļ—‡ąß¸”Ÿ@÷u: Ēq>ę4N×ď‚ôČ}Üņ†ōUGũŒn’nn[§Ŗxų‡#M °ąŽšĄ ˡƒ?ÕâS€/4P]1[/Ũ”oHđ2pZ:ë@Z‘F­Ö¨=4Ę5WÅIũ8MÎē¸:mkˆ(Ūō­ÄÎ>Vü.ķ‹€zߨvJ5i•\ŋÛ ‡vøÛsëɹߡ­šG2ļIAĩ€Ķy×ZĻ?;˜tį98—(N“k6ŽN3¨†×Ņ ô~d-P1&ë†+ëÉXīĨ-äę´°-¯'sëuépõAsæ8Ũšļaœļ)(Õúw!ÆwIpŋp5S<Uk[Ėqj3´ĒN j6MMī}_[ĀiŦQĨšŗGxŖ3õ6hԏQĒ5Q_.^Ŗ2ÖJ—Ņõ~7ö}_šl5 [ô-&g§˛0 mlž?a;ē:}ũØîØļVŖöĸĢjäfķļŨn7›Íĩ(zIš]ĨNËĒ2H đlËä—OĢÜw'jE´ŽįG=ĸQ‹JĩŦ<÷?ĸH;k€ŲwøĸƐĖēÁ'ÖÃđ;˙õ6ÔĒ x$~ŧtŋ>  šá"Uß(ũ8*DVŧ#K7€+ļXŅĀ p‘bz`SđσŦô |>d(ˇ~Īc+eZ˛ ëbœ–4jūl4°Ą„@å~Ë|9wFqŧ.ÖĸÃYJB}Ûø×šļ.Ô9ßĐŪgĢĶYJu@P‹Ķķäb ŗ•jŊ—D@Åī[ČÕ]ûģĸ$p°7dxĐļöÁi4įc—5đIĸ5Ü˙DeõĀ ‘ĩhpHרŗŸ _::íwđĄBÍ´Ī k•¨ÍÅl<ä5/Čx[˜Ũ~đlKg¯ÍÂÄ/ÍÖC5 ׹íˇjļ)YŲÔēu ›b$„ÃëÛōq Šŗƒ/Sá2Õ<qžm‰ą•ŗž ŅŽ xÃ$ašûë\g_ģ’ĒYøW§ĄF=j‚š ƒiš˜ųëŦĒítžL“PÃųRėv‡íî ívĪũËĩčŊjí’bjEÔCÛöŠW‹ëāė lŠ­Ļ_g'g'c/ĮŖ¯_ų!Įl×ä\'°–­gpÖÖ?€í*ĸŪžŊyZYEƒ ¤Nŋn66ŪÃfŪŦT#0fž};?Ė0_HÉu´f8-')f[ļŖXŋŨ‡Ķ"T#xžÍp?…Ķî!ŅäGĩü!éˆāU€ĩBėˇ`œĄ:ō„{¤ņÉà Pąĸˆ˛#ëģĮ ĖK¨ âØrŽ ÷čū›.#¨Į¨ \ņpƒ ø'rėČđƒtHŧNf9 ĐĮĩ`đ’ōPxÆĒ¯x-‘Z@ĩkFĩŊíÆâ¨“5Äi öáŦ íąšs-ė` Å5ŨžxPÁÍqÔāé–Ė ]Uŋ08íö’ũ92šâúCČØc؈f´Y˛ßĩ­TœšđlBŧ$qC5ÆiŒĩr×^; ôæ¨ĶĒá*„F4Ž÷“:(: mpŧ7R]›GŌæfZ ˇÅ”dn;wÄģ§ƒ•jM ë´=†ž›Ģžg¨ļkuŪ+=[ûfå~{(€Ųļ/ßûT8Bu…´+9—wĀ<`ä†ez#\M¯ĨlōiÉwqÔ Œ‡Û}UÛŲ5Ėī¯ 6ũԟ]^§ĖŪmĮŽë¯Å´ŪjÖuV“°ĩÅe2žæä⡨úķHrlIoGWÛę‡ÛšÆu"Ôí[ŠĒÎŖā u:Ĩft#C5AâPfå' ĄGjÃvBOÚĒ8Œšx}NháÄۜHŅIŌ{Ԍû õãA;x„Éãqđ{öÂ,b´\d5“ŽÅuŗüÆû>& Џž uŨT‰Ô˜eåĨ‘ŖTRUÆZ4˜ŠĖŦ"uŅú]¸ׂVSm̝č‘ÁKA×Í(­cíČojØĸĄÆZūØĸ8ú-Ž•#õ3Tî Š—=:"ôU¸3 j09S}ČĮqÔđ5É6DŨã_āĘ' ö›}@…U€CSlĢT‡Ŗ;‹Ŗ6ûSī7͌sQ;Cu´=Aâb\īáõhhØĪ\7Î2Vo×vĩd!(͈ŖF 9ę7æŖ,žšŒuuÔį{dÜš‚g°U<Žšl+ÜĄ$;ã4Ŋĩ~ :¯T_g¸íũ#ËĐĸį +‰° jqÚ5 8i ¯¨AÚĄ¸ĸhmCyÕžšõ/'FB>bJuë⨃IbÛ*ˇČ*C_Õv:ŸĮšÎA˙i.3x ŨŲ``ŊF Ë[ĢÃEļīCÛ´į“&[gņZ۟Oš„uP}ĩRØĖĩq‡Ķs™ÁŲÁÕփú;°ŨžŽļ› ŒŖVV-öŖŗF=ĨZPĖ< ƒžqiQ?UAĩ¤ģ8œ<ûXâĮaÖė€9ØABh6ہ…å{ iDųHņö§Yėœ4ZæÄž­}­ ¯ā=!ĄĮ@36W0v 0üčŧFā™(äe+jũw=f<%—øđ`Ē ķ i(uÃ6xéŅ­ <č ß i&9ÖëKkŗá‘z Î~Ћ¤ÄxI@îÂΟĒķ1ŌPéĩįĒĶ{A°GÁ4j)Ž:Õ$ŽēđēH?häqúÔō€÷E­cOÚÖ>Į\w>n™Bėũqԕĸsä9€”îÚûĮęĸ ÅØf†tcō Bü]T7^2Ģë’F ƒĶÂ32€!”¨@Ŗ^GM†!æ}ā‡G‡@Š!ųH?CÚŧ¨ĸÃáŒíÖH_'õ36ō?(ÕôeÃđ--ÄiۙøkĐ*Q§û°fbu˛ŠˇGtОŪnëŽLËÚ ڃ4Đ´}y€â´>8DŌ¨uŒh–Fũe5jES ’5´tÆĘūékúxRD=į„2ÃvģŸĖĸ€ūUĖīÜ_ļ?Ū‡ĻnOG“NĘjžÕö¨l>ö§ãāŦI§át¯˜ųrĐĐ>šEųįÃAseŦokčõõwv‹ÆöUõņšÔ¨ĒQ+ĸVŨī÷[°|˜åũũũííMY…ĶMĶ({š–B,tsųõë×?˙üķë׎čgĩy”j>= *#”8  z h]ŸÕv?ŠčŌcߖĒ=§ÆÁķ8¯5€u\ĪМTûOMˇęœGjôô Z'<é cŠ*¯ųK2<„b/q¸ÎÄBˆŋŋ×čXx‹YĐĄ˜;Ž=2Āîāz°ŽW1ú–Ÿ@y„Įä1:Ā-]Øâ9 ĀÃ>WAĄu {āņ5ėsŒ‡3øŽßÃ_Q:xŸÎ—ĮÛ%uŠčN‡ūF n—á\Š(Eđč‡Fƚú8؀AuJÔ:œæs}ÜG̓§-$„ j_8äƒC5 ąhüŲ螕Āö,ĄÚZßk€ņ`ķ*7:Ņ-(Ŋkb›oÖ¨ŋo[+ã¨KęiĨځ7ēúg1%Ö¨Ãų90ÃÆ÷&Â'ūLĩđ\ ô ÁiL’Gíļ…ī\Ėš0°čåÄp—Dƒ_ŽĩÅũų3D^ftĪp×ItoūAtlŒĶ]:¨Ķ1œ–•jđ#ųĀŗ‡IĶķÂ8jÅ0Z ¯[bŸ'ĸ$ûy<ķĩšŽW+*@Bų&é¸eŗ¨u}&Íå­¯āęøōŋž,ˏ÷J§ˇzĢe돷fûŪ|ŧĩÛ÷vûÖmß;g{`ĪģŨÖmÂnEå2ĢAŲÖÔؘúíļ*gÛËųũ÷˙Ė{—abˇ¯dõųZŒ×‹bik TOúƒeúÃ>Ѝ)N÷•Â×SãüS˜jÔS€äæüëךhÍâŽ1BģÔAۏ}}úĨ—SŲĪ9ęOõõCįkøĸOZ¨l7ÅöׇęœĀŖŪâô<†R¤ŽœÆûö ŌÔĨ!B͏ŠpYõūRZLÃ2¯KĢtēVįkĨ­ō͍ž(ÕHy†°TzX ÚãEÚ•CQŅ5ī„P„*~1PQ8›ÄlÕ$ød~eūÕ`¸ŅŌ=,tõDIâũū„5_Y×T_6xaá: ŗqÛĸÚ5 Ū`H/ŠĐ°ũū(û$ėqŧÖ&UΚTø4 č‹ÎJÃ'üšq2įOf5Õ¨cqÔ0TIĀiÍ+ÅuË!Hļg mO…UĐĩJ5R§c¸ ŋá-ņõČę+Ņ`á>‰Õļv8đØmqĨÚ×”|ô„.˛Ojj…!€ŒĻ>4núBOF€˛:a‡ęšŠž‘š>tŧY†ôũi8fē ęē Qã0Ąæ…ŠA'2×’‚P@ĐÚ÷¨á‰'x2 ąŸŦEûss‡ ƒĸC>œE9‘0— Nãz6cœFq×ĒNweMŖŠE´F5ŽŖnØlc¨F­¸Ĩ0Kl΋„ēdÖĒZEÔûÃQq”Ī1ļQVŋx8ŒÃ0ĐY>ļ{ŸoK:Ģ×mÚ^uW\Ë÷ˇō}cė›âęę}Ŗ‰÷}Ŗé÷}Ķ*ŽūØtÎvΞ5ŊÎõģeT9*ŋ%ÕZ­ŗÍ‡Žŗ6õWf[ĨŗŠ¨ßū÷úHÅQģ˜ɖ¨=HsĨZŖxÛz%ÍÆ—ÄB\sŅ\…n­síž’ÃĘOĒGÕ­Q+|vų}}tYy‚éAö Q—5‚mÕķã‰CõŦZ7öŨ[ Õŗ:^+€8]āč)?JåąX,žĢ#Åéĸ"P ”j!œ)o ģEV ôĩ,„@}€ŲVĩ탐~‚ä P]btA~ūøA"x}dŦÉąÖ ÷„˛"ĶØ?ĸs{‚Ú•˙Ō?Đ Z ‚mĄHã›Q<ŠëLæŠÂû :(T†Ée¤T;Ȥ1đäÜŖĮAõ}ķQ—TÈS’`Lĩ02 Wƒņ“ BnŒŖN|El OI’č×x9´—(æ|){…Fũmۂ¸ž4õâ>ŠšĄž­€ž’HJ5}-ņ–8j׆ĖĒ4đų¨%ŸĐZȑ_ÆA´ĮQãų¨´’ĩ}WĪú('=ų’Â{ôY›ë û€Ķč=zGŸÂp¨?j9ƒdææ –<‰Š4 𐒟DÖ¨ãę4ŒŖVčbØŖ%v "%ÄEp(oĄÚjÔGÅHoĸ÷}āZčÛeFt _ē0Xûîpú­ū0ĩIZXvV ÎÎZuÚ×ŋūŦTƒō#¤Ú$Ŋ9Ŗ+۞FíæúĀqÔZŖ†Ņ ļ€Õ¨6[xļíqÚfĒЍĄF ôäAč¯mŨúŽöĸhš™ņ¸;Y[#´YļjTãü_ ]5>ëü_Õ˙ĩîŊ=æÜcåÉy{m-$@Ŗ>]ƒĒ?N§wUĮ~ŋĩa Įãûį§ŽúPí1D]đŲ9ÆiTHûĪ?˙(r6õPnm5*ŪV]_m˙ŲרuųZģŠGŖĩZqčĒ­ĄmEך~u„ļ†áę¨éŧƒbĩ&ęsĨ§l̜ūj´%ûîēũGĄuīAڀwy´ĀŦĘ7G×™’ÚémĐČVŨŖÅlSų(ŽŋūųPWÕāV÷õh?TģĮ4` ƒ=(Tcœ–0…N†…| …™>vĮqÔ)ːf1Ė%U§ėĪĒ Ė^Ž´Ŧ–㔆¨…Wôō‘ŧĮ Ü â˜eę4ĀQôWb%ÅkŅYqÔušß¤ĖĐĀ!4ĨŘ ;xʏ>‚⍁(÷pö!žŋ¨ˆĄzÖ¨ĪĄsâ¨Åo& P tEĀ­‘jĘf­!ė[‚ĶwĢĶÂËq d"<—ÉÃéŧ8ä īZwžFũømÅĄ×ŧô!ŠTÚĪO>ē$Aĩ˜‘R§šLtōåAģ'ģ TŖ™Ž‚%3 Í5×ŦfG-ESÃOŊ@Œ—fŧ‡¨ Āõi°úJ8-īcŋ}W€ŅZšV4čΰŋ ˆN?øÂrĀLPĩėpR‚šųø0pFë :žpuGl^5]kŽŖîĮYŖļųÆ?ž.‰Ú”U\uX×hÔŋûĒŦvÕnĢlŊÛÖÎ6ûmŗûh÷íîŖÛk§éŧßųú•orö g ˇûšeSs ļeˇŪ^/0ŽēgqÔ$äÃkÔ›ÍæããũpØė÷oģŨæ|~S˙=Ÿ7ŸŸ*ŊĒáIQVŖ&߁€ö4kÎ_Fju kZļ9 ’?L˜Į Õ!Žē=ũÂËŠĩąŲĮzrĖÂĩúëdaÛŧ“¨cĒ+ĢQˇåû¯EÚs4uSXđ6umß7t~gQÅQˇēüĨqSģ4…ĒMũ×hÔuãÔéÕķĮC5TģŠÚɋ‡ĒáËDy†1Ŋ¤…WÉ#´fšmˆ.–^# !!!všĀ÷ZĮ€™)Û°QČG™nŗ!‚Ôŋj‡âÆ î&-z1^*Æ>™ū,(Ā$äCP¤S؜˛@oAkņ­Ē@3{á@ŅøjK|Fh-ĮɃ'8˛„%\áįŗiȇĒ~Ĩz)Ž(Õ’C8-ˆëÕXĩ1ępHá7@˙¤‘ēw+Õæ…>)đ#„АV%•“А¤Ûs]÷–8ęŖ&5ŗ˜yxž„ōt?Į”jy°Fë° Õ`ŒŠ)uZŒŖf8íjÃ0kĻk‘Ī—#xŽ`ļ8XšņŖfÛà â3åGPãI(ĀŽ÷3Ø?xrQˆú\ÁfuąBr<ąę?Îė6Dhō9˜:jûIÁāLÔā}.əļrŧ˙ÔqÔ$•¯ŊßrŋÂu*Ļ:ŊžtЍģJQî~§RsPvĢŦJ­ļÛV;Ûîs×9Û3æđ2ĘļĻ_§ŪŠųoĩ׎j@Wo&ę~;ÛkÔ:dĸ_K´õÛÛFôvĢ“röûÍéôz8¨´9^4K_.3QķČį/;íbĒĮĄÖ:o[kÉŲÍūĄūĒįŲĒķ ”´a!-žåCkÔÛĸ3qÔV…\„ŗĒÕEV›ŋžÍ_gD?i<ÖķuT&Ö¤ Úĩ‰”îĖģYëļŗy8ŋwõdˇŌøō­‹˛6ĩ)ĸ.OsH‰™ŗQOIæú¨Z“o$Ąņ,|`Uk8×%E#؁Į÷đ4Yåtūgōž>ČöøMv',õ †+›ˇ*ˇ¨=øˇĪ13b1‡ãI?܏—Yå+˛:n^úēŽ?Íņ`mķyhUŠĶiߝ´íí­ĩ)éw§y-SƒNļÚÆmkŪî騖åÖĖžŖfßC´õÛÛī÷÷—ÍFÛ÷÷ß/ĘūŪnu2!4[GíS{ųuî, kĒĩĀlũ_>œÃŗEåūkôĩú˜õj7Cˆ‰ŖVDmTčŗVĄ BˇæÁLĻg@Úūĩ7ÁØ ˜uÉĘĸĩ ą!Ŋ 1¨\9đ%G.ĸgáŗ[iô¤yŗoJj´!%§T›éÖÁ']ØÉįZZƒË61 †—9‚d {)Õv…ŽlĮ|öi <Ŗø+~ŅLBč æ­JÚ#ļ?šĮŋKxMLˆ$„C›‚å…DTŒŖø*ŋ¤ĩeú¨Tn'ē=1͊AĩĐūTLŖđážgG–kcėÁ&@õ•A5˜ëŖž=ŽZÂéyxž`Đō`Ė’¨NŖ3kÂč…PĒÔĩ–Dzˇä7Z¨ö(Ģ$ˆáa:ļÜ64øŽmĨöĪŽĩŦ û9ĸZ Cņ) P§ÅįHIÎPĒ9NcP—õdy­–ėĸK#%ŲM÷— )Y˜@÷ĨŧČ ¤ ÜK4+u—\ųT5ŗ,ßgŠ´‚ûjŋÖœĶ–ņ ‰F\ĪëΟ„ PW§g_ņÕųZž.×Îyž*q8„…0áč‰UĀÔūÕ×ėĘ nB"*ēW§‹B§+đ&Ŗ6ę“0 €đRšë °拝+˛Čj Õ%ØpūH9đļčKŽŪGSīâAāĮ×øąôZĸũ„b?Œģũátž´ú<üˆŪíöxēLĶ×ũõx{~Ûč—âęj¨kkmu›ÚØjŦĢ)aī× ßÖŖŦŽsۊąMŨVUyŊn7¯Ã8ö#͍7 ĩn:m…š>đ§ÃŊ=Ō?ō,Ŧ“}]Ziūjø™rPž|iQ[|ည+īôQrŌŨü ƝÎ}ˆŧ PũeĘ8¨Ļļĸ€ŦûĖ+°]°´A~VNđ!5ÄoQ¯,~Ė-){š‡ˆW˙Xœ*~˛.¸iJà  É€Î–ėቖOEÆPh‚Û刞hXméz>ŋP=TÁĻõ$ʐÛ.lp,H8„„Ķūķ'RĒŅ|ÔĒÛqÔ­á(æ­čûāĖäđĨ]ŒÁœ?‰>-´Įīy0ĀšWŠžĖ‡Qܐh ŋ™ĄēAPí>k"‚ú| †ˇ\'5Ŧ|Įļ„ęÍÖ†xĒģÔ>Oĩšéá9CĪs¨N‡í"@­pdrZŠ×2,ĀgL"ŽĮcüœnE›kÜf!(…J†>% ŗá ˛đrôUøČ TŲ›,āõgÕtč‘{9¨ĪĩŠĄ;üž#~%ŧpŽAŨ|5´a"‚’Xđ€ę Q7L‘ũֈ“—ĸ:O۝~kŲũ ū.d> í__ļ/ŋˇĀîŧ}}ŲmfģĶöuļ6ŊžĻWŲ7õhģĩöõåÃڗ߇Íëeŋ O†w3⨯eĨ–žīģŽKÛĒŽ¯EĨ‘OI-ÄTFN…šŠķŅ:ĮNZ‡˜ķOĨCđŽÛ‘ųÆúD&FŖžm8ÁøŽâ=l÷p>R47i2…’ĄÃ—,¨X1˙[vZ¨M)°‡čNUt ų $[,Æc† h?y(æĮlOs*æWāŨ|č įC˜ų­“ÜėhâííŒ•‰ęŪč\Ę9îÂŽŖ*úÂ QßG ° ÎIN”ęA€:ĢO“Xä?Є1°‘Ŋ÷Ĩ:üēÂ˙:4kŠ›ēp˙°D=P4Ģģ%TwƒßÛ1TĒž,uú›ˇ%î)ÔvØ>—ÔiĄÍ~*KŸáü§ z;J…×oÖ,4 €1RqĮä7I{B€„o-ÂN2dƒ TĮâ¨åŌwIč+ŌdJĪ+~˙"(ü~8 Ī4í'š)ËlÔc„_G•gh;ÜNĐ*ųP?'¯.6›i`'ÄuŒčsË[đFũŠ"Ö¨ÍspˆĶˊJĩŗéë_l'hŋ˜Oõ “Ā{é8ęļW–ōtžž.×´Ŋ\K+Õ%ɨĶf†ęØįÅĶžˆĮkp:à P ōį4ÅAĖėí4j4pvA}°5ķQĄwī ԉĨY7k!ŋ‹øĢĀ;*'™XNNš%4ÍÚ<&AŦMāqHKíĖ:¯Öĩ œoŌšĻ­Äé %Z­5DSŖš>ōâ¨iũ ¤d~a–@5TĨwÂ<$Ņ/~ZqXԄ=T߯N\ą6ė7áێAˇX"CūŒ‘Pũ&GÄ."$]ŒŖ~øļâ >S[z9q?Ŗ:}›;Ôf‡á¸#œöƒĩ"|ÔčŊ9ę´Ÿ6A#~ž@õ!"T;ē$"_GU\0“ŒŋeûÛ ‘"]:ĖÅÍ@:LHņuŋ÷ėLųĢØ|)—ˆFÚƒû ņUAēŽbõäØīŨ–¸äXwØûEö9Hē:Ō¨‡†#ķŪ[ļöE?UaP—â¨]đGÃũ+„fw‘nÍŦ5bŨŋu‚fÛĀCđr"üÎc,ŽÚĩ0l )Ō‚›_Vƒ-ōÉK¯āSā0Ā*ķL—r9W ųd§øûhS]á4ûÜ9ųēˇGŨËuÚō„ķ'ąŒË˙6ÛŖœŽZ†Ķ HÎã[ŲXG}'ÉÔ$J2Äl‚ōĶūa8žē¤Ÿš ]ƒ4:]Đ`b~§ąē…ô–0ķ|áãÎi{§ĘņŖå!Pĩ)´Ļû'Õ1˜ü“āú-6cā Ė0€ĪˇŸoō°‹АČ,dúŽ8ęÄâ¨É6QČ™Ã܅ ķ¨ûųÕqā[†×ŨŨJ5ŧ.°FíÃZRqÔ īˆmęÉܲ­Ė˜íė8jü+VÄQûs;LD ^Âå1Éđ5^€Ülh/\w\Hņm€qÔ`ÎĸÜ8j–(ė!1sQâ…Já>5Åã¨áäĨÔŋB´æ_g€­ĸ!Í_Ņd Pĩ!äÃáũ’B&˙ŒātÃpÚa6nsįƒ=Žķ¯­ĶÕU˛ĪļęTã—àÉjÔ5Ô¨{ĀgåËJõ6ĸZ ęt“ĄKGʈ€Ŋ TßÃē ĨÅQczī#ž¨NĮ,…Ûĩ=ÉëFÕæ¸ hgú7ŽÚÉ>Q¨é&ŽŲ@…€~BˆîrqGŗķŨø˛€<æ¯H\ÕŧŅŤwĶpŌû[3‚î0:˂ī-Ūb§ĩ>TĄĄMį#Ĩz}Lõ˛jS”â¨g)Įėãûé^Äé6C器ĐB€—fö10ī`c‰(Zu둨1?ŠŲŽ;Ĩ~{2ę˙ ¨N¤Ÿ€ĩ?ĶÆnŲäöKb@Ŗ.NW$ŽGA 0ƒąSž—N,lĨhöøÕNw+ĮsĄ ĀÆŽA¸ßÖúĒųÜėTéuíÁđ û BDĨØF‚Xbõäø™ÛšVo 0ū)鐛¯ŠˇšڌĪķp™ŸIĪëķå-;ü—5Ģ™_§sy|ǍāƒÖ 5/ö •WĒųˇŖ6§ ŸjÂx?OáWc´fõ, +Ā>ĄŸŒá-DŗRw  >ņj|ø0 ™ãēÅs\ƒĪģŦÂévŲNĖŋ%$ũĸb†ĪAZ ˙øę"zõ ÕúZŒŖ’~ÜöŪJ´˙(n‹û 3š€Ũž˜I†§Ü6ķ‘;ė|VJÍ´䨌ąYēár0ųrģ‘xfxSKûJø#&°ņv_k&3|&rÖŨ¨N;ė /Z––_KœõáNœ@Ą5UĒj2Ş q×C(^@ƒ‹{ŌüŅŋ™j’ 3á^â×iŽP ļ3hŧŌ> õDÕoŠëüÚDGų.Ĩ ¤#H÷X^Íüģ™õĮŽ÷ŗöOŦ čy|ŸŗúZ ĶĨ:č]Ŧ“ļŋux‰ž_ā‡=Ngˇļ0ÔÁøŠY\f “ûQ•Î˙ڏįö÷CĐ’\ȧ_¤q=ņ¯1ĸ¯ P¨.yˆFqä> |ū’Ô¨ãļYg'æ‡Ô2˙Av\ČOĒÖ@¯ļß YĻÄÕH˛Ņ8ęņGĖ+x§Ą?…Á—ŌĀüLk_ĸ\ž™$2ŅßB~ļí“>ĩcŌŋɚ4eûĶũūZËęAm§R˚°ņé'ĪÉÄ9–:7Ėg˜ô,ĒoėĮnP=ęØõÆĖÚ¤{ŅQ—wŸpbĮ—Ô?ü´|ÎôŌēøhüüŠsÅø÷ĖP$ļSø]ˇļ„_ûßö[„)O_§t\rļ•ę3—ŽõŌ>\ŨĪdüöœ{šFûØvĘ[áûđÆ/îõŧæôũq]ũéã›*?ÄJēžda˙ŸūŽÄūįŦ2ū›}Îl4Ū{ a ĩŖÔ­ eĩ;÷‡ŖŗŸ;ãīžūĶ˙fņsĘüŋņ˙F­Ĩŋ}¯ėįvoĶAĨļú]ûOëCĢōwϤ^q˙ÉÎVØ Î‰ V~ĪėBßbsÛyk{ø>ųžß%˙–øĩQOē΅ú3|žObû<˙¸Ŧđsˇ›{>Ī×ÎøŅķ|Н^úíąmĨũĪmŌßŌ|^n+ŠiŸ¤ę‰–­ŨģĐūøo‰—§›ī{•kQ)T†ä 5j(bĢ˙ĒUFĩ ƒ1ÃĶúßęĪvCžûĪĸ-[֖_ŋLĒgš2ũ¨Í)ķh;åøĶDō'|¸ÄōŋųĘČ˙úúZôŅēųkëŧ§žåĀ"œ?î?Š2‘ķđO[x=ÆŽM›mėڇ}ÅøS—ÛúįØũ"Y'/?Ü}ozúO_ôT͍]5Œ3QÅQķōiŸöįÛqe™XyTfץu+P?VōĪ.S$gŠ”á7ô)æ˙=;­Åø†qŧ,Ņ:ã+,—ųŽå;ļË÷!ŗ|˙ä¯ŋcķĪíČUģ^bËr‰ôëgrĘdôiãĘžîūūöiŸößew‡OYŖ~ŨL6ęÃ%¯Q§/‡§˙ôŋŎ Čy>ķ‡XųØĢķį-ôcp m€c ņ”][>ę ãūķáōˇđõšÜŋĆ6ésãG!zōúĘŋ–á2EüŋŋÜĶߎíĪ#÷3Ģ/ũũ˙Ķ>íŖlZŖļSŽØiF,Q“Kāé?ũŋæÛ%âÃō‚åK,m™?ģL•a0ņül%í¯Ûé&ܝrüį’ŋÜŗßŌĮ(6܀ūĮõœk*v ˛k–Kėz˙YK~ŋĘûįtßË÷ˆáQ÷š§˙ôķü]4ŽúUĮQ÷ŖƒęéŠQ?íOąÃđ5ųé2c˛ŧ]buŽ LĖbeāËÄ2å”ÉPđXŠū‹ė”|ą2ßí¯mÃŋĪÆÎĨØyČũ1,9į?\֖Ī]ø5Îú!ŌÜØ/­ė×öŸOû´˙ ģÛ/iÔՍ͗ÉĶú۟­]ŌųŦĖķcKŦLÎēv™2|˜ÃXČĪWŪ~’ÖúĶ”éOųŅ2÷,ۚĩ­ôvĄeeVėĪ)ŌĮŽ ~-Ā+.ķ°L†˙ŗ–X›S&§OŽø?ážķôŸ>ņã¨ũgViÔŋ˙§—ßæëÛü[Æį<ĒmY_hÕī˙Y˙ũũŨˇķæív]÷ųųšŲlTĩʞŽĮĄīĶûDôũ>ųÖ˙vŋ2Ë˙; r‹cF~´Œ]"åcų‚ëa~´ĖŸZψŋļ|ÔĪQ6Ø?ícė=įĀĘuķ—uĨĶKÎõËûžîũĐˇŦí÷Væ˙[îOû´9–ÄQ÷a>jĢQq^1­Xæ÷īß9õdúĒļœōļUĢڙđ–+¨ļ~ßueY*ĀÎÜ'÷l÷Q>oß?ÚW۟­]ō}XOz‰•ÉY÷ī-SF~T=‹)lˇÍŋÁNō§éNēÃ@´ūM6˙\…×ô3@=ļîĪZÖöuéüėžzH÷ĪO˙é˙<4ę.2ĩĩë4ęˆÚéķ=žõ}ŋŨn||ôI7aЎ‚^…ž///×ëÕĢŦļßĢʇÃAa¤ōÆ{ČŦŸø Å{ŋß+üSā~<=xûōiU6í×ĢZē^ŠöeüR›ÖÂ5ۖčĢV??USÕē›ÍÆˇ_1ŗōO§“oŋBîĒĒėNSnkBąOˇ_ĩSP=ŸOjë7¯ŋâĪÖ.Ā"ų#¯‡/Ę˙˔‘…Ÿdų…uŸ@ëėôũ˙ví9–>WĮ°@ŒäĮĘüŦ%ÖÆĘäŦ{[?–ŸsOyúOŸøņ8jĢQAzw5Q‰ ôö}ŋŲŧŪ6.ā °˙ëëë‹UY•å ųąÁMĶ(āT?áũũŊZņëˋÕÛíäÛ}ß*ĩ€ũķÂUbļ}:ĩm{sËÅcäëOˇ_•´Uöåååž6|ģ5?ámqŧ#ųã¸.?VÆũg1?ę˙íeēŠ<´ ų øcūôŨūO€Ėođö-ŗą2<í·Į/+¯ĩ×cėÚĶ}B¤xTßõ´Oû´Öĸ8ęÆQĪuëäë‡ÄQs•XQÜoCuŋõrŖbœˆŖ†ur…üQąÁUUmˇÛķųėëˇŋČZž-҇J;Ü?ŧũæĨȃâę÷÷ˇēŽnks"Ž:Ũ~ą÷īÃ?īĪÖ.ųĪį>\Öæ˙2=hŨ¨ŋDĶë>JiüÛvúmø¯Ų{Θoũpū Uå{–{úތžqĄĪdö'ÜžūĶŋŲ_ŠŖļõ×÷ÅQŋžžz öf›ˆŖöõ+ åjpzv‹UÖ ļÖ5NbŸˆžˆŲžũZcgŠzQ¯ˇĒĉ8ętûƒFŨu^Á~Zņģ'Ÿ—Yyî eÆ1áÜÕųķ–ie~´ž˜"ķīQ\oōcvm™XųØo‰ųÂoĖųŊãēåQĮ÷O/éë+Į]ŋ9×x$˙ļ>įOô™ė^ķ´Oû/ĩYqÔFŠūî8ęēŽuHp^ũÄOĮQÛ8d1ŽúííMĮNŦŲ–÷UËĐÚ ęŽmO§Ķvģĩe>??ĪįŗrĒĒ:ö|[ĐįûúĒũv˙Āö˙{× ļŦĪFŋøÅ'RDĸÕh4ZFŖÕh4ZFŖ•H$R‰üîí†1 ŧūŸįœëšöžwŽ1Pđxū2ū0G˙SŽüå\˙˛Ō›Âxž:;írҧۑ”˛GŊh¨—uîŧųVÛ8ek?6Üū]ŪÖū î'mámm*õiŊ]6´I›[æõ˙XĻV?iß6Cykéî×i3´˙ļciØļŗlŲ—tygY´—íŨ66žy}O\ŗîm÷‹ŊīuōéÜįÁÁ?Ā9GÔ<ę^9jūŽ—‹ĐŠÅb1úx¤åđOáüŧš‹ŪõCË^ÛĘWĩé|;“-õ}ļmhĶöVn—„]ßæūhĐÖĻ CÛ?@ÚŖ~h›ļöĪl;´ūit?×OoÛöZęŽw^ÛM¯ÛüéëëÁļīŊ‡´ŨŖ†Ū÷Ļs?˙(GŽē6S„)ûlûŧq_f¯Ũ×Ãcėšzcž<<ĩœJ†S˙ˏņ{y^öy{u%¨Û'Ãn3Āqęl˙*šōjŲųo}Ļ=ęÛ¸Û~@iuÔĪ5M›Û8N¯Ũgģķėôų˛m­ņ8ãīsŸIg_møˇįôŸđĒ×öĐk§˙u'ęõ˙vßpĘļûԃ{Wg üōõŋĖGĢqpđŋÂEŸöĪ”Ŋú‘-ü™ömmd›“6ôËÜÖˇciqŅĖEgû>d/—Ûæ}0tÛWĩyf CˇíS?ô¸Zž¯6Ūãyúi{-uņ|˙ĶĩĐ˙.ŅįzīŅū÷+ppđœ×<ęļų¨#+GmmŽ%ʆRžˆWú”˛ÆĨÕ^ēũ8í…ÛŪ…Û˙ķüī!íQ?´M[ûwlÛ§ūoá ¯ų×i[Ų˛mŸûŌĐûJ”(–ÍuSŽZÔrÔB€ƒƒ÷åĪ”¯ęį{Jis)›ë{đūûzPƒ¨ĮŽokßYßĐO'o8'mãéß>Æįačąôym |<õ:|‚Ž|ĮŊÂîŧ¯å¨Mđ5J”/)å‹xk˙Rã}Ęįa÷͇Ā;0ôu8˙ã5čđļ+ņßîĪÜOPĸDŲŗ,=ębŊÆud{ÔB”€ƒ˙"ÎHF‚Č[ˇĩnĒĨ|ķļ ģŊtļ}†ˇ§ĩ=‹÷iãËŪ§ŸŧĪ9ę|<Ž>å=ŒW‡Đ߅Á> t ū>ž:ÜīQĖZ‚=jõÛÊGäĩ09jxÔ(yÉČ~øXĖŅI›Kųa.ûp¯j?e<3ÎĄįįÛŅûĩDīkú .<\ĸũ%Rå9zĖû”}úy-?wˇ [xŸ>CppđWņÕ1 öa)/+_áĨæQ—9jåN×=jK~ŧĖžû¨Č‹ã\%ÚDí8?Ÿ īŊ:†8Ž"MĶôĢKB’¯ā)Īy…O¯”ëgčž\.{´™0N)GÔĘõ1$9Íã*üœD5ßyJē°Ŗ+9jQŠjÛŖf dŪģáČœ+¤)@øüÉQĪTu$$§Í0IĶR N™ÛeĩžĒAT?*ĨÍų–aqéōŪe[Ÿ­ũÛčC7˙d? ¯:‡Cë[ÚûčxMš7xWõí˜09ęBNÛ9jzWŽ’T)j]ZnžŊ^@ŸˇË_ĪĩĸIéäØrįŠ9Ë<>9æü°Že}+­ ŪÍy§IœgŸQåbúģPŧę\ĐŅ„ač~„ųƒĨ|QũXãœB˙}ļ}üZQĮĻĨÛš˙ œ>ČûPÚ9jQËQkEËi%­G]¨ )Zã”Ū Ε ūTAįDŋ•u/ĩ i0å3E¸ßīß(¨KTĨ5:`Em.üqžÅX\ZmÚʆmÛĘÎū[Į`ã™úĄüUũ´õŲ†wėëmZęž_ģžåõŖõ 5}ŸŗGŊŲ ž=ĪÉQĶģr˜HŌŌaĸtĩ){Ôėæ ­ˆŠ2g.ôOی7ˆsUåęäĐ9á“Cg)ËC7Âx*ĪT’)”Šü%‹jú˜ĀG*YrØüUe[˙īØ×ôJųæöcKŸqNđXJzÎ88ø_āGú9j“úĐ$­e‘ú`7OŠĮ$)% x!ÕŌÂÄšĒqĄEŦ‘yJQkƒz¤ņˆĖzĻBËŖ–ÄĪ—ŠåQ?/ĢúđgúŠlˡ*‹KĢŊüˇ6õ ûuęe÷ąØmœąĩWZú|Š÷@¯1?qކ>/ę5āQđy”5‰dĨ¨ŨĩoyÔ˛ô¨ 7Oɒ8NPÖĘ$!]–‹FœĢjIgÄũ¸Á'm¤gĒ"īkršņĨ\{ÔiéQs)ųßf.ûđgĘĄũŧjŋ(˙L‰588øįy=GÔ<ęĀxÔĄūYb˜į¨wĸpķl![BåsĪŽķ‰se‹ØĐö¨ŗtŦņˆ¤AŪģõĪĪO‡Üũ(•žį5nEõ\ÎfŗÍf“čÁđŖQ­×ë™įų~ūŲŠYëít:ļ!û'úßJų"Ū§ĪÁmø–įļ/čĒwļ•ŨÛļĩél?€ˇõŲ9æWõ? ũ?Īļūûŋ\šŽ÷ũ~]kTú\/?sŊSI×5]āüs~*9jiyÔĸ2×GcŽšŨ<%MĒĐw‹]üNX­VÔŌi5ô­Ŧķļ–n%ĩ´;×ųtĪÕoˇ[sŸßív¤ŖíĨm´˙Œöæĩt•ĢC,bGbž/–ôîC"ÜWîĮ<čĸãJę–67×;WŌŠÕÔöÅ÷k×b6ķ˝j“ĨÛ'!đ}ŗ;ē͘ú¸h„|‹Ŗ’ŪDēK ŦøäĀ a<ęUîQ§ü`zVšƒÔĖÎQkąé"/IXÔk<9ßĻ´ĀŽHē,— zˆJ­*#Æfŗ™i°^R-CjšÔ-—Ļ%Ũ7‚Āgk—ˇ<ŸOĘīũųĄōx<Úc¨Ž§!‡o’tįŧœĪfœĮÁ*‹ųũ~¯WãxjeŦ&÷¨;Ÿîš"]fú7%L¤L×ĸ> éÎIõú¨ŲĐöú¨m×ŊmlԆŸU:E†Ķąģ#į~jõ4~zÖô Ą'+äz:í|§!ŅŠ–wǤÎixöyŽ–æã†íQ? ŋ˜“dŨívgžļ"9zŊ^L “j‹wjÉûša`-h÷ô.FgŌ/ŪæØmfĐžčÄÉ}°b+÷ęŗ]ez˙ŗß‚é…ˇ|˝CēLL =×ęŖDĶ.M9ę pg<ĸG›‚Ë˛Ļ•‹mzˇīŗ_ŅŖŸļöŽëüéķü ũ3Į8ô|ĒŨ5]\ęâtŠãzžš/XĄŋ$"ÎõtmŌÅKˇnzˆ6äöt[#QM-ŠĖ/@!øÃ/*õæB‚\:.VqP<ŅA”üû&!čúĨ{f[Ÿô.ÉY¯ķéDˇÁŗžÃÜÕ{蒚Ņ킆$õ!čuWž—ąŨčöEo.ī/‹ká0KËžVRœÄŗ†õXÎ÷ę¸úQ—6ŧy’Ø×;]tԁũ}Û8ώũIš­OēĮŌŊ—ŨáéŊ‰õ<Õ°<æd8ŨÍjk<.ŗ^‹ĒG­\h%ĒĩSm<ę,ËĖō.ÚŖæuéģņ\ĄJŽš$–ʸĸ”ÔszˆëŠ$ÎÛŌM OAX}rKv)ųD„SĮ¤Ė~ĩĐōuå͏ËUžˇąž]?Ü :ļÂqmO}_•`CëšĸB’ˆtŸ?)_]ÕĶYĘ5ĒnC\T~Ô[#&#KęwŒÍ:Ū°pÚ5÷ÎCíQņŧĖøyĄÎ•^=^‚ €zæ–mį96"6ŠyÔqí¸Ęĩz!­ÍkŖøÖŖá|ōHXES=^ŸĮÅĸē%G-Ŧ EØv› ×nŗ(¸geõ=éxSnŸĩzŠŨ°$‰Ũzŗ/’ė;mÖk6¯ė1˜öŠō¨íĩ.ŋœęXdYãō>m¤pĘ>íûė÷ĨĮ2€÷>–į°>fņt=)ęCË|Ôôčũv[­–tŗåzĪēĪxEÎYQãÚ(؁Ōäƿ̨ķĖŠë]X×ģĩ_ˇžäîņpX9juũíŨ>ųæ/õˇWtg`™ÍīԊîbôž@˙ĪįôNŅķ¸Ī88ø?ķ"G-–ë-Iå{,HQ‡ąrĒS­¨ŲŖÎ'ŅNum>ę8Gŗ\+­dl)_#ˍôšļ­}ŽįzÚ;) ēą,æķâ—h‘ŽÜđ…ŋ÷o‰=BõˇÕJĮb=SoöbÍ~ÔO­ė‘ŖŽ—tËåŌxøÄu”">ŸOZōQßø¨įÖQ×ÎĒ;6ûxÛxãsdJûy1\ŋ1­8…R>ƒŪƒūų´ä¨$.žĄĐ?El}O'võI‡ķ+Ąã™rŋM,ņ\ēĮ†Û2[ųKųU&Ũ–}ēĐ_Râ­hķ  a›36ööu9ꏗbÜ1Č ķˇĪˇŸķ‡ķQ `ž1oŧ`?8¨ IaGûęïÚWRõ¨÷ëä¨;GmęûT–ËéÄÁ0õ‰@į'kũ_¯WĶūáqđZXĩRÔ¤ĨīąTĸ:‘Z+ę¨)G­uPõËzXú¸ęļ{Ôam[m{Fm}ōĪÄjõTiúŦĩ×âęĮp„ûũžũLSĪ_ņØ8ŅjW÷x,ŅXw>ŨsUã´C5lÍuZfĄB ‹E.ž­öųQ;ÂĩqlöņļņÆįČÔû3ãQßõ QõAāķŠ‹ :VÁN€kēŠ˙¸´pæúh įÔúĄˇ•=ôh?‹Aī/*´ß~\Å3忍éâ(†´$Ûe2œ>āDü%¯ģí–İ(ÖV˖>Ĩķi—_xô‘JIŊÎvŪ’ÛĐ ŗ˙ŠÍ}šlŽú̏xQ›.žØöãĮøĖø?yŒnŽšŽĘËåœč/Åb`ÖŌT=jōÆt™?O¨9Æ9ßŦסëUčK;Ž:ķŦōBPi፝ëJŽ:IčF‘"Ž^ŋ}{ũû”ƒæūЎ§a̟Oǁąĸîu\õ{88øsÜ䨗ëō¨#q;ÕijyÔ˛æQģ9ę¸YYõquö†#Jãˇ ŠŦŖÛŖĻQГÅoŽĸ˜ëƒ°énåöŲūŲ‘WÚŪǘ@tôöm9j”n)ŪĐĪĢúœhézÔtą"å/Ô8h.(ڄ'~Ŧ<=jzt^ĖtZ¤>x^ŽĨ;ׇlBm>ęÆëē­O×Ņ­Æây"šî3fŽ3°‡ĮĀkQzÔEęCÉéX†z˛]ķļG­œčÎõŅáˆÚõqUlëY)Ė\a^E$Ūx& ķĶZž­0uŌ0õˇÜp:|Rąk¯1Ž|ĮÅųėü™dîâ&VŽz ÂāųRÖrÔĸ8øoå9jpppđ7ņë T't\¯ā]k&Ž1÷Û9ļ$xU)Ģ9j†˙ÕÜÍQƒƒƒƒŋ›sŽZˆ|Ž[”Q]ņ¨;įú`oeĨlr>û÷0ŸĪ9¸2‰cyu™¸u–{ÔŖ —˙”Ŗ6-ģĩ\É‘]Ö†Q§›k6#-}Ė~ö¤¨ŗÕ9;‡Ųíž<đ¨ ëû3vŠ$­Ë•$‰eįšĨnĘQ˙”ûÜ•vį$×Eõ‡“p§āĐu>u"›rԅĸæ…Š$EŊÚlrÔāāāāāãqųŠuĻ&‰•Q$v×Ė;fÁ1#ršËÛ-ŧ^oâŅDųgÃĒ¯îNl6›ų<đ4zõcéd;g˛ßīįķųbąāa3xÔđTÖLÔķQßjķQĪ|RÔėZķú/ÔL)jä¨ÁÁÁÁÁGäŸĘQ3Ž×û-Ę×ôĻQ$Īį^r:ŗô­åQËnš´ôų|âÎ-!=ØŖ6 ĪϞQÃđ$ĘõZ­BÎî4‹jö¨Uę#Í 9­2!ĘŖ^×<ęüæĻKpppppđˇsųÁuĻĸÔa)--dĻϧž&ˇģŧGŲåwĪžgį¨ĮŖzį=wNŽšŋ-ĸΤŊ“$ĄĘũ~ßĶŖnėœ:äwę0 Mâz>Ÿ L  ¯†åQīHQ+w:ĘĶœŖöf>)ę[ĶßU—ĘŖ&ų58888øx\~j>ęLĮ0ũÃü{(ÂHŪnâϤ¨OįčtēŠNjō˛eŽĀĩš>Ž— ÕēÆŸÍ¸qwŽZĪõąŦÍõÁ?l¤ Š4SüŅé"uM͞=)X09jäPuų Q]xÔšĸžŪ#ÕÔlšŪä¨ÁÁÁÁÁĮãō‰õ 53ũËD&ô†˜$ôßôtē]¯wŲųŖBūŒGŊŪ˛G-ĩĸĻRfšG=ŖÉiū35rÔāāāāācōæ¨i_a¨úŧ‡â|ž äüz˜õjŗœú(D5 i_˙2‘{ÔWUÆ:G]ķ¨ķ››.ÁÁÁÁÁÁßÎå9ęÄqŸĪ×îČ4–GŊWŠZ͒'õ\y2ĢxÔą ~”9ę{=G ŸtrԌwä¨ēaå¨wBfœ÷`Q]ÉQĢY>ō囿úĐÎ@q‹7—5ē¨×ŠúõõKļāˇĸæQ‡ŽGÍķQįSįYķQ'Õ5|’=ęûŨõ¨ß15@8G-ĘuáQ›ĩž:,ą(DšŠÆļG]ÜÜÄ\ ppppđŋĀecŽZŧ+Gũ’mø­hËQ‡ÆŖö•GÆ5jĻ&AŽrō9j™/ŽŸĪįŧËP`Ņpø TsÔ魘:Īä¨ũ E‰4LŌ(QĨLŗuÍŖÛŠ˙k\Ž”Ŗîår!‹ ̉ĐNI`íÄ^!€Éĸi>jiĪGMD”œ.Dĩö¨÷ČQ#BŽ”ŖöŧžĻąīûŌYFiūbApĒ‘jĘëP-q¨ íëõĘû"čB픎wŊ^Sƒõē\‘ÚœĪg_aFĸũ|:i:ģŨnĪ&ƒ`å¨Íš‰ō•ķQûÁ\{Ôų_îQo÷9jppppđņ¸)GŨ?†Aš–´1É]YÕÕģŨÎ,8N ÎŪ5 á(R‹3’´Ūnˇü¨íQS3ېz>öf<‡Ã6UÎÛܚü\0{Vƒ īíĨđ<Ŧĩņ¨…Y3qæxÔô×āQkM]¸āāāāāāoįuē¨wŽúß~*†ázŊ>ŸĪü_âF„o6ޤņ‡ –ËE…\i{ԞÖôĻtĮĶÆx7ŒGŊژĩpsԅœNŠ,=ę50ääsÔ5ĐhgŗsŌĢՊ•œô°qģŨü‚ŽyÔŨãiãŧÍ9ęX„Õ5éë9j]‚ƒƒƒƒƒŋŸ;9ęŧūŨ9ęūØív¤yœTĮõzmĪƒëõē\.MÍvģ CeM“Æ6âyąXĐGæûũž]nj@s%ę´qŽ¨ŅŖÛŠ˙k|ŦõĢüíÚīøj4å¨kk&Z9ę9j`+GũĐāWĢ4€_ƒ2GŊŪ&ČQƒƒƒƒƒ +Gũŧ ^,ķųr~ęu$n˜˜<ϟŖāīĀ䨗ėQ‡É-Õĸy>jä¨ÁÁÁÁÁ'Āŋ=G Āo‚åQo!ĩœNÔ˛‰‘H‘Ŗϊ¯ÎQđË`rÔĘŖōĒ<ęD;ՉíQ#G >)ūŊ9j~ęu˜\ķāGĸ=jķQÄWä¨ilëõzæyūlļŨnųS@Ļ—_QK‰ĢÅÄfꡍyȝGēqõÃ.ā‚ÕāyTrÔ"Ŋ†1iivĒe˙ĩ}‹?¯yÔĻūŨ9ęū ņžš\2Ŋ8ËõzŨl6üįyĩņđĸäY“TnĪĩžVW€įQ™ëŖČQ[ugŽ50ØŖžō[88888øûųX9ęwxԕ‡\EŨÛŖļĩąi@§eŗŲAāi¸-áQĀķ°<ęŊō¨eSßĨ¨3;G G LĶĪQ“Ž=ŸĪuˆØų|Įåø‰›¨ÆÃŒ†ˇ=jڜ†Ag&kō¨Š%rÔđꎭčíuĄfįøĄ˛MüsÛäąšŌšëciæú¸\.ŗŲ,üĢ&ÆÄ&­žĪõ5<2GMŠ:ĩ=ę9jppppđÉōÎõE5x-rÔųŸĖ*9ę´Įš‰“p-ĀÁÁÁÁ˙wrÔyũ¤:˙Š5VŽēÁŖFŽ|š9jĻ7G}‹ķYŠŗÁķQOÂĩ˙ 9jσîõ ķQƒƒƒƒƒO’#G ĀtPõ¨íuË|ÔČQƒƒƒƒƒO€O?GmĪ=›Í6›MÛĸäÛžZ-™ØŗX/—Ë'ĮV¯Á:/đĘuŖGíä¨#ä¨ÁÁÁÁÁ'ĀĮĘQ÷GmÚéǎ n\ĻcĨÅĄpûĮʉđ šrÔš¨NŨĩ@Ž||Ŧu˙]××HâÛíļTĢ"zķ ÃĐ´§úõzeœmŪ÷S[!ąiÄ•Y'‘Ŋ\.ūlæû~ķ"æ cËkč”Îįsú/•ŧ„:X/ãčրķ…=īr>ÛãY;ã€_;G°GK]Vļlˇj¤ĸGAčđ^ü€‰ŖŅŖæ?ä¨ÁÁÁÁÁ'Ëŋ1Gm0ŸĪĪįŗĐ>p)P­öŧÍŖæƒ<ęyĐy3˙ã؄O nˇ›é=Nŧ#ĪąĮŗ&ĪūLŽzŠ<ęô %§uYņ¨e—G-āŗhô¨EîQŋ1GŨĩ?›‘ %åšßī=ęÅ|.ŠÜ…Š_,FÛšåãņ˜é0ŗŖ6]5Å×ëuš\ro$§—ËÅí–˙qģŨōų Ã0rE=ĶĻs~8Ėxhwôš€UšŊėw;WGQdbØđëQzÔ›ÂŖŽšŸķLÔMs},]Į8kö÷{>}•öG ūą$ĪõaęŠrĻ'úPSˆøžĐjŸįú ¨ŌĪfŗáÆfĒøõ09ęŌŖŽš>ũĩ^‹û‡ŠŲlļ^¯ã¸Ī)Ũ†î…ΚOŊĖ÷áˇzOößą‚$üX9ęę|ԑ”Í5rÔĀø+GŨöĸ'ŧ0JĪ 1zÎí\“¸īžúÉūß-ø`\Ô=jŗfbT[31EŽ|:|ŦuĮJˆõ–U y>ĖĒÜF^†aČĐQiVŦ­‡Č‹z?ėŸļRē}6#é~ģŨLâëõ*ĢŦޏę^]Q-†¸XĖfŗķųlގŖĒ\.ü§ąOÛQ|âā aå¨IQgFNˇŽ™˜ G ŒąrÔũŊÖZK0iQæÆīĨš(Š2-­ŪæGã8&™jgEēû§­‡CĻ—8‚Ā´1‚|ˇÛN'"T¯Äæ4–÷§ãŅėĨ­˙ũ~Oúœ´wmü5øŨ°<ę}"Ķ{Ŧlj.GM„„4ÉéP•iƒG=ļSū×øįsÔvnŲ8ˇ,ÎíŌnīôP——ķųüx<ŌĀífô( ÚÅ|n\ßļņÔļr9ĩ1šœÔ;whkûÆ­čQcbWš­nų°O¸ĶĀī†Ŗ:-)íŽđÕ(sԛ6Ú'rWr:Ņĸ:QŠzŊČQãaŦõŸZ3Q§>ēĻĄF:U:Ŋ')Ô÷"GÍõ-LHKķIjRÔČQƒƒƒƒƒČĮĘQ˙lˇ[ūāp<ģ—wÄ å¨īmuT*jŪËõF G Œ‡ąrÔÁŖVs÷-Teąčš;ƒG ę9ęDፓjŽēPÔ7VԎGmûāāāāāāāu2Ĩ5ĩuņŗDgŽjŽēâQ  88888øx“G­ę‘Ŗāķ8T=ęPģĶ\f<5ĪõĢY>ø¯mŽ‰¸āāāāā×捜zKˇ­jŽZßĘr€ƒƒƒƒƒŋ—Ķ›O–{ԉ]€ĪŖČQ'ŽGm¯™HZZÉi]ēk&ŽîT€ƒƒƒƒ˙5>ũõ?Ŧ†aøËårĩĘgØĀO˙ā+Pæ¨×;‘–uXxÔ~ <ęX¤Q’r)Ķlm¯™(l$āāāāāāācå¨ûãÄđl63ë¤0秃`"psÔüWæ¨ũ€H$´œÖĨLĶõļâQîT€ƒƒƒƒ˙5>VŽēŋķėYKŽV+ŌÆëՊ?pårĄÖ%<ŸĪܧ^bņĮŦ´ČÍėĨĖ×ē{ÅC:|Ŋäâ•×ëõE‡Ā`´å¨ku"ŗXpŠ.cRÔ9jppppđņøX9ęūÎŗišÛíN§*÷û=Wnˇ[Ö§ãŅZÔÛĢmkj¨“ëõƒ7øžĪk—SIūķAđ$ÜĩųË,ÚČi"*õąŨ'ČQƒƒƒƒƒĮ?ŸŖ6î1;É\ÉâÜ.íöL|ĩVšzWĨ’x­2ŗÜlKH×k¨=ûÛ´U\Iäx<ԁŋøP;G-ÕŧĶʝ+õ+Gũĩ…6ÜŽėãQ{ZĖ{Ļ’NÂ~ŋŸĪƒÅbEŅ?/•ŖVŠHģĶē”™ëQkQízÔŖ;āāāāāQŽÚ/~oØčQSé:ŌŽkm{Ú.nˇ›é€ĪÃÎQ+Z͏—rÉŗįųúĢĨD–ÚŖŪ ä¨ÁÁÁÁÁĮãcå¨ûÃÎQG"TįĘív{š¨\ôápčãQīw;nE‘‰Lácái÷ŪL4ÃÎQ“ĸŽôüx\f–GČ.zt§ü¯ņ/šÚĖõąĒÍõąTs}Nî“ŖĻƒŨl63Ī ‚ĀLëAÄĖõĨg`D˜õjŖ:˙59j~Ü5˙EĨG­fãQî´Ž'E­=ę]‚58888øxüÛsÔü&XķQoyŽíQKãQĶ_Ē<ę\Nsũzŗ‰ÉQ sŖ"˙—ĸČQSĨU€Ī#ĪQ'I9×G!ĒéŅŲĖ÷fėQKí]ËDhzSņ¨Gw*ĀÁÁÁÁ˙Ÿ~ŽÚķ~Â0ŦUžN§ūĢ.đ-09jãQ›ŋ4ĩW!Īå4•TŋÚl…™ëC0Ę88888øģš•ŖNėúI­™h74X,fŅ~ fÍDRÔ2Í=j­¨éQĪxÔʸ–\’¤&E85J”(QĸDųą˛1G°G}yŖGŨßaö~~öû}’”ƒšŨn‡ũŪŗ–A\¯×žį­×ĢTe*ķūĪįŗ¯0ŖŖ;ŸNšÎh[nи#õI –‹Eā××"‚ĀtĀ›Pæ¨W›ŌŖŽ…ō¨ĩĸžų~ĘŠD&ēĖ=jä¨ÁÁÁÁÁĮãcå¨û;ĖÔ’ÆŗŨlLÍjš$ lz Ŋ}š\2VڇŊŲęp8Ļʙįív[â×ë5rŠŧÛíN§SĻ$ûŨÎlÅ]™>Ċ柀ž(sÔ*õ‘ÆVę#ƒG%J”(§ZvyÔīÉQ{éÖ]ü§šÅš]ÚíŠ\¯×B‹˙8Ž7Z]›m}ß7îąņ–=KąWxšÕŒˇĸŌ7[y?ŧL ų|nļZĖįT튪  eŽÚõ¨Sö¨y…ö¨‹õēĖQ‹‰.pppppđđąrԃ<ęLûĪėoˇÛ(Šė<­ŅMéößČMK›Û-WĢ˙"’sŗY::ū &GŊĖsÔ˛-G­Dĩúq"!ŨģŗŅn6§–Įã‘•[æķųõz5€wŖžŖŠKĢ2cE­=jŊyšäķQ§ĢĻų¨QĸD‰%ʏ•ŸĪQ…q•i¨A¸õ4ūÍfãy=J8´-GũSöÖ0×GÕ9'ĄN Œ†āŨ¨å¨­_ ĻėQûÚŖRŲÔ\æõė-˜ęĘēu?ųIdld$‹D"c#‘H,Ų‹DFb‘‘ČØČČČܡĒHQųágíÕPáŦ1žēŖ“’^w7“y&Uô¨a†aėĢGũk&ĒOއGŊ[­ų¨ĢJ÷ЌeŨn}XS}oÍDFÆ1öúa.‹~Žõ7ã咭V+y˙2Y“™ÖË=>ŨÂČø?3úęQ_ō›1å|_B˙n=jŊfbc§ÕXÕjÍÄHČ-*SëQmG]ĐŖ†ŋŠÅëž\.???âĢĪįķo>ŸeYv=–‡õjåņívĩeŋūuöÕŖšS],æŗŲlä‰Đ˙žn=ęd-ŽZ—ĨĢŧTÅiŲ6uc§Õ(–:^mr2jƯ;iínˇ‹ãĨaŗBŲbąČUÚ|‘÷#1ĸōŖ°}îá°Ã`>ŸkįÜ=EƒÛ māMãҜđúŠ“‰\€^-ûmŽ˜ÍĻr12Šį7GŪģsÁīŅŨĸĪŗxpžÁëddķ8ū5BčßŅ­G¯Ä*7ß@ŦL_ZgÔjĸx1ÛâĨͨ2ęÕē G wŌZ1Ābh O´ŋ5œ$Évģ•7e…ísWĢÕmcīüâKÅËI.MRm‘'š“ĢāzŊ2Ûå7ëĩI˧ę2Ôņr=ĮãQ^åx8č—~t=úœOîąpRëU’ČkÉyä2V&?oŸgđ:GōoÃL!4ŨzÔ:ŖvŋXkG}Í¨Ģ›Š&ŖfüÆą“ßæ:(6, ß”‹­Ŋ\äŋˆĢåļĪ5šŽŒaž_ΰ^­ä)ķų\ ļŨ.Į›įš“__1˜ØįŠ­5,ÖzŗŲ¨.Šsæ{×sęģĢšYžG§čr1ƒįŧNFÆ1ô¨Bã‘ÛŖŽĒÆNëŒēv3jSS]̌zSĐŖ†ŋŠ;ãkF­ŲØNÃâE:Ī͇ēĘ>ËårˇÛÚįēkĸ…ķŠm–K2_oœÍfišžr=īąpRë@Ë\Ãāy¯s$˙v0<Čô¨Bã‘ÛŖŽZĩÚk3ęŌͨk2jÆī‡zÔąa71ļčN&œ7õ+ų­‰˛ G:Įˆmd3‡¨IÂī]Ī+÷čnŅYwˇí?xŒŒcĮßŖ–ŋ-û<\ÁüõÕŸžî¯œ!ôēœõējĪ’Wˇ{ÔöQĶŖ†ŋmZkÚˍSU[ÜÄ8I=÷].ŖÛ[^¯ÕŒ‘ÛíV•{į—‡ũŪZ_yîRŨ¯húÉōÜ4MmÛMÅ-Ë^ķ-Åãņh;Ū÷Žįņ=Úíˇuë2âūņƒ×9’;d_=ę×%˙uĪf3ŗb¸ŨōøøßzŨ_9Bčuš=j“QÜŖ.œ| ‡áq˛;ĩØŨķų–kš)ņå’]į֘Ī;sb„j 3×G÷üō4ąÜj>ęÉD˰¸Sû\8ŽÍ<Õzux“ЎXöÎfSy-ĩzÚáđøzîŨcgģMĄķf*šŧŊūŠbįøÁëÉŋ ōPF­ļëuöž÷Í×ā@M¤ž ÜŽ\šų/.^.åvŋ1"s˜ÜŖlą #ĘöRk`ÍÄÉD>Įņ˛î­Ž(×đwwŒzŽVúAF]w2j§G]hé?hŒŒ˙“ã-ËeddÍ8ĐŖÖãģ3ę×`s¤8įSŗ¸}îfŗ1FWŽD¸ŗwš\ȇwųp-Ėå˛Eėą€|~—Ŋ2nÖkû\ëœÍy˜™æ<ĄwĢßŖ6uŅΨŨÖGĶŖž´{ÔcI-`ø×Ųéâz`Î[õÅŨūžĩ;‹ŽM›9wG÷øZÅŗéԄÉ6=ŽĸČļAĸ&‘ļįĢŧŨn’$˙lRnŲbŒt…æš2ēĪ mYÜõ|>ˇ 6BčŨjzÔųKuíô¨ķvZ˙)cdüŸɨG8–åwdÔ"ŨĮˆŨ-Ųu:ĮĢģĐ ãmlŗl1ąŧ=Ūe÷Ēˌ÷ËåŌzl„ĐģĩŊ“Q_{ÔáPFM†aöÍ_ŅŖļŧZНÛįÚlųŪņķųüp؛š‡îBīeKķ\›Q—ˇŒÚyŽaųũS#ô1 ÍG]åEUÜæŖÖŽēÛŖVķ¸=jķ­¸ũYƒa†á7rŅô¨s-ģ}Ts}X<›ÍÜõ~ŋ¯u;zŨtĄįŗ™š)ŅĪn'nŲöĨīvf—ŋĶ,ãÚéQ÷_÷ĸŋ’üļûCŨd2ę|`>jeƒ0 õįߥų¨éQÃ0 ÃŪøķ=ę?U'͖KĩI˛\y’$AĖĻSķÅC‘ēøéÔx`቞†¨Ãu3ƒGwŽįĩ\–“[׍zŸē=ęĸ•Q‹ŖŽz­Qœ ÷¨Í4†a~7÷{Ôf;k&"„>/ÛŖV+ŧÜzÔĘT×&ŖUF-[TF­GÕŖNŽ­zÔ0 ðöÕŖFĄžîô¨K§õqͨ­Š–— =j†aØ'žzÔdÔĄžz=ęęšQ7Ž:¸eԕå0í¨éQÃ0 ÃŪxü=j„ĐŋŖĄų¨+3Ö­o&VíŒzUĐŖ†a†ũ1=j„Đxԝēj­™čfÔæŅdÔ̜5 Ã0ėéQ#„ÆŖVēžŲéÂŽđēõuû2ž:ęš5 Ã0ėƒ zÔĄŅ¨ÕŖî}ąŨŖŽÛ5=j†a؏ŋG­×Ÿča†a’$ŖZÁP~Wq‡A…ájĩú­k î¯Ō.˙^›ÍF`ąX<:ÃËĢRž.y]ŗĐ$BoR§GíΒWë5o̐ëeuͨ zÔ0 ð?z2éŽđ˛|č$?)õ‹ŠĸÃáPkŖ{<Åđ˙ʙ'÷ĩøöŧY§æŋá?K^W^ũ×O‹UĢGŨ[ÉE9ęĄų¨éQÃ0 Ã~yü=ę~Z1iššå×@ŦUŦēCĩКņŊĩļžqˑqŧ´‰ëų|žÍfjųÅŲ,M¯Ë/ƝEoœČh×dtĩ^¯vͲæ ¯Éč܋eš šļHÍaÉí˜-6™īœV.ÉŽŪhSčëÂötĒoŽąˇ,coYŽjąX„ApØī'Î2”qsŲöH÷÷,¯NcŊONZ9jŗÎø`FŨ™ë#§G Ã0 ûãbô=ę~F-æĐ°ŋũ~oXœŪĪĪ€Œ›Æsnˇ[qŧ*ļ* kD7›q×âˇÛŲ(†6ËÔ'ąÖ6†ƒ*?v6ē’îÕ<¯ĮŊËrrrUŗétđŽ­äŪÍ%Ũ;Ãtč r1æ–åˇg#zãĘl”_‘ũ"GŨ#Ũßŗŧēũũ#ôëęQ_ĮēQË3ĢķQÃ0 ÃŪŲcÚ˜ę{Ŗ•ÛŖ+ÎÖÍNķĻ˙ –Øl—1Ō3֊Ä^ö¯ėĩg˜6GŠ•cŲ9xĒ7úCĮ ŦíkđzÜnŗ›0ۍ7“|ĮQģŪMšÎāŧ–}–\ŒĩÜaöŸ5x¤û{–ŊövúuŲĩqÔųÍ9ĢŊâ¨Ŗh*ÛĪyyž”f,*æŖ†a†=syͨSĩŅŲ>ڌڕkhyđÛyöčv4å–ˇÛxČÅbaÂęZģĮÍf3›MįķšŨčęAF=x=÷2ęĮGļOû_Î Ī˛÷kĪpį78Gũ#û?"ô‹r{ÔâĸĪyuēTfŦ¯­ųD\ÎÅņ\ʌ—ĸlÍõqũ#–įúo Ã0 €2jŊ}Ė=j+×4:™pų4Ŗ~đrišN{ČÆÁg%IbëŊW¸ž{=ęūŨ›ŠŖ•QŋœrģąüāF7Ŗî鞟ŒŊUMZ9ęŧŦįō•ežåOÕĩG-pĖ”VųĶUTMZ}‰€5 Ã0üy.ž­GíĘ K×ëĩų’ Œë^ZîČĄ7›ąÁY–Ų#e¯é'ËFëeŖšNŲ5ØHųÉÁæK‹š>¯Gūņx#eËã„y6›Cm“{=ęūæÎluü’eö÷ ×`~ō[˛ĪÚ4ëĖ9Ō=?=jôV9=ęÍĨ¨´6ĻZ}Ņô¨UF}Rvzē\3ęËĨ5×ĮõZÃ0 Ã`y÷šfԗ‹ģ]gÔc™úŪŽNv:8ׇøÆū\â{åHío¯3x\įú˜N­em6Ēš>î}įi^7ŠZķQ^OšĻr1ōērļĮ ŗú'˜NÍôŽZs}j†aØ+ŋG:Z­’ü…ų¨_—¸čĢŅ9ķQŖ7ËíQ?Ȩ÷'å¨uF}šäĨ^…œ5 Ã0ėĮßŖFŲ5˙RI’˜Ī5ģŨn>ŸŋōÖLDī–íQ/´ŖŪëõž×ŖÖõŘj“Qįô¨a†a\ŒžGŪ¤4MMŅEėtūĢĄ7B˙Yî|Ô*Ŗn봌6Ŗ.MZÛéŊͨéQÃ0 Ãūxü=j„Đŋ#ÛŖ^Ä:Ŗ>âĨÍ8ĐŖžfÔe“QßzÔúO#####ã‡FÛŖÎĩėv2j„ĐįÕ˨ kĒ+w>ę[:§G Ã0 {gzÔĄņčÖŖŽW⨔m.~Ō\ ?ĩZäÅĖGíô¨ -ũ‡Ž‘‘‘‘‘ņCcŅëQ›‘Œ!ôyu2je§S-F:#“Q‹Ŗļ‹ŧčų¨×ô¨a†a<ūĩ^{ĸ‡I†I’Ü[øÛ‹äwĮqQؚúu=XÂϝÁ9¨˙Hvǐ§g\­ōÁŗ˜ ũŊœĩΨu:­FqÔe%Ž:šNå˙ËŌKyĖĘ4+rli×L¤GÍČČČČčgēcáä‰ËÅâ?ŋîīJũĸšĩc:k&žŽį‚6\'ņ$WXčô§g4Īž•3[5úkĩ3ęōZųHķxčkF=•mōSzЌŠÎ‹*NÖô¨a†a<ūu?)ĩĻN,_šĻfYĀ{k&n6k&Æq,GÆņŌfĒįķųēfâl–Ļ×ĩå×b×L´ ēZ¯WfŠņž¯GîeŋßËÅČ%™ĪnīŽb÷ Ŋ•ííČÁövÜ폞K.v^WžĪįæöí‡9F~irŠ‹ÅÂ^Āã×eEEô—ęö¨uF-vÚdԥɨå˙“s1Օ~”EYkGMš‘‘‘‘ŅÛXŒžGŨ΍ÅČ;'Õ°xšŸŸ7Í:ŨÛíV¯‰­ėâŨ›ÍƸką‘ÛíuŠ1šYĻ>AˆĩļAĢX_ŗ"šģŅ•p¯æ1x=r/ģíÖ¤ŲĶéÔŪ…}ÖŨ;j–tégÔr˜šųÍØÃÜķ¸JâØŽąîžŽÜü6ęfáuûZō SŲÛüērrû¯ƒĐĐ@ē)~¸ĩ8jkĒeGŧÚĐŖ†a†=˛Įĩ1Õ÷F+7M+ÖÎMbķfiąÄfģŒr˜Ų(Žĩīx#õÍĻëĻ͑ŗéTŧwįāŠŪh>t ęAbđzlÂ\ˇ“vģą}GŅĀz=jëęå0ëŌŨķ¸ ÃĐũí ^ųÍ´7 įˇđøuŨ#úęö¨Ũš>ŽõĖ:jcĒ‹ĒŽW&ŖĻGÍČČČČčgôÕŖ~]ž ×Iwûė:Xw¯˛čÍh6Ę-oˇq†‹Å„Õĩö‡›Íf6›ÎįsģŅՃŒzđz:æųÅģ<ƒ{ö^ėũŪŗú÷Î/ˇŸ$‰ÜŠ>ÉĀÕ:/÷čuë;ŋs„^Ôđ|ÔzÆų,ļ3jŲ&c^Ö*ŖĻG Ã0 ûãoėQ[š–ĪɄ˧õƒ—KĶtÚ;@6>K\č`šâŪõ¸÷28kGëŽÂG)ˇ›÷gظ÷1DgÔe˙ņŌûũOĄ?^9Ļ}bÎ,ŋC7ũ~đēdÔč/ÕéQë%Č[k&šŒú\t2jqÔô¨Ŋ…§õëz1Ŗ^¯×æK‚2Ž{=jš#ÛŪl6ÆgYf”ŊĻ`,­'”æe×t:`åw%›/-væúŧžAķ<ŸĪķĻĄņôŽúĩ­…_˛ŦßvîČíQģ¯+ū_ØLŦįfÔ;ŨŖūųųąđøuéQŖŋ”“QoTF•‡ŗzˆŖnÖLt3ęú–Q_ÚĩųƒÆČČČČČø‘QŪ}ē=j=Ž(Ŗžß"p ęŊš>Ä öįúß+GN§S;ƒ‡ųFžšėb:ĩžŗŲ8qgĀčH¸y]1Ĩî|ÔÃs} ĩ#äˇ-į_č)ŸŪŅ`˛Ũŋ{Cė\×=ę9=ĖL€Qš—“×:6s} ļ¯û¯Ë\č/e{ÔjŅ–˛VvژęŦėgÔz|ĐŖÖĐ`†aøũ|íQ§ļG}Ũ>žŒũŽė|Ôŋޜų¨Ņ_Ģ“Q3SkSMFÍČČČČ8ÚņÖŖkF~WvÍÄ_k&ĸŋ×ĩGm3je§Ģ{ĩųrâ­GÚĩūS6ŽÔ†aū¸îQįdÔĄĪë–Q‹I.¯ĩɨG;zéQW•* _.y–]Îgõ8Îoz-„Đé~ēēĶŖŽéQÃ0 ÃŪųc=jąĐâŸŗ,˙œeEv)/ō†˜×‡Kušâ¨BŊų¨›¯%ŌŖfddddķøąĩéŧ¨ĪâŸķú'¯7—:>×ķ´ŽöõėP͌ .BČö¨í|Ô{=užŒô¨a†áŅrņŠĩŠĻ/õĄ¨ˇEŊĘë8Ģ—ÚQ‹žūÔĮSQŧg „ĐŠ“Q+;}ēšj2jFFFFÆŅŽëQŸNį<¯w—j]ÔÉE9ęÅIgÔ?u¸­öiqš<2đVN‡Š–]ÁPîhšX„JÁj•Øųœåfwģ]g^hĩ”öDú˙üí­"„ūĢlÚdÔ?jũqõ_M†a-˙Mú$§Ŋdåxé‹rÔËsŊHë龎vÕd-ī›ĩ]WePŒîņx4K˜%QĖJ.I§ijš$˛Å.ä|oáo„_،z¯ŗüæ¨å!˙5áķŒúú'.ŋ †a†ßÍũĩҝgÔō*įs~ČĒe“NO”7Õ˙_UëCõ¸CbSåžžĪįYvŊÔė|6‹öžŪZ•ÛMŧ§ßĄOjÛô¨į˕8ę]š˙¤šéQÃ0 ÃŖåâ/zÔĒ4=e—zyĒgGNëžĮd]ũŋ¤Ü¤jīƒįZ#ízcÃaēßj”;ĪŊ\.‹ų|đlĸHK­?>›ŲÕ´B^ädÔâ¨Ë˛ĶcĒK•QG÷2ęKģG}ũƒ–į0 Ã0üļ=jy?rˇ?Ψ;ŨcŗŅ„ĖîØŅųœe—*9ÖĄxé]=؈ŖŽ—ûzŽĶSū$ŖžÜͨ;ĩN-wˇX,:•ˇGm%wĮ1ËÖ äQĻGĢŒ:G-{wĖeŖ8ęĸŦÄQĶŖ†a†GČŧzÔúŽĖ˛b}Ŧƒ]=ŨÕ‡S™Ļįã1-žMôņß2ę˛,c]¨žwļŽäøNšú¤2jm§eû㌚5 Ã0ė‘?ÖŖ6:Oi&ošÕé\eYšßŋd§ë‡=jņĀ6‚βĖö¨UŲcą°ëÖŲ8ęĄ6Bč3rzÔMF­í´øę;5=j†aØ?ėQ×ĒJ}Î2åĨ‹˛VĶSķôTž˛úp¸<ž=Īéétz8äSĀzŊ2ĩ™ëŖÔZ.—G=×GšĻÂůîÚō$IĖ˙X,OO’xЁ#„>Ŗ{õ.ífÔöˉô¨a†aīüßzÔ˙MÚŽĢ…ĪNį✕iZœÎ•8ęŸ}öķsŧį~lĒ,VŲ|—PŽŲãū|Ô×ώĶ÷nÍéQËy‹šųfbŋ‚ú¤œĩšZŒ´ąĶô¨a†á1sņÁõų|ĩčUUįōnxŽ~~Ōãņd—_AũãędÔj&ę4˙9=Ÿëƒ5 Ã0ė‘˲ģfĸŅ;2jqÎįŗré§sąß§ĖǁęČö¨¯k&*;]ėô8<õe¨GmūС?w0 Ã0üVžfÔ§vFŋĢG}š\öûããĘ4B蟕ͨ—ÉZõŪ,A~.ŒŖîfԗę”Wô¨a†aīüÉ5B=–íQ_3jąĶŠn2jå¨ĪW;m3jĶŖnĪõaū Á0 ÃđûšÛŖnļŋ)Ŗļĸōęk{hgÔįRŧ´zœÄ8+GF‘ʨŗR9ę‹ķ˛RŽš5 Ã0ė?ŲŖFĄĮęô¨÷Måã–QGĶĒŌŽZ?R=gœčŒš5 Ã0ė‰?ÜŖļ"ŖFõÕíQëŒú ĮkFFâ¨Ķs~:éš8eE^¨Œš5 Ã0ė‘ŋĸG-×ĮqQŽV+;۞k:&ĄÚ•Ø]ũUíÔûũŪL^}ĐkÁ „Æ#§GŊn2ęĢŠn2jå¨O§<=]MuQV׌š5 Ã0ė‰}õ¨_—š;ŠŒûķ<“$1ģ˚‰Vōš`š\žôĩŲ"įY.fíuĩĀâņøö@ŊŦNú•âĨe_ŨdԥʨĶ,=]ÔCÜwĄ5=j†aØûęQģĢ>ÖzŊÚívƒģúļŲzėūųMjŊX,ÎįŗŲ’eŲ|>ņ2BĐ­G-&šŦ6ã5ŖŖĒŦNé%M/&ŠžeÔô¨a†aOėĢGŨ7Ã÷EŅŊE;ušĻqß;ŋŲ†ĄŨ"%ÜBŪe{ÔjŅ–˛>f*ĻÖcådÔUšf§“2Õ6ŖĻG Ã0 {äĪ÷¨]}ļũgŗŅ|QŅãƒūIŒz=ęÕĶuįlũÃBe{ÔK“Qk;mNē:éĘĮ霋ŖĻG Ã0 {g_=ęwdÔ­]}GŨdԕĘē”"2j„Æ$ÛŖžfÔS/ĘQˇæúhėôé\ĐŖ†a†Ŋķø{ÔI’ė÷ûÁ]lųl6s×:–-u¯GŊ GĐ˜d{Ô&Ŗ6vúĐʨÍ|ÔÅIO—f…:ĨG Ã0 {b_=ę×%×E‘™”ãņ\Žä01ĪōÜZÛiaķnĢæúXØš>ĖõĐ¨ÔíQëtÚ$ÕnF­ĩZŪEfÍÄK;ŖF!„>Šĸ×Ŗ6Õ|ÔY–-—KņĪQԝúÁŗäĻæķyLdtģŲĖGĐh5ØŖȨ/åu!rwÍDzÔ0 ð'6uúņ5k&"„úræŖŪ\ŠĘÚiąÖ׌:Š´žšj2j„BŪe{ÔcΨB˙ˆL:oÍG]™ŅÍ¨ĪNMFŊĸG Ã0 ûd_=j2j„P_ļGm2jk§ˇ5uúL!„Đˆô=j„Đ?"ÛŖ6õūŦL4cĢG}˜ëŖģf"#####ã§Æá55Bȇäƒ|“Q¯uF]ZSŨZ/AnįŖʨŨ”†a†ßČ͚‰i“Q_ˇ“Q#„>¯[:VŽēąĶÅŪfÔaÍÄ;ķQ322222~j^3‘Œ!äCNZgÔĻō‘šĩZö4M3cĒÅQ;uÚdÔ#J-`†á;=j›T*Ŗ– Šã8 ‚(ėÎGL”‚`Ē]‰Ũ5° yŗJŖÜīnˇ{}t„ĐĮtëQ̌ē6étģG­3ęô’Ļ×âGQVô¨ũŽnÚÍûŅxÖL”Ë‘7QŗËã5åâ—ËĨážaļ[ę/‹ë-"„|ŠÛŖ>ßéQgÔô¨a†a?ÜéQj†ę2Ú&ÆOĩ^¯vģŨāŽžmļ>šūNjMFĐåô¨WÃ=j“QŸ¯_KLĪ=jFFFFFīc§G7ÛߝQŋnhå Ôv9:ęäĖō~ĮņŊķwļQ#4B ÷¨ûõųj§ĶLĪõąĸG Ã0 ûäVZŪ’ŌÔŧŊ/ŖtõŲöŸÍFķEEwtŽŋk}{=ęÕ+=jûÜŋŊ„Đoë…ĩžēYŪ…ų¨Į0væŖ.åŨŠ,‹īˍ[ģúŽšŒĄŅëyZĩüÉĘJm§Õƒ5 Ã0ė;=jģrâxzÔI’ė÷ûÁ]lųl6“{ą? ˖Ÿ‹ōĨįķQ›ŒúR)S}QŽš5####Ŗ÷ŅíQsŽš°(ŠŽĮcũlŽWrØbąįÖÚN ›w[+2j„F¨įķQ7uz)YĶŖf>j†aØ+wæŖ.të#Ų|ÔY–-—KņĀQԝúÁŗäĻæķyLdė/(CFĐuëQ'ĻG]v{ÔS•Q§:Ŗ–Įņ\äeĩLV9=jFFFFFc§Gmˇŗf"Bčķęö¨ŗj0Ŗ>Š—ÖvZ—ĸGM†aöȝĩŨ>nj!ôčÖŖG]ÖÖNËčö¨MåC‡sŽ2ęXeÔô¨}ų¨ív2j„Đįåô¨7&Ŗ>(;-`3j5ĩĩĶōp2jzÔ0 ðîô¨ív2j„Đįe{ÔËd—ĩĩĶĮŦjeÔĻōa2ęâšQwzÔF0 Ã0üîô¨ív2j„Đįe{Ôņj#ŽúxŠÄK[ĩéQ+;M†a ĶŖFGļGm2jk§ģõĐ\ļGmūˆ$ĩ€a†˙ėQįdÔ!˛=ęVF­ĮŪ|ԕ™ņCĩ>Ė|ÔiĶŖvķ†a~?ƒõe\=jšĸ8ŽÃ ˆÂî|ÔÁD)&ĄÚ•Ø]̐ëUÅÉ/‹P)pGAÛvZyæÆTßæŖ–˙ķJĩY9Q[ę5éQÃ0 ÞØWúuÉNŖčp8ÔĪÖLTnyš4Ü_ĀÅlIâ8ÕA–°œSŒúģ¯!ôē:=jÕîĐqÔ­ŒZėt~ŨuwÍÄq¤0 ÃđŋĀ×uÚdÔÍöwgÔ&1~EëõjˇÛ îęÛfëąûįī§Ö5k‘#42uzÔÖNË(–:RõLõ9¯Oú!Û‹ĒŽuFM†aöÅžzÔ¯/EŅŊnFĮËûŠÍœīeÔŽäCÄb>ņ2BĐöĐęQĪlF7Ŗ6vú¤“j9LĻG Ã0 {äĪ÷¨]}ļũgŗŅ|QŅãīÆČŊõęiÚJîząXœĪįŋŊ„ĐīÉö¨ãÕē¨nvZÆVF](G­“jQ¯čQÃ0 Ã>ŲWúukWßQ;[䄱.Tŋx ĄĪČö¨“ÕĻ(›,Z?2jũ¸eÔô¨a†aO<ūu’$ûũ~p×[>›ÍäŽėÂ˛ÅōbąČ˛ąĖd‚˛˛=j1ÉEÕuÔŅtÖ˨k›QĶŖ†a†}ą¯õ뒋‰ĸčx<ÖĪæúp%‡‰m–į֍…6īļ2.—KsץąÉö¨“õVŦōÕ9ĩبƍéQÃ0 Þø+æŖÎ˛Ll°øį(ęÎGũāY§Ķi>ŸÁDFÛÍžļ¸.÷ģ/!ôēœõ5ŖļĻZgÔíuŅʨéQÃ0 ÞØWš5B}ŨzÔMF}}¨˙šI9ę[FM†a ûęQ#„P_ũu;ŖžŨɨ79=j†aØûęQ“Q#„úę÷¨íŖV3˙ĐŖ†a†ĮČ_ŅŖFũ#zÖŖž—QĶŖ†a†}2=j„ĐxtˇGm2jzÔ0 Ãđ(™5Bh<ĸG Ã0 #ĶŖFG=ęœ5 Ã0j3§tLBĩ+ąģV!×Ģ4Š“_,AČņI’Ü[ß!äEĖG Ã0 #ûęQŋ.šÂi‡úؚ‰â–—ËĨáūŌ-f‹<×YĸívkGANzS”-į\w2jzÔ0 ÃđhØWÚ$ƯhŊ^ívģÁ]}Ûl=v˙üũÔē~¸Ž9Bčķę÷¨íƒ5 Ã0ęPĪGŊ^¯Tė…lÚdÔĮŦÔĻZeĨ5=j†ax„ėĢG͚‰Ąž:=j›QËH†a-ûęQ#„P_õË5=j†aØ'ûęQ“Q#„úĸG Ã0 #E!ô¨×ŖžÚizÔ0 Ãđ˜™5Bhj†aø™5Bh<˛=ę8YeoÍÄ)=j†axŒL!4Ųu˛Ūå5…6zÔ0 Ãđhų+zÔrEq‡A…Ũų¨ƒ‰RLBĩ+ąģV!o¯Ō(GFQôöKGũ‰lZLrQĩõŖŒš5 Ã0ė“}õ¨_—\á4ЇCũlÍÄĶé´\. ÷él‹ūx„Đįe{Ô&Ŗvŗ(ĸG Ã0 ’}õ¨;‰ņ­×ĢŨn7¸Ģo‰­ĮîŸßM­~~ļÛíë׀úŒn=ę&ŖļĻZŒt4ŅŖ†a†GČžzÔ¯įÃQŲ.GGŒZŪOã8žw~ģE>;,‹?ē„Đgäô¨ˇb•Å3Ÿĩs>Ģ?T팚5 Ã0<ū|:ĐÕgÛ6ÍŨŅ9>čŸÄ¨×Ŗ^=íQËÍÎįsķ9ĸ Bȝú=ę×2jzÔ0 ðOöÕŖ~GFŨÚÕwÔz‹¸nķ? ˙Ņ5 „>#§GŨdԅ“QOéQÃ0 Ãcäņ÷¨“$Ųī÷ƒģXâŲl&wd–-u/!ĮT#4*ŅŖ†a†ŋ‘}õ¨_—\LEĮãą~6ׇ+9ląXČsëĻ8mŖi+ė4BcĶ@ē G Ã0 ŋb>ę,˖ËĨøį(ęÎGũāY§Ķi>ŸÁDÆÁečQ#46ũY:§G Ã0 ‚}õ¨Y3!Ô×Ũĩ™Úö¨õēäfĖˊ5 Ã0ė—}õ¨B¨¯į=jqԕN§Sm3jzÔ0 ð/öÕŖ&ŖFõÕīQ[S-Ž:T=ęHeÔYŠĩo5=j†aØE!ô¨ßŖļĒR­“Q+/§‹ŒeQÖqB†aöÉô¨BãŅ­G-Žēl;j“Q‡‘8ęô”ŸÎEz.ÄQįE¯Öô¨a†aL!4ŨzÔÉZuĒËŌfl2ęH˙ĪjųŠ1Õ6ŖĻG Ã0 ûbzÔĄņČö¨Õĸ-:ŖļĻēɨCqÔišN—Tį\eÔɚ5 Ã0ė‘ŋĸG-WĮqQ؝ÚŽ~Ē]‰Ũ5° š^ĨŅ]-Ņđįn!ôLļGŊLÖ⨏Y%^Ú<ĘJ9ę(ÔuzIĶË锋Ŗ.Ɗ5 Ã0ė—}õ¨_—\á4ЇCũlÍÄĶé´\. ÷­˛Ų‚…Fh˞=j“Q[;-ÖÚɨåOV&Ž:=]NâžmFM†aöÄžzÔ&1~EëõjˇÛ îęÛcëąûį7Šõ믋úŧlēɨKm§Ë[F}íQ_í´›QĶŖ†a†}ą¯õëYąŧÚ.GGŒZŪOã8žw~ŗ%Ō’'ÎfŗŖÎŊBãQ§GmŌévFmæúĐvZĩ> zÔ0 ðwū|Úí0ÛÄØ|QŅãƒūIŒz=ęÕĶĩUŽģŲ|AĄQŠ×ŖîgÔz>ęsqŌSįĨYÁ|Ô0 ðwöÕŖ~GFŨÚÕwÔŊ-rÚų|ūâe „> zÔzÍDm§Õ"/ĖG Ã0 €ĮßŖN’dŋßîz`Ëgŗ™Ü‘ũQXļtŽGŊX,^ŧ „ĐÔíQ7vÚdÔQ“QŸ›%ČŨ5éQÃ0 ÞØWúuÉÅDQt<ëgs}¸’ÃÄ-Ëskm§…Íģ­<×4=ÄN'qœec™!!T÷{Ôũų¨#Q7vZÆŧŦä`zÔ0 ðGūŠų¨Å÷.—KņĪQԝúÁŗÄ9Īįķ ˜ČhûŌâŽCũÍDãąBã‘íQĮĢĄ5mZU>ē5=j†aØûęQķ•@„P_nēĐk&ÚGkŽÕŖ.čQÃ0 Ã#a_=j„ęËö¨•ŖŽēŽ:šÎÚs}î\ô¨a†a_ėĢGMFęËö¨“ĩrÔįByi3>šzÅ|Ô0 ðOūŠ5Bč‘ĶŖžfÔÖTëŒúҚ‰ô¨a†a_L!4ŲĩͨÍã¤ĻíQŽ: C•Qŗ4UĻZõmÍDzÔ0 ð'ĻGú=ęvF=‹BQ‹NÅBĢâ‡Í¨éQÃ0 ޘ5BhÛūŗŲhrcwtŽú'1ęõ¨WO{Ôrļ$QukšëõzŠFhTz>õTgԗJ™ę‹˜ę˛(ë%=j†aØ+ûęQŋ#Ŗníę;jŊEŒˇŨ"Ļz>ŸŋxĄ¨5uŲZ3ąžfÔS“QËãØdÔKzÔ0 ðW:I’ũ~?¸ë-ŸÍfrGöGaŲ"°X,Ė×kí¨åĮW¯!ô~õ{Ôöáö¨SQ+S}.ōRõ*§G Ã0 ûc_=ę×%EŅņxŦŸÍõáJˇ,Ī­ĩ6īļfģškZMˇõJeÔG6=ęcc§åq)JqÔô¨a†aüķQgYļ\.Å?GQw>ęĪj杞ČčvŗÅTĪf3qé÷ĸo„/ŨzÔÉZĩĘĸ/jTŽēR­kFmėtVäØĸZÆ*ŖĻG Ã0 ûb_=jϰCõe{Ôņj“+GmLĩz4ĩęQ[;-•QĮ+zÔ0 ðGöÕŖFĄžlZMßQęvĮĨ’ņxŠĘJ9ę[Fu3jzÔ0 ð/öÕŖ&ŖFõe{Ô&Ŗ>j;­MuÕîQGzÔ0 Ãđhø+zÔĄDļGm3jņŌfŧfÔS3×Gytįú G Ã0 {ezÔĄņ¨ŨŖŽtFmĨÛŖVSįiS}d>j†axL!4õ{ÔĻøŅɨOųõëŠîš‰ô¨a†a_L!4uzÔĒA­'Đ;ļįú0ķéS—Ul2jzÔ0 ð'ūŠĩ\QĮaDaw>ę`ĸ“PíJėŽUČõ*öxûÜĪŨBč™:=j;ĩŒMF=Gm—}‘]EYĮ:ŖĻG Ã0 ûb_=ę×%W8ĸÃáP?[3ņt:-—KÃ}ĢÜß"ĮÛS!„Æ Î|ÔÚ3_G7Ŗnu%•Q¯čQÃ0 Ã>ŲWÚ$ƯhŊ^ívģÁ]}“l=v˙üũÔzą˜Ë]ŋxĄČö¨ÅQUD7̐G͙ɍĪEcĒ/•&Ž:§G Ã0 ûc_=ę×ëQŲ.GGŒZŪOã8žwūΖķųdFDļGŦˇĘQkįlüs7ŖnLu^ŠŖŪĐŖ†a†=ōį{Ôn‡Ų&Éæ‹ŠîčôObÔëQ¯žö¨­æsj„F'ÛŖžeԍŠÖõÔͨÍh3jzÔ0 ð/öÕŖ~GFŨÚÕwÔÎ–ķ‰€Ą1Ęö¨MF­ŸÁDFˇ›M@ĐhÕīQŋ–QĶŖ†a†}˛¯5k&"„úę÷¨Íãd2jzÔ0 Ãđ(ŲW!„úĸG Ã0 #ûęQ“Q#„úĸG Ã0 #E!ôˆ5 Ã0üL!4ŅŖ†a†ŋ‘éQ#„Æ#zÔ0 Ãđ72=j„Đx4ĐŖÎéQÃ0 Ãcį¯čQËÅqAvįŖ&JA0 ÕŽÄîX…\¯Ō(¨S…r˛ I’{ë›#„ŧˆ5 Ã0üėĢGũēä §Qt8ęgk&žN§eŗbņŗeąXėvģJkģŨ.Y<Ą1‰5 Ã0üėĢGmãW´^¯ÄîęÛfëąûį7ŠuĮ„?XĮ!ôyŅŖ†a†ŋ‘}õ¨/ î*Šĸ{ŨŒŽ–÷Ķ8Žīßl‘‡ƒÍ¨m܍Đ˙ąwļŪ­2[ū"ąČ‘‘ąH$‹ŒŒŦ­DFb‘‘‘X$2™ģaš)eH īm2°ÎķŦ.ÖS>ĻĐ{ßÃ>ûü2s€5Žã8žDŽÚkŖĪ&˙Ŧwę*vˇķļ‘­õvJŽzŊ^ë{!G 0+ČQã8ŽãKtW9ęWô¨˛+ę{:Ë2ųK„ø~ŋ§G 0+æ¨ëæ(9jĮq|ž>˙ĩŊRzR–o6y"ķ­¸ėš‘Ŗ˜7ä¨qĮņ%ēĢõtäf”R‡Ãá66×G9- Cšö֖ĶâúmEaúųŠ{ÔR¨3×ĀŦ Gã8Ž/Ņ1uUURúJũŦT>ę'WEį­dk˛Ųrm’05ĀL!Gã8Ž/Ņ]å¨Y3lČQã8ŽãKtW9jrÔ8ŽãøŨUŽš5ؐŖÆqĮ—č‹ČQĀ?9jĮq|‰NŽ惝Ŗ.ČQã8ŽãŗwrÔ0ČQã8ŽãKtrÔ0å¨ ]Q“ŖÆqĮgé‹ČQËÅqė{žōûķQ{ĢĪ[ųÍĄīųĨV!ŋ¯Ō˜eŲz­6›îhĀ| Gã8Ž/Ņ]娧#w¸V*ĪķÛØš‰EQ˜5íÅ_ôž˛(vģŒs9Ÿ›•_(Ēæ9jĮq|‰î*Gm:ÆŖėvÛ4MŲeŗŠąíņu×:ŠBĶĮŽĒ* ɡo€5Žã8žDw•Ŗ~ž€xĨÔŖĩÂ{=jyŸÆqüh|Ŋ'6ō7ŊGÄ÷ũɡ /‡5Žã8žDŽÚkŖĪ&˙Ŧwę*vˇķ={•ŖŪŽæ¨Ë˛”Ķôk7I’é­rxä¨qĮņ%ēĢõ+zÔ?Ųõ}՛ÍfŊ^ī÷{|âmĀ Gã8Ž/Ņ៪N’$˲ÁCOĘrŠ™å‰Ėˇâ˛§wŽ”Ö&%sāW9ęæët5=jrÔ8Žã¸+w•ŖžŽÜŒRęp8ÜÆæúč"§…a(×ŪÚrZ\ŋmåZ)¤o÷ûTÍe†@¸ũ&G­Ëéĸž’ŖÆqĮû"æŖŽĒ*Š"П•ęĪGũäĒĸ(‚ đŧU3KŪ=›-uĩŌ2”lYe`nLČQ7uųUN˙čQ“ŖÆä^û™"Ųúž//8ųīŽ//ÍŨnˇ^+ßķÖëõĮ‡ü•ŗrōŒŊũzĪ;īĮßæŽrÔTŗ`3!G­šuui*ęv[_ŽRQ“ŖÆ—åRNk?UUš~*ߗ×â_AY–ÚĨēβlģŨ:|Fŗ˙kĪ ~˙8ūįî*G `3žŖVëëĩ­¨Û¯cu9_nqBŽ_˜÷ēĩŸŸŸqk—˛Sjā0 ĪmˇYDNŽÂPÜ\+'øž/•ŗŲŲ_ęķÁũuķ’?Eí€2ėũßĻ›Ÿ˜ĻŠ\Ĩ”’‘õų‡<_¯×^ÛåÎö{=ÎÃûŲīŖ6iųŧ#mēÖæŸžåĒĶĐ8ƒ÷ų~Ī8ū6w•ŖĻG 6ã9j_IE},ëĸ<åĢ:×į{š5žīukĢĒ” Yģį­¤Ā֞$ÉĮĮ‡œ&Ûm’˜kˇÛ­\cęđŪøRK,E˛éT›sd@ŲjĪŲnŊß[­v2āé$‡ÖJéķå~‡ƒėĖķüë§<žšpôëN×zÛÜÆgũu[{œÁûœÉ˙v8>č‹ČQĀ?ÂxŽZŠöŦúX|ÕįËU÷¨ÉQã ō~ÆXĘÚ{GW ˯HÛmÖogПÅÍĩUUéN¯j āņeŠTĨ<‚@ŠRŗ_Î××ʰëûĩ÷Bˇüîm{§kr3ūŖû‘*bđuVŧģũ§mMËhrKö8ƒ÷ų~Ī8ū6'G ķaq|ÎNŽæÃ„ĩŊ^›u[T˙čQ“ŖÆ—ãĻ[ÛÎõ‘JyOzüčK™ŊÛíä[Ų&Ü˛|+W~||č:ŧ7ū6I˛ũ^ęmž,K99ŠB}ŽÉ'“ÁūŅŋģÍķüÔvšMÆûŅũ<Fŗ˙;GŊŨĘ_"ôāƒã ŪįLūˇÃņA_DŽēn˙Ķö=OūvܛÚü̐ßJĖĄUČīĢ4ĘķʧŊšŦķöCÍÍ'šŦĪo`rŽē‰|e-59j|‰Ū™ÚkæŖ.ŠÁ.ą™[#lįÖ0×JÁŦįúĐãŪø˛ķcˇ“˙XÚu”ūŖ>§™C#Šä‡ĘûnŸÁŖÛ7že{3ׇÔŊĪīįŅ3öö›.´ÜŧÖõāröųƒ÷ųūÎqüĨî*G=šÃĩRRņŪÆÖL”?‘ä?@íöâ/fßūQĐŊVĮĖ.-2‚ūYđ~&ä¨Ûš>t9]HE}&G˙kNŽĮgčŽrÔĻc<ĘnˇMĶtđ]6›:Ųŋ×ĩî^̧ôÔŽ˙>ņŪāo™:uynž*=×ķQã˙–ŗō ŽĪĐ]娟/ ŪE)e˛=z=jyŸÆqühüŪžîĩ:™ŲũvâŊĀß2q>ęļœžĢfûŊf"9jüßpVÄņúûsÔ_ŗåÜķĪz§ū bwÛ9ßŗŅX9ęí”ĩšvП˙Dx)rÔmút1 ‘›59jĮqܕģĘQŋĸGũã]Q?.›=j€y0žŖVJū[mËé¯ĸúģGMŽĮqwäķĪQ'Iōhū'eųfŗ‘'2ߊ˞G׆aP–ĨvrÔ™šŖū*§/ĮNš5Žã8îĘ]娧Sˇ3Ŋ‡ÛØ\]ôôrí­-§ÅõÛÖĀ\3dbŽúHŽĮqŸ“/b>ęĒǤĐmįÕėĪGũäĒĸ(‚ đŧ•líez×fYÆ|ÔɚŖ.ĪĮ˛næú¨Îä¨qĮqįî*G͚‰`3u>ęûäĖGã8ŽĪÁ]å¨lÆsÔ~gÍÄļ¨6k&’ŖÆqĮ]šĢ5=j°™Ŗnfæ9ĒãņÔ|•õwš5Žã8îČ‘Ŗ€„ 9ęļGŨ”ĶU¯GMŽĮqwåä¨`>LÎQWĮ6øņŖGMŽĮqwää¨`>L˜ēíQ—MwēņãĖ|Ô8Žã¸s'G ķaō|Ô_åôąjįú`>jĮqÜŠ/"G-wĮąīyĘīĪGí­]u9ŨíQ“ŖÆqĮ]šĢõôōU)e˛=z]eyŸÆqühüŪģ#MĀ9ã9ęļG}hÔzË|Ô8Žã¸sŽÚkŖĪ&˙Ŧwę*vˇķēVŽz;%GmŽĩGûČÄuSN7Klļõå%ۚ5Žã8îÎ]å¨_ŅŖūqČŽ¨éQĖž‰ķQįE}(ëŧ”íųtžHEMŽĮqwčķĪQ'I’eŲāĄ'eųfŗ‘'2ߊ˞į×ŌŖpÎhŽÚWëËõ*ĩ´Õz[ŸŋzÔä¨qĮqWî*G=šĨÔáp¸ÍõŅEN ÃPŽŊĩå´¸~ÛčQːŅĩįĢËõ–ĢŦ8eĮS^œž{Ôä¨qĮqGžˆų¨ĢNJĸH*^ĨúķQ?šĒ(Š jĮqÜš“Ŗ€ų0u>ęō|¨Î‡v{jfĪۑŖÆqĮ:9j˜ĶæŖžO—ûBäįķå&uMŽĮqwįä¨`>Œæ¨}Õô¨M9-RŸ¯ąîQ“ŖÆqĮų"rÔrGqûž§üū|ÔŪĒÁķV~s(1‡V!ŋ¯Ō(Ī›Ļiwöi)īõ|×2H’$×ëåÅÌæ¨Ĩ¨–Šē-§ŋŠęúŪŖ&Gã8ŽģrW9ęéČŽ•Ęķü6ļfĸ.Œĩ?YĀE*ķ0 ģ×ʀŨ5Í đfÆsԊ5Žã8>;w•Ŗ6ãQvģmšĻƒ‡ė˛ŲÔÉöøŊŽõ“YŽĀsԃs}ŖÆqĮ]šĢõô%ŋ•R&ËŅŖWúĘû4ŽãGã÷ö<*›åoALŧ7ø[˜Įq_ĸŋ?GíĩŅg“Ö;õģÛÎų;ÆVŽz;%GmŽ3 Ã˛,õDđWL˜ēY3QjéėøUT›5ÉQã8ŽãŽÜUŽú=ę‡ėŠzŦG-ŗˆĸH˙Ã18a4G­{Ôûc%u&ÛâtĒ/RQ“ŖÆqĮúüsÔI’dY6xčIYžŲlä‰Ėˇâ˛įÉĩōĖaTÕ\Ļ7ø7™Ŗn{Ô÷rZÄô¨ÉQã8ŽãŽÜUŽz:r3JŠî\æúč"§…a(×ŪÚrZŧ×|î^[…œđ¨ocjŽú^N7=jrÔ8Žã¸k_Ä|ÔUUéųĸ•ęĪGũä*Š“ƒ đŧ•líeē×vķØÚ˙ú `rÔÍ\yQįÅ)/e[“ŖÆqĮģĢ5k&€Íø|Ô~;ĩTÔe­'ĐûîQ“ŖÆqĮšĢ5€ÍÔų¨ĢËážČK;uŗf"9jĮqܕģĘQĶŖ›ŅĩîQK-­— —Ōē>_cŊf"9jĮqܑ/"G ˙Ŗ9j)ĒĨĸ>ž¤œžčm}šé59jĮqܕ“Ŗ€ų0žŖVJJkŨî÷¨ÉQã8ŽãŽœ5ĖrÔ8Žãø5ˇ‰ķQˇ}œõŒí\ä¨qĮq—žˆĩÜQĮžį)ŋ?ĩ™GÚo%æĐĀ*ä÷UåyĶ4íÎG}<Ã0ôjrÔ8Žã¸Kw•ŖžŽÜŒRęp8ÜÆæúč"§…a(×ŪÚrZ\ŋm Ŋš>tÔDÆ'G āŅĩīĢk3uŗŧKŗ-Īßk&’ŖÆqĮų"æŖŽĒ*Šĸ6äܟúÉUR$Aāy+ŲÚ Ę ÎG­įģ–_Č_?Lb4G-Eĩüzl– ŋ´Ûs}šé5ÉQã8ŽãŽÜUŽš5Āf4Gí+%ĨõŊœnžž{Ôä¨qĮqGî*G `31G}< ô¨ÉQã8ŽãŽÜUŽš5ØLĖQČQã8ŽãsōEä¨āaÂ|Ôįú Gã8ŽģrrÔ0ĻÎG}d>jĮq|FNŽæÃ„õ÷š‰Íļŗf"9jĮqܕ“Ŗ€ų0šŖö|_*ęũą’ŠēŲ§S}īQ“ŖÆqĮų"rÔrGqû÷ųĸģķQ{ĢĪ[ųÍĄÄX…üžJŖãøŅøŊ=Ŋkå/ēŋMĀ!rÔí\RK—uŪnÛš>˜ĮqwéīĪQ{môŲäŸõNũAÅîļsūÃ×ĘQo§ä¨ÍĩÆå7ūË=j‡üf>ęf‘—f…æŖÆqĮ]ģĢõ+zÔ?Ųõãõnˇ=ŖcĀĢÍQûĒY3Ҕ͞=™5ÉQã8ŽãŽ|ū9ę$I˛,<ô¤,ßl6ōDæ[qŲķčZŨåļ;įđfFsÔJ÷¨ĢKSQËļéQߤĸŽÉQã8ŽãîÜUŽz:r3JŠÃĄé!?ŸëŖ‹œ†Ą\{kËiqũļ5<ē–5€CFsÔŨuŗ•ŠÚô¨ÉQã8ŽãŽ|ķQWUEÍŧvJõįŖ~rUQAxŪJļö‚2Ž%G ␉9ęŧÉQ×:MMŽĮqwîŽrÔŦ™6ã9ę¯š>N_č}ÍõÁ|Ô8Žã¸Kw•Ŗ°™0õēY3ą]-1/š^˜ĮqwîŽrÔô¨ĀfÚ|Ô×ũĄÜĢũĄY9ņ{ÍDrÔ8Žã¸#_DŽū&ä¨Õå"ĩ”Ķe&EõąjR19jĮqÜĨ“Ŗ€ų0šŖÖ=ę4/Ûĸē’ĸúTŸĨĸ&Gã8Ž;trÔ0&ä¨MēŌ=ęúŪŖ&Gã8ŽģrrÔ0&į¨ŋĘirÔ8Žãø|9jšŖ8Ž}ĪS~>jŗĘĄßJĖĄUČī+!ĘķĻiÚ_3ņ>ŽöW? 2m>j3×GŨëƒ5Žã8îĘ]娧#w¸V*ĪķÛØš‰EQDQ¤Ũ^¨Åė‘Ę< ÃîĩŦ“0rÔõā|Ôu[N7Ûv>ę9jĮqÜĄģĘQ›Žņ(ģŨ6MĶÁCvŲljc{ü^įš×Ŗžx3đRĻÍG}kWKŦ›•‹ē]3qW“ŖÆqĮŨšĢõô"V5Dē ęõ–å}ĮņŖņ{{ē×Ē_öŦ×ëÃá0ņÆāĪy˜ŖŽŋrÔÍ'›UČŋĘiŊfbŦ{Ôä¨qĮqGūūĩÉ*ëÜ˛ŪŠ?¨ØŨvÎɰrÔÛ)9js­= ü6ĸ(*ËōˇÂhŽZĩ=ę{9Ũö¨ÉQã8ŽãŽŨUŽú=ę‡ėŠúqē‹üũ"‚‰÷ËÔõņ¤?œ(Ûīš>ČQã8ŽãŽ|ū9ę$I˛,<ô¤,ßl6ōDæ[qŲ3åZŠŪÃ0œxođˇLËQ_Ĩ–ÖSįu×L$Gã8ŽģrW9ęéČÍ(Ĩtŧųų\]ä4)ŒåÚ[[N‹ëˇ­Ą{­ ¨Ŗ&RN‹WŠ7Œæ¨ŋÖL<”ûCŲŽœXVfÍDrÔ8Žã¸#_Ä|ÔUUEQ$5°Rũų¨Ÿ\%Eržˇ’­Ŋ L÷Z)ļõ|zA°éŪđN&ĖGŨŦ™˜æ…ų:Õį0NjrÔ8Žã¸;w•ŖfÍD°ų‘Ŗž ä¨=ߗŠú3;ę/ЍĢS-59jĮqÜĄģĘQØØ9ęĸ—ŖöÕųrIīĩ|™59jĮqܕģĘQĶŖ›ņĩ×ö¨÷‡Ļ¨nˇÕŠŽtš5Žã8îČ‘Ŗ€„uŨīQ_.—TjéļœN;=jrÔ8Žã¸+'G ķajĮqÜĨ“Ŗ€ų0Ŗž}õ¨Ĩĸ>_öiŪ|ĩõŠ=¯&Gã8ŽģsrÔ0z9ęÂÎQ¯ŧĻĸūØgmQIE}:Į ķQã8Žã.}9jšŖ8Ž}ĪS~>joÕāy+ŋ9”˜C̐ßWi”įMĶ´7—uY–íäÕŪzŊÖĢÉĀûy˜Ŗ–ŠúúŨŖÎ¤ĸū˞ũA*ęúŪŖ&Gã8ŽģrW9ęéČŽ•Ęķü6ļfbQQiˇ1{¤2×뚘CzrŊjyUUR´ŋæQ`„ÁuŅÉQûēGŊK›Š:Íķnš5Žã8îČ]å¨MĮx”Ũn›Ļéā!ģl6u˛=~¯kŨŊ6Žã˛dåq÷LČQKE}Î?šŠ:ßįŲžéQĮä¨qĮq§î*Gũ|ņ.ĒYtø2x¨×Ŗ–÷ŠÔƏÆīíé^+?bŸĻëõzŗŲPZ8d4G­{ÔŲ.Íí59jĮqܑŋ?GíĩŅg“Ö;õģÛÎųž=ˆÆĘQo§ä¨Íĩ]O’D˙*dŠjWLĖQįŸûŧéQō&G}‰ÉQã8ŽãNŨUŽú=ę‡ėŠúqZĒqãRTA0ņŪāoé娋9ęėcŸ§ySQį9jĮqÜšĪ?G$I–eƒ‡ž”å擆ũŲÃG׆ahN–߆ųx#ŧ™‰ķQįi–§MƒZ*ęĻGŖÆqĮ]ēĢõtäf”RzFģįs}t‘ͤN–kom9-Žßļ†îĩr( ƒËå"ã||0W6€+~ä¨/ßK÷sÔ÷ČGž—§ú';rÔ8Žã¸C_Ä|ÔUUEQ$5°Rũų¨Ÿ\%…q;ÅôJļv‘ÜģV*đÍfŗVjŋß˙éŊĀ/č娏§~ŽZ}õ¨›ČĮ!/¤ĸ6=jrÔ8Žã¸+w•ŖĻ 6ũu'õqĶ9jĪģ^ڊē|ä‡ōģGMŽĮqwäŊĩéTĪĒG ˙Ŋõą­ĨŨĩī_.—ƒ”ĶYq8”RQ×į‹TÔ59jĮqܝwsÔm“§Đī#zÔđ~†sÔu'GíIE}Íŗã!oĘéüXšÔ9jĮqÜĨ÷rÔōíårŠg–Ŗ€„~ŽēÛŖÖ9j_IE}8”‡Cu8žōãŠ>_uš5Žã8îĘû9j]WĶŖtsÔĩŨŖ^¯}_Ii}8V‡âÔ~Õ§ķ…5Žã8îÖä¨Û6Õī#zÔđ~~ä¨/÷õé;G­ÔúrŊËZjéCy>”u}š’ŖÆqĮŨzo>ę‹N}ĐŖØ9ęc/G­Úuy>V—ļĸ>7Ší9jĮqÜĄ÷rÔfåÄYõ¨åŽâ8ö=Oųũų¨ŊUƒį­üæPb ŦB~_ĨQž7MĶî|ÔŨqôöÕOƒ ÎGũ#GŊۈ4åtuŅÛúr‹ˇä¨qĮq—ŪÍQ›š>ÎsZ3Qng­Tžįˇą5åæÍâöâ/fTæa>ZoQ1ãĀ›ĪQĢĩˆ)§›ŠÚô¨ÉQã8ŽãŽŧ7ĩŧŠÚÔĮË{ÔĶûĀģŨ6MĶÁCvŲlęd{ü^×úŅz‹AČãOŧ7ø[ú9ꓕŖÖ=ęĶUŨ{Ô59jĮqܝ÷rÔf˙Ģ{ÔĪīĸ”2YŽŊ>ŗŧOã8~4~oĪ`š5€[ē9ęz8GŨö¨īåô59jĮqܑ÷rÔf˙ëzÔ_YåÕÄ˛ū bwÛ98žqČQo§ä¨Íĩö€aĘīáˇO…•Ŗž~÷¨MŽúÖė?üčQ“ŖÆqĮ]z>ęûū%ö¨˛+ęąuY–ĻÅ N°sÔf‘—9ęÁ59jĮqܑ÷rÔf˙|rÔI’dY6xčIYžŲlēŨfqŲķüZÔÎyŖžZ9ę9jĮq|>î*G=šĨÔáp¸ÍõŅEN“ YŽŊĩå´¸~Ûz×Ō ˜rÔĮŸ9ę#9jĮq|Nūūõ ĒĒ(ФVĒ?õ“ĢŠĸ‚ĀķV˛ĩ”é]+ãËOųĶģ€_ķ G}ĩsÔGrÔ8ŽãølÜUŽš5ĀæaŽú4œŖ>žČQã8ŽãîŨUŽĀÆĘQ_GsÔgrÔ8Žã¸kw•ŖĻG 69ę9jĮq|5ü#Ø9ęãƒų¨ÉQã8ŽãķqrÔ0~;59jĮq|NŽæÃŖõąÛŖ&Gã8ŽĪĖÉQĀ| Gã8Ž/Ņ‘Ŗ–;ŠãØ÷<å÷įŖöV žˇō›C‰94° ų}•FyŪ4MģķQË/!‰cĪķdųA=€Wķ GÍ|Ô8ŽãøŦŨUŽz:r‡kĨō<ŋ­™XEEÚíÅ_ĖŠĖÃ0ė^+WĨéįĩEŠm3ŧæŖÆqĮ—čŽrÔĻc<Ęnˇ•*wđ]6›:Ųŋ×ĩî^ÛĢĖũĮ‹›ĀK!Gã8Ž/Ņ]娟/ ŪE)õ(†ŅĢ„å}ĮņŖņ{{ē×&I’eŲ­í~~&I<ņŪāo!Gã8Ž/ŅߟŖöÚčŗÉ?ëúƒŠŨmįü‡c+GŊ’Ŗ6×—ĢÖëĩG„5€+ČQã8ŽãKtW9ęWô¨˛+ę§=ęũ~¯sÔY–™F7ŧrÔ8Žãø}ū9jɰyR–o6y"ķ­¸ėyt-9j€™@ŽĮq_ĸģĘQOGnF)u8ncs}t‘ĶÂ0”kom9-Žßļ†Ū\ŸŸŸĻGEáKžÆ Gã8Ž/Ņ1uUURôJ ŦT>ę'WEį­dk/(ĶËQK•Î|ÔÎ!Gã8Ž/Ņ]å¨Y3lČQã8ŽãKtW9jrÔ8ŽãøŨUŽš5ؐŖÆqĮ—č‹ČQĀ?9jĮq|‰NŽæÃÄõW9ŨlÉQã8Žãî5ˇŠ9ęš5Žã8>#'G ķĄ›Ŗ>“ŖÆqĮâ‹ČQËÅqė{žōûķQ{ĢĪ[ųÍĄÄX…üžJŖw•ŖžŽÜáZŠ<Īock&EE‘v{ņŗG*ķ0 ģזe)…ēînAĐ[]ŪÆīsÔWrÔ8Žã¸sw•Ŗ6ãQvģmšĻƒ‡ė˛ŲÔÉöøŊŽu÷Z)°å— ]ĒkV!p…•Ŗž‘ŖÆqĮįīŽrÔĪīĸ”z´,x¯G-īĶ8ŽßÛĶŊ6SQ žīOŧ7ø[rÔõŗõš>ČQã8ŽãŽüũ9j¯>›üŗŪŠ?¨ØŨvÎ÷ėA4VŽz;%GmŽ5.?qˇÛÉīA.O’äÉO€—ō G}#Gã8ŽĪŲ]å¨_ŅŖūqČŽ¨÷¨omØ#6ōŗ˛l/ۉ÷ËÃuMŽĮqŸ¯Ī?G$I–eƒ‡ž”å›ÍFžČ|+.{Ļ\+Ĩĩųä#ŧ™G9ęâĢĸ&Gã8ŽĪŅ]娧#7Ŗ”Ō“Ú=ŸëŖ‹œ†Ą\{kËiņŪ ŨkˇÛ­ŌúĖ ØTÕ\Ļ ø× Gã8Ž/Ņ1ĩ”¸QI ŦT>ę'WEį­dk/(ĶŊVĘo)¤eüöLĻÎp9jĮq|‰î*G͚‰`CŽĮq_ĸģĘQؐŖÆqĮ—čŽrÔô¨†5Žã8žD_DŽūČQã8ŽãKtrÔ0ČQã8ŽãKtrÔ0~䨯ä¨qĮņe89j˜ŋËQ7[rÔ8Žã¸{_DŽZî(Žcßķ”ߟÚ[5xŪĘo%æĐĀ*äí*RÉGač7xŨķķ<_ˇ˙u’$æßxR–o6y"ķ­¸ė1†Ą N ačfõ5€Sä¨osÔ'rÔ8Žã¸{w•ŖžŽÜŒRęp8ÜÆæúčĸ§īko÷ZŋmeE‘~ęŪÉĖõāœ˙’ŖžŖÆqĮû"æŖŽĒJ ]П•ęĪGũäĒĸ(‚ đŧ•lM6û+ÅŨÉrëũY–15€s~ŸŖž’ŖÆqĮģĢ5k&€ 9jĮq|‰î*G `CŽĮq_ĸģĘQĶŖrÔ8Žãø}9jøG Gã8Ž/ŅÉQĀ| Gã8Ž/ŅÉQĀ|˜”Ŗž‘ŖÆqĮįåä¨`>ŖÆqĮ—č‹ČQËÅqė{žōûķQë9Ĩ=oå7‡sh`ōv•FŠäŖ0ôŧîųōKHĶtúJŽđ ~•Ŗ.ČQã8ŽãķpW9ęéČŽ•Ōë>_3ąŠ–ŖHģŊø‹Ū“Äņąmd‰Ë˜R¨›ĄÂ0|žd ŧšßä¨oä¨qĮņ™¸ĢõônđnˇMĶtđ]›ÛßîZßŦš|đxŋËQ×7rÔ8ŽãøÜUŽzz7X)e˛=zõ°ŧOMĪųQē‹ü%" ‚˙vWđ ~‘ŖŽÉQã8Žãsņ÷į¨Ŋ6úlōĪz§ū bwÛ9ßŗŅX9ęíhŽÚ O†aY–O΀7ķrÔgrÔ8Žã¸kw•Ŗ~EúĮ!ģĸîė‘ã6PũŸī ^9jĮq|‰>˙u’$Y– zRo6y"ķ­¸ė1†aU <9jˇü6G}īQ“ŖÆqĮ]ēĢõtäf”R‡Ãá66×G9MĘfšöv/ĄõÛVļQé§ļĄG ārÔ8Žãø}ķQWU%e°ÔĪJõįŖ~rUQAxŪJļ&›ũ•âîdšģ—Ŗp 9jĮq|‰î*G͚‰`CŽĮq_ĸģĘQØü&G}+ČQã8ŽãķpW9jzÔ`3=G]ŖÆqĮgã‹ČQĀ?9jĮq|‰NŽæÃ/rÔ59jĮq|.NŽæ9jĮq|‰NŽæ9jĮq|‰žˆĩÜQĮžį)ŋ?ĩžSÚķV~s(1‡V!oįš–J> CŋÁ3į7;ÛųŽeo’$×ëåM?!Gã8Ž/Ņ]娧#w¸V*ĪķÛØš‰ē0Ön/ūĸ÷$q|lYâ2ĻęˇvĄķfx3˙%G}!Gã8Ž;vW9ęéĢîvÛ4MŲeŗŠąíņíŽõíÁ:æO7€—BŽĮq_ĸģĘQ?_@ŧ‹RĘd9zôJ_yŸęžķāøöųKDöÎĀÚ ī5Žã8žDŽÚkŖĪ&˙Ŧwę*vˇķvŒ­õv4Gm§ð,ËŪiƒ;ā=ŖÆqĮ—čŽrÔ¯čQ˙8dWԝ=2`ÜĒģ'HEQo'ŧrÔ8Žãø}ū9ę$I˛,<ô¤,ßl6ōDæ[qŲc< ÃĒúņtōĖaôvĀ›!Gã8Ž/Ņ]娧#7Ŗ”ęÎÅņhŽ.rš”Íríí^B롭lŖ(ŌOm(ŠBNxÔ €ˇAŽĮq_ĸ/b>ęĒĒô|ŅJõįŖ~r•ÔÉAxŪJļ&›ũ•âîdšo?ķØÚ_ü@0 9jĮq|‰î*G͚‰`CŽĮq_ĸģĘQؐŖÆqĮ—čŽrÔô¨†5Žã8žD_DŽūČQã8ŽãKtrÔ0ČQã8ŽãKtrÔ0ČQã8ŽãKtrÔ0ČQã8ŽãKôEä¨åŽâ8ö=Oųũų¨Í<Ō~s(1‡V!oWi”J> CŋÁ3įĮ0 =Ī“ŊI’°Ô €+ČQã8ŽãKtW9ęéČŽ•Ęķü6ļfbS-G‘v{ņŊ'‰ãcÛČ—1ĨPŋĩ ë/–Z:MS3ŧrÔ8ŽãøŨUŽZwŒ§°ÛmĨĘ›üŗŪŠ?¨ØŨvÎØ1ļrÔÛŅĩAž: Ã˛,ģ?HxRĀĀĢ™šŖnkiŊ%Gã8Ž;wW9ęWô¨˛+ęÎ0nÕŊsdžeä¨\15G]]šĩlĢK}&Gã8Ž;öųፓ$ɲlđГ˛|ŗŲČ™oÅeņ0 ĢęáĶųä¨1žŖ–Šúz+ÚZZžÕÅô¨ÉQã r¯ũ÷ÚvŽ)/Š";{ų˙xU•ģŨnŊ^Ëø˛ũüø¸˙3ôģŸąˇ_ī™ÃīĮ˙Ü]娧#7Ŗ”:ˇąš>ēČiR6Ëĩˇ{ ­ß?ģôSd@5‘ņÉQ8dŧŸũ>joøÉ3֝Žĩy­GÆŧĪ˙đ{ÆņˇšĢ5k&€ÍxŽēíQŽÕņx:§ŖTßį‹TÔä¨ņey¯[[•Ĩ´ÚŊÕJ líI’|||ˆČVÜ\ģŨnåĸNūc|ЊĨ N??M§Úœ#ƒHņ,˙ŊäMã:Ņû=oĩk”CëĩŌįËũ‡æĖví†į÷“67<ōŒu§kŊmoCßjĶ?ˇÆŧĪ™üo‡ãƒî*G `3žŖöÕår=Ģύ.šĸú|īQ“ŖÆäũŒąŌ÷ŽŽ–EQhWž/…n{ŧRĘ7×ę´l•RƒãËRŠĘŅ LÛšnS”úZyé7Ås‹üÄûĩ'ãR“ėv•ŽÉīã?ē}Ãö3ęŦxw[ßģč§ö6d ķŨqīķÉīĮģĢ5=j°™ŖöÛĪS”獖ŠúģGMŽ_Ž÷2ÆUuīQˇcŗ_ŠLŨī:é’ÛēÖŗ˛Ę]—Ģô§ŠšŪīũ|“{4×ļręyijōÍæpČ'ŪĪ“gŦ;]ë¯ûkūÛáį˛īs&˙Ûáø /"G ˙ã9j_]/RQWmQ}:OÍ'ÉQãKs;GŨ†(:ã&zq2íhß7מÚėąéQ?˙Yō~÷}Oģœ/ßöΚē}×4™í{ųŅũLyÆēĶ™—kīÕČđųƒ÷‰ãsvrÔ0&ä¨ŋzÔmQŨö¨krÔøōÜtkuzY ČR~vŒĨĖŪív"˛íæ–å[šōķ+ĖÜģM˛ũ^ŠŪS›Đ–“Ŗ(Ôį4ųäĪĪ˙ĩwŽlĒëPū Gb‘•HėH$‹D"ą#‘ČZ$‰­ŦŦŦE"+įŦdĩ!ŊA™=3Íėũ~OOÎģ;ŊQŌduõkųįĶÉy°ëYņ’å¯ĮãQŗÜQåņî;žĮŸŅÍŋû¨7›w›6ˇãw­ÚËwg ß w2>j„P8zęŖžZĩņ{H8}Ύ^Ž5ü‹ø>õĎGíųũ,qcl ˇŽĖS;ÖGVÍôˇ/3wģílé›ˇĢŤ¯—ŨÉNgQ$Ņ™~VÜąüՍõĄŽĮĶ÷ķ]Z"}‰“uã‡ę0üå;sāš…áQ5B( ē8žŌãY§,ËņQÃ˙÷ų–a‘Ã÷QßnˇŨn7Ÿ›;åų|žßī?ą‘á?zî/ЉŽĮã'vŠzUO}ÔĶ(ēID}NcT—9ęåz“ãŖ†˙æ—a8@ËG=\‹Å›ûIËå"öĢyđëŠ}KJŊ\.oVËÅB´!ô­zęŖžLMD}8%QNi|Nŗü&5>jøßa~y†äą|ԓÉФqE.ĸnČÄēúŖK6ôuķwģ­ūö“Ë-ģĖŗ|XYXį ¯ĒÕõwĢ‘|LšX,’$ŅĩŒsėíí!ô’j>ę[—Ú4QK8Ø2ÍŽˇåĘä¨ņQÃ0 ÃcņX>ęá6 é%įķyĮí¸zģŨ)wÛ­Î|ßī÷!ËÍÂļšŠ™gûĢĻoRēÕO'Zûyo—Ŗ–˜ÜíHļæ˙!ôMō}Ô9ęh6™NoEą?J8Ø2MķĢDÔø¨a†áųį}ÔnĖvÍëL}QŅ/}eöOŖ(Z,š†REŅTsËRĘ_uæl6kĮŪ˛¯Ķéô6ŸûrŲoY]ÖrKVĮ9iláĶ!4Pƒ|ÔˇbJ5œŪŸ’,ŋ-Vø¨a†á1y,õįÔ$IVĢUĮúO?čuÜé'‘ŋnˇÛÕr™ĻŠ?Ķ_ä­ŪŖ–čũŒzI}ÔNīm8­9ęÅ 5 Ã0<&‡īŖnČ7`x9ęÛĶõ‡õ]/ ÷WYEWo/ųaۈŦû¨ø¨úv õQŸ%œNĩĖŽeŽ5 Ã0<åŖŽív+Ĩ†ÁRî÷ûÕjåū¤ƒéIšmų¨åŖm6é2Ī/—KåŨv̝(ĘLˇäÛۛ|ü;և†ßú2#c} ôz:u™ŖŽÂi)S|Ô0 ÃđØūxÔrx$Û_núo6‹vģK,÷õ!ŅußX6TÖ×e;ëõzZū T0˧ŸĪfK05B?ށãQK }°áôĄĖQ›bÆG Ã0 Åcų¨ųÍD„P[C}ÔÆA]NŠÉQ¯ņQÃ0 Ã#ōX>j„jĢæŖ.ē|ÔSúr8^¤Ü/™y3Ņä¨ņQÃ0 ÃcņX>jrÔĄļø¨§&ĸ>žeÚORĻYž\­đQÃ0 Ã#rø>j„ĐŋŖį>j“Ŗž$–Žļę†äh§ĶО—Ŗž=ÍQXßõbąp•Utõö’į „žIO}ԚŖ>ŸLvZËk•ŖÆG Ã0 Åcų¨‡kģŨJGŠa°”ûũ~ĩZš?é`zRn[>jųh›ÍFgē$ēîëC$3õ5FŲÎzŊžN&˛ņĶ餕O?ŸÍ‹…[ž5B?Ļį>ęhZhŽútĐRŦÕ 5 Ã0<&‡?5BčßŅPuNŸMD]å¨ņQÃ0 Ã#ņX>j„j뚏ZsÔᨄĶļŧ^3‰¨s|Ô0 Ãđxž!ôīh¨ē §ĨĖë5 Ã0<&‡īŖFũ;äŖ.lŽúŸíärÔø¨a†áą5B( đQ›ˆZÃi[s—ŖÆG Ã0 Äø¨Báhē¸/G‰ĨmK†a—ņQ#„ÂŅĩē>L8}˛ežgĢõ 5 Ã0<"ãŖF…ŖĄ>ęËÉ$¨mérÔø¨a†áą5B( ķQ'NŸLD}Ę]Ž5 Ã0<ãŖF…Ŗ>ęčfrÔgN›RBh‰¨s|Ô0 ÃđxŒ!ކų¨‹“‰ĨËIš­Õz†a‘ņQ#„ÂŅut“ˆ:šœ.—S’H™›õ:ĮG Ã0 Įø¨Báh€:*ŠâxIĘ)I2QãŖ†a†Gd|ÔĄpôÔG=ĩ9ęc’/Š–ųíļ\or|Ô0 ÃđxŒ!Žø¨%ĸū8^˛c’ĮļĖŽˇÕzƒ†a‘ņQ#„ÂŅSõ4š™ˆ:šē)ŋËõ6ĮG Ã0 Įø¨Bá蹏:Š$´ŽM,}Ķ2욈5 Ã0<"ãŖF…Ŗ>j›ŖN ‰Ĩĩ”Ā[sÔø¨a†áą5B(=õQOĻ&ĸŽm8­evũXnvø¨a†á5B( õQ›ėtaĘ´°9ę]Ž†aņQ#„ÂŅĩŸŖ.lŽēˆ5 Ã0<"ãŖF…Ŗá>j §Ĩt9j|Ô0 ÃđXŒ!ކû¨%–ÖŌø¨×ø¨a†á15B( ôQģpÚDÔÕXø¨a†áą5B(=õQkŽúp1ᴖ)>j†axlÆG G}ÔNl™15 Ã0<6ãŖF…Ŗ>ęHsÔnēį¨ņQÃ0 Ã#1>j„P8čŖ>ø9ęëĮ5 Ã0<*ãŖF…Ŗ>ęŊÍNk™æ…DÔø¨a†á5B(Õ|Ôˇ^õáR¸ Úå¨ņQÃ0 Ãc1>j„P8čŖváôŪú¨ø¨a†áQ5B(ĩ|ÔEˇē´|eŽz…†a“ņQ#„Â>j†aø72>j„P8jú¨ŗuRÔrÔø¨a†áQ5B(ŊęŖ>āŖ†a†`|ÔĄpÔíŖÎ†ašņQ#„ÂŅë>ęÂæ¨ņQÃ0 Ãc2>j„P8ęņQmu;G†a‹ņQ#„ÂŅ@õĄz-Q}ÔK|Ô0 Ã𨌏!ŽđQÃ0 Ãŋ‘ņQ#„ÂŅ@ĩÍQ—“ÍQãŖ†a†Įd|ÔĄpôĸú††aņQ#„ÂŅ+>j?G†a“ņQ#„ÂŅ`õ­ŖÆG Ã0 Åø¨Báh€ēžŖNđQÃ0 Ãã3>j„P8čŖŽ­:Öõíc‰†a•ņQ#„ÂŅpĩ†ĶRfø¨a†áą5B( ôQĮI!ąô15e^å¨ņQÃ0 Ãc1>j„P8zęŖŽ"ę› Ēī9j|Ô0 ÃđHŒ!Žú¨%.§¤°9ę]Ž†aņQ#„‘īŖîČQGåo&J ­–›ŖūXnđQÃ0 Ãc2>j„P8čŖŽrÔ\Ž5 Ã0<ãŖF…Ŗ!>j8)-ø¨a†á5B(ĩ|ÔÅcuŒ†a€ņQ#„ÂQ‡:ëđQĮ•ZĀø¨†a•ņQ#„ÂQÛG}îÚf§ĩĖ†a›ņQ#„ÂQ¯:+}ԚŖ>ØėôĄĖQãŖ†a†Gf|ÔĄpÔįŖ>ˇ~3ņ`~áÅÕ.G†a‹ņQ#„ÂŅudrԗ›DÔZĻ.G†a‰ņQ#„ÂQēhø¨]8-evũXāŖ†a†Ge|ÔĄp4ĐGŊ—púrŗĨÉQKD†a‘ņQ#„ÂŅ@u™Ŗž{/G†a‹ņQ#„ÂŅ@ĩ—ŖžŲ5>j†axLÆG GC}Ô6;}HĒõ 5 Ã0<&ãŖF…#|Ô0 Ãđod|ÔĄpôš:ÁG Ã0 Áø¨Báču†a„ņQ#„ÂãQÃ0 Ãŋ‘ņQ#„ÂŅ+>ęûo&âŖ†a†Įe|ÔĄpôĒÚä¨oø¨a†á‘5B( ôQģpZĮúXâŖ†a†Ge|ÔĄpô’:ÆG Ã0 ‡Áø¨Báh Úf§oÎGŊÄG Ã0 Ęø¨Báh :îë5 Ã0<ãŖF…ŖW|Ôeš5 Ã0<:ãŖF…ŖÁ>ę>j†a8ÆG G|ÔõurÃG Ã0 Îø¨BáčuRå¨oK|Ô0 Ã𨌏!Žú¨cNĢņã–áŖ†a†Įf|ÔĄp4ĐG'…ÄŌĮԔy•ŖÆG Ã0 Åø¨BáčŠēŠ¨o.¨žį¨ņQÃ0 Ã#1>j„P8čŖ–@ēœ’Âæ¨w˛Š>k+Š›mÄBÉZĀ0 Ã˙ßn7›Ûš4æ“ŖFũŧø¨mDÖōQØõĮrcrÔI’Įëõ*ÍÚ՚?ÔÃ0 ÃßĘÚīH$=QÃGŊÚ§Ë=9j„ЏJZžÕ>ÉŊu§ēĘQĐõÕ´[éå|Žã8MSמQRRRRR~w)ũŽô>Æō!P=ƒ}ILĻHzˇũ){?e””””ß]ŽÆōqIŗüĄē°o&ÖrÔÆõa֒6ílƒęøp Ã0 +k8-}Ÿv|IŌõ!íéãf[[Â0 ¯ö‰ §ŗ|€ÚÕÎG­Ę˛LÚ´Ë咈l Ã0 ÃßĘŌīHį“×ŗĶ0 ÃŖŗįŖ.Ú>j3Ї:O ĖQũ–7 Ã0 Ã0 ŏ}Ôvč< Ē ũÍÄî`†a†a8nŽõQų¨5G]ūy™Ŗ.œ:œ;†a†a—9ęs+Gí‚ęŦúÍÄ|ėģ†a†a‡›>ęĖų¨#“ŖžÜ$–Ö2Õuw0 Ã0 Ã0ˇsÔ7/G}H N›2ģ~,đQÃ0 Ã0 Ãp[>ęÂ÷Qī/&A­ez-Æõ1ū] Ã0 Ã0 ‡Ã}ԇKá‚jÍQÛk‘ųņx|{{›L&ĶétĩZĨI"3“$Y.—:S@Gã×%§VfÉ4 įΆ_.­ŊQYQS˙ß˙ÉL-]m×ueNc;:įÁžöīī3ŖhŋßģųrČ–Ŗ(ĐÅN§ĶbąøąĪ˙5,5PjQcūûû{ģŽŽČĻS˜ĪåäJ8Ė…ā_eZęÂrär]Ėgŗ8>tn3˲Ũvûāē“­ÉÕԘ/›•ųƋśBûJOlŋÖŪĻėtEŠ÷ëĮ8– ļZq™ų}_ĢŨđˇy:įķų´<=Ÿ1M7›, glģŲTŋÞËöŨ÷îŽŋė̃īÜæ¯`9ÃÛíVZK=?ģŨ.ËŌīŪoglĶųũúë6j¯”Ÿ¸6eŋykk‘ũ6ĩ*vs&ˆ<˛‹…‰¸äP'Ž#kŦ+ŊœU9ĨŌõ9ÕļÖEe­Û–ĩîŗĮų7ķcĩ §÷eŽēÃG-§.Žcķs/Rį7Ķûg™œgŠ˙åĖíVOûrš†ÂŽĪŸÉ÷hã„Pî,`X9š\¤]’æEÃHjŦÔp]FZ ˇŧHĒũÂTl3§ę”īÛ)įôėKęŋ\ŌDË.&¨6ķĨw>K¯:JÃĨËK'+~8įū-,5PēQžTZŠNíē:KE—znĸÜ,“:oŽ6Ë&æđšËKŗ^¯åb‘nzf‚ę¸ŊŒôær)=¸îäúu×r5ߜ˙ēÖųū1Üģ°ŽmĘ‘Ø“|Ÿ/ģF#ŗÍGÕÍĩ{ɡÆvÎöTœĪ§ĖœŠSc›yu(LÚX%“–Jb°rÉú÷îXÎ×nˇ•{–Ņŋë?d9˙ÕMMĻ?C/1Ūwīˇ3ļéü~ûļcLJŠrŸŋ6ũú,Õ_žũš~›­mĘQ5ļ)W܈¸ęĮyĐnČ^VŌ—in§ąLę×ēŦĒuÍëč…ãüģų‰ē´|eŽzõĖG-mŅdr-tY^ŨÅO[Y‘ÜKâ]ë3ĨĘŨŸ~Ņ:SžŦØŪæö^UīĨô2ŲfŗļÉŊŠËøÉ_5͏\,܍Ûéxt7øšëČí­œmW'ķšI¸Œ‡ėbjwäîČė’3ģä\ĶŨįūĩ,ˇ´Ë4˛ šW‡Û™ąvļÁ_WfjēL$éÛÛÜÛNfÁėKZ<9ž/˙ŒđŋĀR—$ę¸\Înž´ĸ¸ēꡐŽ-•j68šNĨšS–ĻÕąeģĒ-°Žåö%}ˇTcY¸ęeŦ2›7ËR—AõSöŪÎÔšŒą?_BPˇ¯“š^:ŽŠÆu×>'˛åívs>ŸŨ|sBļ[w]ģu›ĮPõkímĘŠ0¸=ŋdģbc~žgížONÅŠz,ÕwüŌĮÉ×ҎŒ;æF[ÔŲ˙ū:–*˜ļ˛Á2ĮÔˇŠå>Č-ŲōũJÔîŪ=õëŒ1:ëg;ë@×÷ÛhįũúæôôÚl×Éę(˛é´û)ŒÄĪŽOé<žöņËÉqOmÜeÕXFŽķũ}סÍOįßÍ/ø¨ķ'>jķĐmˇŗIs÷^eų¤ÉjerŠfæĻĩų‚ä”ÕüģošRʖ$Ë4āÉížl°a֒Ē({Ņ[­uuwŋY¯÷û÷ÜæÍũŦŨž\GRsäĀäúrÛŸÚž#ŋĪ4KŽÍę{ŗ¤Y}­Û”žÅ-šļ•öy€5KeĐP{?sĨ9jSą[wčy+SÔŪ—´?æ6­ŧŖOe§Ę~ŽÚfō]Į6Ÿ,É=õëŒ1ƒÛøķÛ߯Īō=J”ۚͧ×fģNæö‰ÃũÛŦoSŸŨ<ø,ÕĄÖæOī÷)æ3Vņ|m™{­{Ø'<Îŋž˙ÜGíîPä”ÎĒ“¯O ÔWŖ63ųL/„VvBˇŖŲwûŠwßæÁˇeiy¤EĩB˜ęēū.üģZMMgļ-Ōųæų…ÄŪu_™{¨áOTmĶ6印@õĐ­û<ĀŋMĢR^ Y ß]ŠĄ;īĐķg>jŨ‹—=(īô6yĸ>ꝕīŦáüĀŋ…ĩZCŖi<ĨKÕøÁÕUi?ˇwļ…ĖŊĩėŧĖ] ^oĢ-°)Môtęöušœ¯ö‰~å:_:q‰\kŠJÔ'ב\M.‹h—‘ á"¯Ü]æöŲâzŊ2)š$‘OᮗÎmöíômmđËų,'įڕŨmø`ĨŲīlķåōtIãöņd^ŧįļ/›ĶĨąŧ=Æ­ŲģũÔíeüí7|æ~ļĐ?~ۑÕúģ_ĘRĩŒ›7ŠäŦē'&rū%x–ŗ'įMk¸Ėi/īü´cŒûmÄ6y˙÷ÛX÷ím~9ŸÛķķ×Ļ_'›ßf—?yˇÛîíGîü,.âjŸ“ûŌŪ5^[fâ-S¯uŸ8Îŋžú¨ũ>jĮŠŊés^#}w@gúwjúа˛L4ˇãßétf Ęä^ŊÁŸtyįĒ/÷ŋĒ˜ų‰Ŋ*åë~›ĪĩÕ˛Mw˛ŲŦ%ŽžĪįjęĶm–ëÚIˇi—Üܗ æÎū*.oÛf×[—…x°/SM0S^&įV_FīIõŊ'“ĩļnĨÎü[Xk`4K,]ļöū^ģ:Ņö­l![õļ“[avŗū/ošeĩ9ēGŲŅÆ“šöSËrĻåŖŊ¤éÖ´vß6û¨s›íT'•œŗmÃÛŲŨ†ZöØö æ6“õ´Ļ›+?ožßK֗ŸV™"]¨Ė7֗éĖ>Éjڃ/M2ÔÉ/`ë3—hH››5QqĻļېæeô›eúŽšú<;ĪĪĀw üØFįwŋ—•˙ķ×fį‡ôŨ}›õs"ŸŅe›û’û)9ōX#ŽúņÜsÔÖ^ú4ęËt>ųäqū<ČGũÂxÔO|ÔrËώu2 š61E•6ņũĢĶ*ķœz’ÆÛÖšËĀdYĪqےK˸ųvfšMk˙ËúŽķ譝üU,Û{Ī[Īom̤AĶĮ(yUĪĩOė[ŪĪœĪžPéôõŽķ~ÛØõŪ ?hK$•Ęéŋ•¯ŨŊ÷uũ§-|ö—ŪŸúŲ-3€4caL¤ũãęmeMŖ5–‘` íí”UĒ@Ŋc›}Ô×ŧ ?Îļ'ĒÍī÷Qg]ZcüëyĮÁžíš3Đ6–ÉģŽeßpĢI§öē>ęÎlĄŋĖßáŖn°FƒŠoķš{CĐDÚûŊž‰yĩͲœ1uņųį§c ØīŊ<ū~]Ũ>?ŒsúŽÍ—üÉ'ëVíܗF\įĩËą_Ÿø¨›Ųī?ņ{˙ŨüįãQÛˇKb}õÕy¤bËa^2ĩ7wúMéØ}ĄÖŎ•mU6éŧ‘1™æ)O–Iy÷Qo+ĩÚäėĸZôÕéuå÷“ŋJK¨3#Ŋ$íĸDȚąq˛YŌíęgīÍëæ’ÁÜÁ_ÅŌNJ[­osK…:ė÷ĢĘÖ9 ōÁŧ–Ĩ}ĸ1×ŊŨ_\í^^ĮúHÜX•_ÔƒË×EĨŗúä|€áœ+8dvY#“ԝNãĒŽŨ}ÔÚW–-dųūH_Īqŗne¨4dÕūø8õĒqVÍl¯t€û@Đŧ¯gŸ$Ž,˜ÖØŪĢžĢ'‰ím>öQįeąÔ¤]ŸßņTÔvleŽēžÍÕjY=ņ¯Í?šˇáŪībTŊdzī%ëĮfNÅ|Ž ÂÆ`ęËÜG]07īåHDĪŗ…īī‹ßŸ-”s↭HJ›n™1–ĸ†üį6ķ/!Ĩ:áũķĶŽ1:÷ÛÛt~ŋ ö†<ũäĩŲ÷Äáũ}ˇh=q;w=1ī‡Ú}œ‡ƒšû0CN%‰ŪŒ´—Éjĩ.¯Õēķ_ā'>ęÖo&Úk‘šT97ŌĻ:KskMˇnˇÚxÔ §Mß8Rģô=ÜĖØ-Ķëí.ģÖą>e­0-ŽƒīŠÛí :ĶyÕä¯v˙ŧdLS*G>ĩ>đ¸ÖÃ.iGąī„sg!ŸĪ'­c‘ŠŌĩņ¨Ŧkš/[7¤ôëFß[äīvNˇŠa˜¸öū…üúöœØ#oļ7ĩõwqŪĄĶGmvˇZĩĮLūulbZ{¤]}ĩP—š\.îM+å¤z\(Á€6ž::‡CgŒŅY?;c›Îī7¯ˇķoĪžÎ<Ŋ6;ũÉÚšcV؜Ėŧs_íˆËž[Fz}öŅxÔŠú?šT—ÕÛ /į?Â/ų¨—}Ô_Â}M0 Ãp˜,Mų<=ŒãaĮÄđ÷ņ=Guų¨[9ęīŽđÃ0 ‡Ėjdō3Éá cĀßĮ­u‡úpĪQ?úKøņ 0 Ãp°<ŸĪË•Ã8n01ü}üĖG}¨G Ã0 Ã0 Ã˙&wį¨+õáĮ}Ô0 Ã0 Ã0üģø‰ēæúøx?œ$¨^ŦˇËõvąÚŽÉĢíÂđFāmĩ‘iž\ŋ-72ÉL[ę’f’Ī(ĶZËmĢŧ˙Õ,i—ˇk™u7Ģĩãú_KvåN÷Rß×ģ_ŽGb¯ŦްúËōĘ´6år}įÕZ>ūrĩŅRĪÆō~N()))G+—5.[*i¸–eƒvŸ´•ķú‚ĒGŖ}†aø¯áÃņÜãŖÖą>Ž6œ6å)ŊĨy‘^‹ėú!]+ž6øĢĘŽíß÷[˜ƒ1“Õ-Étē&–uff“O'ĶUËÂ+oM6“YŪŽeÖÕ-Üō{YūĩÜōÕãÛGĩ¯Ž=~-ģŖ}ÂE~Ÿs?6˙Ķé‰Jŗkš_åė $–uŽL™YĀ”fēÖøIŠg,÷ĪåŊĖã_RŪëUUĮnO8¯ņŋ[Ž˙Ũ}ŲuŅQzŸ4ĩ ”+Ŋž@—/[ŽáĢËë7ķkå€>Ĩ¯|Üŋôô5CøËë‹ÜQÖ?o˙9/Ëb$žėŽoÍōsuāÕz5ˆûęđāøĒÆŽĖn˛z>j=o6—ˆúOŠŽõarÔqoYÔ8qsūŒģˇĢŽĮųûķUĻ÷S&Ķ6NwG3é?÷į\–9Ļr#PĻßå~áņd?~q2Ģ܎ÉÕNš”ņ%/˙ŠķËŋĘÁ(ߎi!ĨėČL™™Î÷_ĖņļŸ7šŖÔûš!\}e}ÜQÖ?],§čœíOŠLīĮDĻ]œ(ėOf:Ø?ÎfŠĪ™)/™@9]îåņ’×ĘKUĘ 4g¯*ĢédĪm°åéø'÷õümĨŠ'W†qgé×+å{}ûņ2~ĀįrÎŖeîœyËgî:ō¯/˙Šë^æáŅë×ãŠÆ]GCĘھʚ`σ=‡ĶXI enJ™SĩáښÁĩÆ×ī-“oæ'eŖĮÉú¸đå­]~ĸ¯iōWL§ŸĒ~í9÷oÖ}Ōûäį{yŊ—ÉĪpū§|ųnŽ—ÍZ×Y—žvęĢoÚUŠķŲ•Iūq<š_g[­ÖQ4“;úū@úIŠSøŅö5œ–˜ųũ”Ë´;f2Ų Ú€‰¨OԐ^eá2ÄÍjÁí‹át+k ęÂi{e™Ū4z×K¸~z5Y¯~qƒ*LŖ9*?ŨM?—Lípz_AĩLąTWĨNwnuôCëĶ`>}1WŪ÷ņåŸãŽ ­7$~ĩŦÂü3AīšÆ]j››Ëw-ĶŦķ?PÜēîîĨü ēZ?úî>s=~îÚš˜ßUVátųym,mĩ Z›;mú4īá‚Éæt•_-oŗ•ŽÖ§´8ķû”>~úvđ0¸Í_T6Ļ{/œw„ÖŨ6Ĩ ¤;ë^ŖvõÄ$ŋ÷ęՋÜ3 Šķmn^;×Âŧ–h~C|ģ›Fŗø(͕Յ†ģĘcúœãÖü㝋§ˇŖŖd›W&ũ úNŋ›æÔæ¨męØeŒŸ4>­ ZŗĶN7ŗĶÉí>Ĩ… §ŊŪË*ˇš¸ŽæŽŪô=âOd˛îO÷(G},Ãi—ŖîĖN×éZ÷ŨNĻĶ ŦüŽŒî_nKŸĖNw—~ .SÕæŸ{–¯g§û2ՏĘK3xîĘQëëšsĻúčžS ĪQ7,Ÿ h—aeĒk}MĢßy%Sũ¤|%á3|ú\ĻúA(å>csę (;ũĨYëŸÎT×§ž˜äK§`2Õ~™Ũ>$„ŽfķÕjĩ{߯כ˙&™“]Í~ŸÕĩû!ģƒęÛã@ēTĢņãjuĒŽXsÔŠ;ißĩ߀ø§×Pį^h}íĖT׃ęę˛mžŪAMÖ+Aōā ã5,~8=fĐJ7]oZūLYĻв>}´æÜ§?8žĸĄLîhīgŠpįÍ/oå™Ŧ•ēVņˆûˏ‡Zđķw•}uésüjŊ ˇüė5øS׿OÔ1ī“ļ§Æ:§ŋ”ÃšŠ€š>=>ˇŋąōšž–ĮŠcOĮãIÂæh6—pzģŨžŋŋīã“ü'AõjŊY,WWÁ4íá?˜&ĶhĸĨ7ÉÆMé-iiGė-īKęēļœzųیÚuėkĀ~˛l|FķéôŒĩʨ,Ë3@é—íēÃÃųÕzE9 ô[-¯Ģú˙ŦŪ[f-į””””_X. §M‚zŋ˙)ÍŧŖ endstream endobj 25 0 obj <> stream xœė-˜ŖŧׯWŽüËą#GVŽYYY[YYY‹D"ąH$‹D"ąH$˛ožsNNB§ķąĪ[Ž\÷žMC …2?î’žī›ĻŠëē(Šëõúúļ{ÛŊŋ }ÆĪø?ã_ī˜ōōúÆô…ëۋ­Ęâ֋u_^u'ēfCü •öˆžžŲWu1.ģņgÖ}Æßgų ī÷?ã-eķõų[âR8O×ë%/˛ĒĘęšátUUyžË?^eU/7ž<õGtIŽŨ˛Dãģtūtü”îÛĘgõŒÎúŲ~­.\EY¸ÎL—ÉęmÂ5˛čz¤˛LķíŽ8Ô'ŌÅŗ?v?åū#ÅåöŸŽŋZ˙Å}ū]ū_Æŋ]o8åīÚ-!Žū41[N§ĶíŅKQí°ŧœÛág–ãūƒiQV°ßŽįË9˯e)qúåõ•EėŖg^Ļ‹ˆ˙ŒÎ¨fõƒŠĄ1Ņ!§ˇßV&÷Ķí×´›x`”—ҝ­Ņ‘é"ĩutđkŗMŖ˛ĀøîR÷ŗP'č0ĶŽĖŪ:}Júķ ÆĶ—ÅdO~•:Å{D#ˆŧvĖU7•íČJŅ EĶgu—×ByÜį͐ņ˙ö×ĒËĒŽ+Y ĢōfdĨh§ŧ™ŠvÎۍĘ×âë˛Ndo˛[š]^ڑ•‚ĢøoĶ‹ŊbûĀö¤ÍÄ.ņŊeõĸA!Ûw|•˛› öÖúšėfŽė Ô Å xuqZú4Ô>=~¨_÷üŋ8žK7NQēn`ģ\o•‰G§Šm?€z—Np…øēĄxoqbi0H7>„^=vņ6ĢĮŊ毊5ÃéŅi^kLÕP-qš—’Ŋ$Ņēg%¯G Փ†ęé.œž˛zTP]K€ˇP]¨68Í÷¤aåRԌĢ9Z Ɩ ĖZĒKÕSĄēwp:ĪHËĮŖõ§ąüĢqZÅ3€ęûp}†Võļl  ZÆÕ ІÕÆ.BĢ‚ļEâÄ-Ū(fĮt âZĮņ:úÆQŲĶįãډĮģâq1œ ãÅ[`{M'ϰmcqíÕx ãvô@ĩ[ˆû폧yą@;Ο‰ ĀY^ĸVeJˆ}…˙æ¸,œ¨‡1íX×F§ēZsŽ+Ž ŒEŧDâ55Ŋéū+¸Ũšīœ¨ÛqfŒ4ĖKYU/oģãų–ˇˇ{ãĐé}¸S=ŨĨĄuíõ[€Đ{\Ģ {Š;Õ¸>äKG@ÚÆAžiŦąĶEëfpã„°ŲįpjNņ ”Iš–ŽzK3(mTģí¯jF tC´õŨ8Ŧ|ŧ˜ūŊĮqõ&(Ô2´.;14NOŽGsZjĀŖÎuŅBĶr’äÚŖ[  œVĀĪŨ鲕8-‹ kíW ¨.ØŨA;hœž$NWžą.6ĻĨˇqjķ“åöuąë¯ęVDwuŨ‘Ž !œÆZqũˆîô3Ūå6';Ŋ÷­[“~>ĶįŖŪËĻq§ŨXkCâˆļ÷ëŌjŗÆÔŖžGŊ՝;՜¨‹bŅž÷įœjíQŸŽÜŖŽ+Æ´LEOZgІ5W‹ˆ+5§yįĒgŗ-šõŠī‚¨ģ‰˙iî„SũļÛŊīģŨŽŅõčsn7šĶ~W”x–mŌÛĮ]nwWŊhŽ"zĀŖö:Õ~įy ÄGZĢfã<[$žĢ “D ™0vÔ°ÄŨ;ŨEØg¨8­ŲΛY‰2Zme _UąŌ~2ČŨ [uˇņĮá’Ĩw€ko[zxėāqĄ78öH!%€Ũ8šđkEãÛJč¨‘Ûœ‰ŪøT˙(ģ-;ģ.ÆiĮŖîGœŽB8=ųp:C§z8ŨP=HÂĪ*ĶyuÎJĻŦ\Zg<¤oAB5đ¨{íQëbcėT{0;”(B;%.7ļOęį JIâÄäķųlp×#{˛ Õa­ĸņW¨ŨēÆ{ˆß8Õ!Mr’?žžū+ˇ˙hü ׯw”f‹Z¨Ž:Õ\ë`ŧ@ZĐŅōeœvÍsĐįl^͋ōx<3†įz8Ņ8/*Ø^ƌēQ3”Ē’•” ŽÁLEYjĄUY\¯Ļ˙ârå5—ĢŲŽŦ™A™´š7Įļ>uŨáũŨxÔLûi)ƊA5+2߃A ÂōN5cOâVeZt5˜aŊ§D×õ 5Áiņålûuöö/?ÃeĒ<K Žá´đŸĶ›{Ļŗ…j¤üŧAÚKȜ ÖēhíÔC0NpCŽq ļ¸^4˜„.9B¤G´ö¨|ŋōièŲØFšåÔ†áwŧĮ=Fû Áßn8ˇ?ô–'pėœÛ ŌļĐ>‰âãŽÎsŗS㛗ø- Īøī”8]6ƒČúčĩG-ŨiëQįĒk™ėŅkÜeΊÂã˛3:-ąVļ­GŲĄJŸŽä NÔøšÎMéĸæ8•ĒĪŒĢ…Y- š;ÕG|ÅJ§|X<Ļe°uM´"qEęSt­ũ-1ŽhŊ•áöđ¸ĸõI.÷ļ‚>‡‘ŧ/­5Ôä˛ķBIņ䇇ŲĢ~gۖ)֏Wéē6ž<}zú˙2 mwĩ4“j';ÕÔĩNv°ŠSíæTËÍVĖv Qšé|ģÉŅ?XŸ=FÁ îtJ•uīƒj‰m›eŠ´_|=ģjˇkZœö™;)ŗ‡ ŨįHqŨ)ü“œnŽöZ…ˇŒŠÁc[Ŋĩ´$ĐĘÃd¸5ičxŦāyŦ%Ōđ2eõR‹+‹Á¤Ūq CJÚ{œFŠRQW{ŅeĢrh+UDGĨēeiÚÛ÷ĢßČ↠Üx"âAM¯›Đ&ØOƒ°ÜDČWŅ­ÆÚ‰(Dqô^|¸>š¨蟠˛<ĩė§m…U~ŒĒVßū `ž,rĢ[$ԃ¨áđŲ@œîôÉĒ%NwĒyÂsĄ=ę­8]ē8 ?4N×Ŋ´Í:gĖ\6ܚf8}-ÎY!T 5‡ęZØÔXQ%~ ¨69Õ¤A5jĀÆŧžď„ęīîį3Ûõ—{]÷āvŊ8MŅ:S,ߤqgÛŋ–ŅqÎ6ĘâÖŊa3^ËÛÃā)-w;áëŽõč‰ŨĪ„ÆsĒaŧâTķxąĩ'jНŨzéQ3îÆš—eb:õæŋ îTėĒSĪPęšqē#mkDÍ—ö9Ns~؏]×yŸgƒÖ!ĪF­cžML Ž“ÖšČķķÅAhØį k3åv-Õčy[d:ļí~÷=jĻ‹ j“Díā4ƒÃĸjОÎËZh…âŠĸTeŨH“ß]âNĪ@Ŋ€Mĩ÷:Õŗŗ]û´ŠJw7?LFMŊÕHĶqŪOq¨Zkœæ<ܐæN9ĖĸИę Ú– $°YhĶkâ2 ŨIÔa4ŌĢĸđf0¨)!ĮÁļ Bô;LS,<ĐeÛ@|ŌĒpKõŒëāO*—RÂR Ø…mՂĶ Q;Ž>÷ž°íKđļÁ˙ä¸ũ ~~ōƇ7ęĸ5=RāX¨žÉļāûĨ@nēöMŠĐâĶĻĮÅ*ŧå H<Ęú˜¸OĶĒsø Ÿô¨ fkœ.N—RuŠēEh,6Å@ĩpĄ…Ũ Ģ€ëž°˙~†›Õ?ôo3€ŖŒVõ]Ū"yßĮ•ԜŠ'ÆSÛ+‡Ų-˜!6̏“m°Y›~ o:ã?>úōSÕGG$6‹sL(ŒmM¯9šÁäHÛã[÷ō\…ĮZ'{t&ÚxÅšRÕ ¨–šĘ§5TĮ:Õŧ>÷zÔĨ0¨mRJÕæœ¨ĢK^jœÎea5—ĸĘDî‡\u)‹A -ÖĶYÖæU Õŗ]×ÚÕÁBô­%Žú[ÁūŽxåcĢ’rķj3ú4§%œ‹?x c¯_MGüXmo‡$ĨRųä^—›€´iߝ+”QoŪ…E'ô_;­ã¸;ŊîT?Čĩžš~¯Ģu§}ŽĩĮöÄ•5#ąŒ]Ÿt˛ČxœüõҘ1ŌåšMļĪąĶ/§E, heŊÔPŊTZŒ¨Įļíŗk—]{PQFŠ9&­ĐüĖĄ}Ņ ‹YyÕŦ5j5}xsl댨÷oÄŖ^€G=š5#jÖât:žËĮĮĮûû;S†Ķ]×Ũø0×ĩ'ē+ūüųķ÷īß?Žė|›Ė°ųŽSŊEm<íN¨ž-Zˇ9Û/=}Ŋgû“užgĩÖ֝p?S—ąũĪēnô)pzĖüĄA5/Ō‚îųY§Y¸ĘRk¯vĘm–đ ãĒSi§mR‡¤ÁĪ?Zū'^ ÕĨøģoPÂ'…Lē}OŊGØüŦk\GŒžĩÁ'ĐáqgÚ@-taëš}–7::´’rÄļ_e{ˆÖŽ“oœ[tõĢāŅO˜â+jãŪŧ€ŸÁ´ĮÛuúôĸģ{ `p@ĩ=—ĖgĸÎs>X… Û‰Ŧ3gŠZ‡ ÚŪœŽ¨IųČõXö¨N7#gévBĀĖūž …1U՞Ŗø„GķhĘg`āQ3ĸΊúĘø™Aĩ°ŠO—\Ō5#ękĄ?„ɯ=jÕúF`,ģÉIüđ^j‘°!Į‹E¸(šö$†ëÖ´>ēîÖmÅ×]qÔ@N?“Um”; Qg[ b6Ša˛A0i¤ (`€Áƒ‹ĶAuÕ`Ø×éRŋ(–;8íäSĀŽ~T)6'"4uĒ=ŊæTÃØ¯Ŗu¤ŖŽĩox=G͍lõ¯Ø,Pe8_2ĮIv”5P›žR=ôm?ž.×aœLÖŽį-‹…­­ī vÃx}ũßPWõūŊŪ0möÍūŊÚ>:ŽīLûÃGxÃ^¨Žķãņ 7!ˇÂjúŊių.ãž÷đ.zûhY˙RÅVŊŨžČ?^ūįxÔˇhu^VãųÂXZĒÕŦÛ1ό¨]œ†¯Y§ãŦˇ`ėzԋ…ä.˙ķ'oZ“ŧkŒĐē PĮylŗ?|ÉęQÕ°—ÚrĪ뎭pĒ'ëW‡:Û]uøŗgHbjHŗ@€´ø‘b?U˜,DŪ¯´ž4Dh[ÚÁheTđĖjÁ(h°ĮúŌ2Ĩ“˙}gĒÆpjäQ ¤Ū5¸ŨčŨOØŊõ N2ÆrôKę9ŊŽëKn^HēÁlŧoAīš$o¤÷šĐp˙ÍQnŒ/-Žc؊Ōhm¤ ĢkĐ ũ#ēíSSzŦĩ;ÆŖ5öŽņ˜ĪAGēWZ™ØŧŠq¤—ôjŧâQ‹OIœÕŒ¨‹J'~0œæ5ĪĻÎËĢxDQZî2ņÃøŌ§Ížc¨Žû:ÕĒôËzüˆŌ„á%Đ{¸­đvũë>öũ6*ęGk ˛č¸4pŗ‡u§ĒIPq焪”8Ž5oëNΤ™ĘRJUßë3ƒDƒĶšBŗöv]ĐCŠũjPcQ„ĩá1ŽĢ¨;ŠkoéÎŖÖ2H–Dm@š:ÕÅûŪxÔ¨p6.Z =SĪYzŅН–D­ĻëĨíÕŗÕ­5Q3|Öõc{ÕUė(Į@zōĮ‚¨ëÁļ€jĐũ0b¨ÖŠøü' Õʝ6.4ÂiŌi‰sL›ž4ąVTëW]œŽǁSíIį°‰5D&ĩ–‹ŦĩûŖ‘J-ŗŠ-T3œÁ&~Č?䁧Aâ‡įáD‰Ö›ŋŌŠnq¤Dķcņfwz{~uä=FÔbļ,ŧ}hŊ„ĐÚŌCĖ‹öįT› Q´đ™į ÛŪ֗ŗ…ĘyEEŅ`ŦnBąS]œ†ž´YWŒ9ÉčZļú%Čô ą<‚ĐÕ].tJ\?Ōĩ^Gî”NŌĀÛuĒa}ĸS xÔ§53•,:ĪsH‡uļfĪFA%ŽgĘڏŅÅzÔ`-†č×חžĒęũGÅČö 0"ņCĢLÕč6ņC*Üâáx†k§´īE(åCôßč-2í ãQĪf46•GŊ{gDMĮ“ĩ„įëuŸeEņ~:dČõúqšđŦļ?‚¨+::ĮŧĖ i˙ūũËČYÔS}øsā¨,@úĐ csø{d§%ņ¨yû–‡ƒč‡Ŗ5[qšƒ mFל~y†6‡áæĘé|€f5'jö‡ŽŪsØf5c)–-ĮĄ<üeh=ā]_%0ŗöŨ•× ĸ%F™4r`úGhOœt¤‡›W[° fGjū#:â‡Õ0wž´ŧĩ*€Ų õ\ᇁĢlpzTĨEcšĢŗĻ—ÛdR 5iz-TwĒAúDčÚ:ÆĄŅKžŅĩĶ]k÷vŪ Ô´~ŗk Se¨Sí×$ú¤‰G6ļœāÆŽęzÖÛņ|eŒ¤ëIKŲ:îŊąQîQŋŊ2ĸnNĮút`ڜíYi{:tįcw:ôįc> JEšlˆûŗęĄŊuĸįVkŖļ{ėĢę]ĩÆiGŊ{gšː(=ę÷÷éÁœNģ,{;ŸYŲĪ¯œĨųÔí‹ÉŖF™Ī7;­sĒįŠå>oßrËYūÁ^åãØE´”i!=åƒ{ÔîiķŅ<„ =é gÖĢÎŦ¯æâU…čĮc>^G#rMë]‹LéAüwP^ˇÍCĮŖö¨š•δīu–ĩč](ęLĨ”đaôĨÔÎX­Fë Ž7é1ĨMBČäq­UlGœ8ä3øhÎYĨ‰îøĪÎOđÚĶ&y׃‚ĸØXÄ=×ČC‹ÄņöŽōAQšGûƒßģܡ ôG04Ž=UƒŽīĄ'ĮņšÁā6å9Ŗ+ēuo^´ã<“qŋããQWø ņŽ­8öÚČ$āv <āéųũj€~%1㓘i_ėÕčĄÅCĩ-§Ճbf˜J!†ÂĀ;i šēģÍņ‰æ Ę T{ö?2nļī=ÂOžYrŦíQŗ ' ˛“{ĐT%x\K‹Ķ—ĸĨÆPÍ耎tĄōĸMv4|€Ņ¨|/Ŋ~ęŧOÖŦde ķÅ-†Ņ3ckˇƒVšįjpŋĖĸu+ vß܎.Z+oЏzMĢČĢĀINĀoēCē‡jœ˜(í'ÁCĩ\QŊ]wČįPûs§‘kŨx]ëŅu­Û@N5t§Ím¸ Ē]Đz@î´Æi‰yÉxÂŌ\äB[ŋZČĄdPwē3ūőÃE:N5˜ģĶÁiũũŌk r"$=ŅԈž ã~XÚ3Ōz˜Œ^æs¤nöCĩ^SŊ1ŽaŋĨŌąŊŠ7Ū8æQķ2.7ūu^0âÆ­hã҉1ĸfā$ęÁ°f)ą¯0nËŪ߆ļm¯VēL)+ŊVV†ü2d—Q+ãdĸĄúŗ\Kö0ČŪtˇŲVžņ\î˛ĖŽGG=ģyԋr§mĩú|>ûĶép:)=Ÿįķūr9\¯GÖārš@:Õ]ūWGÍn‘…_}ģÍ=ežYÍbžG-Ûü‘šÖ“ČĩO"Î2]D÷ üęŅxÔ7ëBĪ‚uåŖ‹zšiM7b:˜ž6¯JēƒīõWŨķ_ŅĪh^=čxšŖ˜-ą“-˛]•‰Özߨ†8?KÔÔäBg=gâh§ oÁdâp>ÄF+šwĪ3įoJk ÍxF<2ũw|qf[˙°†đcãP{Ŗ^ˆĨp Õ& T֊o đ ‚ĶôīõB{ŧÎd(Ū™ Ąģ‹Õ­'S{ŖŖI&¯Õô=n{ßŧ‡ôîn¯§\w˜^đYäėŋ¯gw˙Ũķm}fL|ܴΰ€iw$⨎üP}áגĢT›ÄNÃ1Ĩ!NË_.x:´ Oât•—ĒE1‰Ļ36ĩŠÕĀzb‚rôxcŸĖ´§]€ņ šˇ–ė1û등9Ĩ„Ņú1M tŧˇ`vt8Má9Ōä›p:žōa‘)ŽĶPUßŖ… ¤›Iá4€j„ĶöŅE5Ŧk  âôā°>ņ yÎDHĒ‹†&5á‡ajHeÁ ĻGwá ëøíņ9؏IüŪāūxæP=Œp>č´Ø¯—Ŧ˜ÅālĢ-5ßÍã86ÍØ6L§–L'ŗ2wJįOĢéÍž­Ží›Ļ*ĘÃî{šĶĖŖxÔr¸é¸zÆúĀS‡/zvGüHSØ'™]Ēoüj8M9hīĖ´Čį÷÷‰Ļ5'*O-3)šéAÍAy3‘wĒå†7 ÕŽ6.`[mŦ: USĀVLq(•Âgˆ=¤äžúʋßŨHęˆŽáJCڄ&ŗö@ūäŦkZúo+ HÕ8ÚB(õŪÔ z÷X$Ü -pģŅ#ûãíÍŨ7ߟûAõâöāë'ŌĻF˛ã[9įXāãîÅisŽŲÛ1Râ<„(ČYyÔgūô_}ļP?4NrŪC5QKM&jŅi§›ĸĒYÉ9NWYQf\+iVķiūtá@ zRS“k´Ę­3Пj‹ÖNūĒEkāTƒ‚w< ÷ZAí1Ņ:Įn|q‹}ûŪV­UœĪK‹ũnļ-át‘ÚIb¨/!:=zĨ[Ęgũ`ÚF&X:ĢGX´6P­‡ŗĶņ„R>ėŦ ƒ:Ģ Wv(ŧ9†Ķĩņ¨ņČđ de;é(ûĩrƒŽŗmGۆ@ūqMãû­IՅÄV›5m@Ėp‚! ģ .ŲūxâåpRÁŨņáxú¨øôörx}9ŧŊîšĒXéÛëq§ôČõMŠ,oo6†åÍ‹~¸DŸ|‹R__ÎģˇütäķKšQ>&íQGķ¨Ëēa ģ!†!ŽMۖUÑIíÉŠžÍÂNž=cS§ŖuŠ.>´^Ė$ætĒtŪaI,ÔįÆDxÔJN+í°ļF l‹'p}/ļĨ'?€Ų? Ąø­uR!"J ė!ēģ.zīö„[˙&Ā'€ÕEPô9ß ”ëC:ē5 ‰{ë„bĪųfŧ΍ČķÍ-tā~†Ú}ot.ĨwĪeŖN)ąoë@ĩ¤čŗãQW\MR‡ÉęôüŅo­S>Q×u^UEňēd…A5GkVĒF (†•ƃāáéZô41x˛˜øŖÂwZâ hŽÁ´/˜C Ŋ Ēë@ŧ ؁9ēÃ'Üø´} ā4ÔNõΎƒB5vŨ+ Ō@Į[EÚ@wZ=ZhšCĩĸk…Ķ­ũÕCOš2“Į ­ˇÄé=*k&H˛‰öûŠIˆ"XnŊn2ß"ęšB#–€Īj[ĒÆņQ9›×Ąē!ąĄUš‡ÂéŅBĩ,Œ =äö[tqæ4Ąą[n_‹)Ą;’GŨKQÕYÎ˙vÄĩ(kiÕ:ęáN‹ĒCĶ‹Įc/oÁé ¯@5¨We ƒ4Ų[{Ô¤§0NČQ1„Ÿ?{Ą>EPQBÎĖˆ7w*lG Äã0*,wÎrkhšrkķ˜ą6‚Į.î†÷3éŧÚļoā|ķ{­CĮ=ŠĶÎ4+îČ×Ēģđ”CĩôŽUĸ2|H ˙$=š<ęÂ"ˇ} ҇ĶE& šuY‹üęÖ¯Cl0Bœs/¨ÖŠĐÚ´™°§7‰‘ũĖ(¤1ǟš’ÖĢ2ADG¸Đī)ņũųē-ŊŖø¯xƒÕ¸ŲFá-@ņŌ×i—yõ‰ģI{ÔŖĀiĢ*@ĩš~EOÂR´āĖlė/)€j{ĢØĒIˆ*;ģčÁéŧqŸ2(ũ^÷lo üŌEGˇ=tļ*ßū›âډ‡`\¯Įū¤plo3kOė҆ęč‰ģ* ĮŲę0;5mTeQ5æ)39€ƒŽZ?Š÷ĻŌhGĨU‹žŨjŒįt]čxԋÂE'§:ŧ á´ãTGîDRîüØ/r¯–5˙9Ē#‰G Ø1wZ{Ô­'€ÖSØĩŪ¨mH,ž¨Ģ)žMD …bô'c%FEôOšå›Ô)q|ũĖŅųīiǎ°­ÎˇÆwC‡Ī‚ú P?Ž"ÚjíQwŌŖFYĘÆļC{iœnmžåÔ"åCát^yYh¨‰•šL\?ŠķEqąP­Đ=Gxī۟Pú°šąP‚ÍaœŽÃsŠRN‡äxžoRâGátuí:ƒ0ģ§üzRŠÛŌņ™•Û,‹Åi, &(HƒĩÎôv­7Æß§=ØnęˇÅkīũÕeå—2x-ĒÕĩKÅŪü gŖĻĀŅËaú}$'@5\×Ļ<ų0؎ÉcĪC ՞9FƒũX,Į7‰§ĸŊĩ#Ūtž(áäŸĶƃ‰áųŖ]hY?˜WgpË&ãmHŧZZ7ö9ÉÛâ{´Uę@5Ak Ã×ã˜ļ$FyÔ3qž}ą×Š ˇ[zņ¯t›Ã.td?ãī1ņ†Â).TĪĒiĄ˜=Y€q;%AQKb?đæųŌHéÁoHAy(ŪP¨ĢųD÷ūá ž+ō™‡Ž×ŖâÄãū۔œoŪßAVŽĩÔJ Āb§}1ĩÖĢÍĻnNĢ1@Gmũ:ĪDáŖUjZ3ĘG•™”ĸ¸rå3‰Ģ‡ëÎūDÆ`8Ŋ8b!Mö?˛Ļ€—ÉV{#×:!î6ļ˙ÖxŽļ™Üũ'q§<âŊ ÕXCXhƒ ÍļŲjĮ§ Ąų7‘NpžPõ{% <6î~VJÕ ŅŨQÜCeÖĨ7ž=4ŋ&,Vėüm:Ŧč[¯Ũf7ž_GTŒ AÆ@ącÜ:ņˆęŊr’N<Á¸ķĀŗV‚ģbPˆ‡ëLãøX÷;Õ°„r¯šÍg;Säā‰öauŠžéŧ%ē‚ņâÃæUw: ~<¸Kž÷Ä÷`đW(…jĨŪŦé_ĢŋAWĪIį7˙9“øĢ„úŖo†ŧã;_åÉĒ{ Õ5*ĩĘ žōú^zÔzL0SŠtDąë@ÎŌ(§Ī!r_ēŧ挨KQ„G]6dæD ՁjĢÆĶëā8ØĻ‰žmŽō=J1û—Æ~ ÆqB?Rgŧ P1įŗE¯”ŠĪŲvÁxŸ3bŨ_:ŧ#/™ąâņm…ˇŸÎßž|*Ōu§ÆBučķų—´ņÆpô$ĩi‹Ķeō¨CŽråÁĸz|šöūŨø Ję5aą †P=ą#äÄ4ö})˙ļļį‰Î= į–)¯ßüŽß#án?˜|nöęũÛâæ“ņč­_‚mFĶ)’7œ Ũ’8AurE8öán¨~ŗvPGŌŊé0(7¯ž8Öj0zŠÆaRˆÃmŅxÅažÛņĸ×KįķŸ#âųGķ ڒøŽŌ tą5át/{ŞŖ>‡ģ|fxŅŽĮû#Õl<„ŨmË`†ĪâœuÎ9–tž˜"gį)É$)§Uõ…xÔjôíT“2jŗÅ‰‘ĸ35L_+§Gŧ5+ÎŌõEÄ×ĸ‘ãøŠQ¯Á¨HÕ¨-N΁Á6icéËpB‰›@ũ?Ai ĒkēļՃĶl ˙ē‡!y JמwÍ@ŪKŌĩ^W=×XüŪˇô¯qņĢPöwÅč/‘ŠA›€t›ĸ>Āļ<āy˜[ ŌîÀĄ4ŒoQ­{ ՝™ŊÚ¨…áŁįôØQ†Ôo;_ĩ˜õ{šũÛ:Ã8>Æ ¯L$NTųåúČ$ūVę“uŒÆŽÎŅø.eIŽ—ĪĮ[•ôƒöÍ;”bŌ€Ī8zNFÎąØšĄgÎâeœ‡‰]-e™tĀ.§“TYl¸´öƝ˜áĨuđĮēĨ\KmEöÉtũŊUĩĸ‡Yîä8Š™Åî>?Éšēõ;EcΐĄĄøŋĒäũ>ęŗ _CBJŋ ŌĪôZ÷§úšW§˙tüƒ:ûã…Æķcâe[Ŋl9x+L˜ÆŽm‹W׏ƒ‰Mŗ‹hyáAĒÚM˜ž÷ŧsUdlöaĪ÷ŠížTĩˇzĪͤįsH7w~qŧõŊ˙§õôŒŸņ3ūgcĄeU3TîÕ؜œĄG s‰ŲYû™-Ķ$dzÆĪøKcĨķlëõVã`ûвĩũöeyT?˒5ĨÍŖuI‰—ÅŠ_@=ŠáĒ˙úå–PģŨVc´nBũÖ>?ĶĪzŋ`ņœ?ú?ą6ķđģ~CßMY5ôŨ‡×Šųˇ.÷]ŸC/ĸ}Ōöͧ˙6=ãgėO ’ô8[Ū´G-ķ@ŒG͈šžO}ęī×yc›P{ÔfžWםĄęÜ8Ôō{—%PŗÚĐ?čK(ū9]ļb|Ã(Ū–`ŸáÖÛ|ÅōÛĨŸ!Qúų¤¯ŸŅôs;đ­ }_BËz‹øēΤ´I¸ĻͯuŸŋŪ>õŠ˙–Ī'ņCxÔī Ē™õĄ‹ņ¨ã_‡güŒŋ$– ¨Ÿi=‰§PûĐęķ÷- ŒCp 5Ā!xĘnmŒ†Ņx Åpų)|}.Ÿ_Bˇ6ņsãW!zôû•ū]†Ëˆ~ųĖõvëõ<đ7‚Νø[öŒ˙ģ1ô¨GäQŋ1„æ(}zÔOũ¯jčkl/—čēēÉz›9ÃēÄ_ũše!:Ķ8đĄøwčō ŧ§ũ,˙HũJûGÅd˙Ķëį„÷žôšũ‚ãģé< Īžs~ö/Ąú/Z6\[`û„ëáL4~ÍLē–ū‚ë˙SŸú({ÔúiwHĸvžĪø˙X,—@ Û{”.Ąú­mžwY1jC`ĄõÉNڏërî.)ņsI_>ķšÅQčvÆŋ×SžSĄī ų.Ã6p }ßג~]Ĩ×įøĩ=TūFLú[팟qZ|õčxÔ;îQķõŠ˙ =žÖ<ęÉõ¨ã_“güŒŋ?V*—x=i3ÁúĐj“˛î÷.KB k({ęĶˇß¤ËÖxYã%Ą>Øæ3KÂļ–Gm+ž]¨¤Í†ĪķEúĐ÷‚~ā7.ķ°MBüģ–Đ56ĨMĘ59˙†ŋ;Īø;ņjĩ™ø`“Gũō?žŧˆd,ë÷ûŊlcjĸÎļdėŲĢ—˙ÉøããÃėįŨÛ†áršėv;Ö-ĶėzÆ1ū™xcķ™|ŗŌ÷n>ŸÄö? Ķô[œęƒmähĒ÷İÛ|ײâ­íƒqŠ‹øģÁūŠŅΜ×M_ļĩŽ/)ß_z Ąë~âúãšļlŊîmŦ˙Wū<õŠ)ęäQÆŖŪIúF=jøÕÅhŊm^^^RúIŒYo)íå^mÚĪH˰œAĩŒĮa¨ëšvâgō™í>*Ļû@?ŸG}Vß+•Kz û‰/Ą6)ëūܲ$ÔŨŗÃvØü ē<*^–OÆË'âėÃ>‡IĶĪUøq¨‡Öũ]ËÖk]ŧ>ųZ=ůĪĪø˙žøčz‡ÆŖ–ēÍŖ¸ĻŪāŲ8Ž‡Ã!ņ~ŋŖoD#Ž7ƒ^†ž¯¯¯eY—Uļ‡ÎöÛ}{{c{_e€-ßSöĻæ€‹.ˇ ?“ĸ(ØŽ˛žĢĒrö?ĪsŗŸŒŪßßß؊LŲûēãķ#Ķ?Ũg?ŲîŊžžČ{ŠûļūęüĐú™´Ų\/—@œŌæK—ek‚ųhû…Žûi—ōŠßĢ q蘎œ°~ū}Kü;ŋī´}ā;ū$ }nŊž…Ú<õŠ˙4˜G įL”õ–ņ¨Cn§Š7Žōų|fÉbáņžûwâˆã}:ū1pŋ^¯ŧMû¸+Ģ˛ü?öŽ\U&ņÆ­F#Ņj4ŠFŖŅJ4ŠFŖŅJ$ŠÄFŋww`]Ø]ÅãĪ™÷ņ™ûŪqöY†9Ã‡ŽŽŽTk=(4-×V[NŽ^mÖkteƒ Đũ‡Ī E‘î?\îãņH“ƒûúßąoī?ú |ģŨFhũŽãõ^J‚Á ^ØõØJ˙8wĐ{ŸVûeŲĄ­äųų_”}cíŋUq…É…Gīŗy/øÖCŸM—˛÷­Ã§å}Ž)Ė™7¸?šbÔ3FŊ|8ē%.ŒHožįA0šīžĀŽëo'“1EY!íųƒšÁI’ĀáÄfŗŲۈOÆcЎĶíļœ\÷ 0æglG‰eÚvĨizwĪĮH×ßŪXŌˇãņø‘><]Ē!üZ‹â} ŅOīŗŠūsSī坯ų.{SŪĐ[ŋŸŸÍßÁÉ|ŋ1ˇ–ôŲØúž°ÃŖįšĶ÷|ôûĸ}MđŦC­],Y˛$YËŖfuŖNĢđõ yÔv”^ÜHyu#‰;#Æ-yÔfv„|¨Üāãņ¸X,ļÛ­ŽŸFDŌnËÉÍHģ9?v˙ÕC‘+øÕŗŲôt:Ū×į–<ęöū;ûųøū>/%Ąƒž°õ67ŅW˙|œ*ëå}Ņö˛CE_-ĪoЇo“üNL=ņëĪđŖĘā‘ĩĢÃÚxcÍ´ä;\˜3ŋ›ßĘŖĻõåyyԓÉDĮ`ī–-yÔē~xĄv4¸}w‹^’ļÄ'дˉ“;ŨlŨcˇ"ęûũ~ro”¸%ēŊ˙×u–é6Kį)öˆŪļ–ŊÍ6B´pas_ī‡sOŊˇ_DÔĮ‰¸ŪÅ}˛¯ĪŪ7wŒąËxE? u|íįWî;ģœãũ}kÎoŦ™Öĩ†%Ë•ō¨U¤úŲyÔ§ĶIĻwĢŋÁÛķ¨)Ų™G=NeîDŸļ4GĪáĐRu–ĻQ- ˛Y¯×Ûíäx<ŽVKģ-“ÛsbrôŸæĮė?”‡Ãę—>í]ũoÉŖvößė' •ÚM Õ}×›đÂŌ—’Н›õ´Û›čkķų8›ŧ‹#gEM‡žK4ōâĢ,ģ‹ĮîÆīÁŌÛ6&Î=y_›šūÜ´1ĨÃæėļ,õwĘŗQĪšĢM_î•vģ–Mßú}cq”m•žļ„Í[ee/ZėmüÆ~œŗ=Ö ß:cđžë^+Ÿuž9ķ_āFŒšœęs3FíÍŖ.˜˙— û}Ą˛>fŗŲËû#Œ˙;ĖĪĐŧčŦī+;•CŲ´^΄GßĨŦÃÆw)7eëí@aę}‘ÃŪˇ>úÚßĀšƒž¯Īū‘˛}õŖũX?\Ö÷[j×[ŋm×īļøüēQöškˆoęģîŊĪzΜų¯ōŨs¸ĶåÛÆ1ę^yԍ"´ėRöîlKˇ8l[7ĮØņíŨy™ā1’¯SI’äú?ø?——˛ËåÕvAí: ĻMˆSĢũPîĘĐnį}už;č}Üļī!ŠēEMĪn+ŌkÖé<[uVÖčÕ˙.ķĐ{&­ļ|¸ī˜Ū…Ą~Û}Īîį]ŅÔߡnXŌˇ.ŨXģZ“Á˜3˙ƒœvĪK+wŌŖ>÷ÍŖfÎüĢxŅÅūŲŠááØûl„/’Ö÷šŪËą0xáæEĢ}—d§(ˇÉģ oŲĄléC߲]ô}Įå9^>Ūá87ęņũ–Úøŋķģ΅îĢD—ķŊƒũ3Ö+æĖ™—œbÔI.¤G­äų,=ęÆ~ÔŠ‘GmgɒĨCЁx­N!\öÂŽĮ˛/l{vũķŋ‡s}_Ÿũ3ĘvŅ˙-<á7ßã<õIOŲ.ëRßõŠ%K–7e¸\‚ĸĶĨSíËŖ.yÔEÁœ9ķŽü9T=Ÿ#…É…pë;đîmŨU'šũ1õ>ûVŊŖžVî˜_ēđ‡ĮØcúŽĨËoŖįīäĄßáü÷ä3Ö ŗNæĖ™ûųbšŌ1j%ņ?+F­?8F͒å R ÄŊõ Ņw‘ÃŦ§ g0žžŋÞüÎsĐâž3ņžõá‘õ„%K–eĮ<ęԌQÅĩæĖŋˆōĄčÉŊeÍŅŊĢO.K0í…Uöîë×ÆėÁ쨨cšÁģỐw™ķ‡æŗį¸ēČS’-ĸd˛N‚M(ɜ9sæ7ųb›`õxЗĐ{}¤Æ^×uĄķ¨9FÍōË%áō ąčŅ “ ņË\tá&†˛g<ŌĪžķķéčü[:Ĩ.Žđ¨Ŗ}ēŲ§RÆémŪEvŠgXˇÛ$ŪĨ΄9sæ ŽucēIą†ˆŧˆr?ę¡ĩŒN7cԆû1Xxî+Psc Îs•Ģ jËüüvg¨õz˛,ĶŠ|@÷“%—ođÎ9¯ņ÷“âÍęéۖÍE›wā=ú)ÄË' n\Š_׹0gΜyŽÕkHÛĩō_īL”’Ū™˜1ęŌŠ6cԅrôĩ›AĀ„–ÃsEĀ äŽ„ā÷'GŠzOāNënHr>_ÔwæĻŦë1(‡S}K “ĶrcpaķÎŌW§ˇ~í}hįŋYĪ_ÃPsØWīą 6éöģ‹3 F+ļųG.Ņz•LĶtĩZ%I➊ŋž3QI!= ŠQãĒlfƒčuQEķĖ÷tš\~=WÍ“cē‹ĶÆę›w\>xę|ˆw@§>?0W}KŊĮ¨ ÆŨh‰QĮqŧ’XRâäjšÄ˙Ą7-Í<ę\Į¨eõĈQ‹kŒēŠæIˇ$Ër– ™įđËJ§‘įĒ.1#öíMڋŽTÍŊo¸Ķ„å*F}žÆ¨;îÂÃíģrDö­g¨vYūÉyÔĖ™3ŋ›ûō¨á-Ā—’ĶIÁåu)ęeŽōWÉŪ“G}Q'ęąÄ¤ĖŖ–!îÄr„2ÃQųãÎŗųäš2ØÄŒQ_ίęO‘;Ü{;FũķķĶn˙r49KAOr<ãŦĖUgčÛ4MÃ0F“Ių•.uR§ŗYÛnˇõõárWŒú>)â]ęėmCËĨm_}ŅώƊö˛>›VûÜWgkŸ‡Ēŋ‡ũ]ķéĢŋûoĀŽQã|ßl6Aā\ƒŒĸ÷õâץĪwHœ×8Áéqƒņ>h‰Q' ty% ų€iyG5U+]“:ÔjņCOAÂOX,°´Ŧ^ĩ”]ûFÜgi+aßĢ9;ōiĪôĢÕJ¯ķëõ~T¯V|ŊŊÖ6ōT•k'V%]ëė‰>^p,qõŽŋ=‹ųœ|ŅĐøÁ~ŋß7NJ6›–qŲM¨Ez•D47õôĶŠ×\ēģ–žVgQPRũ/Sīv;ÕŠ|ĮžnKs]įt:uöávõ@ŧSũČGęņ•Ēo,‡•k;z6›eYĩ–'Ô~ÅV¨Râ#`?ę”'ŽË{tė—ûœ9ķvŪ’G]úuŊ¨ļVĐú1ęjë<3F]w„Ž~ éēĀ?Œã˜V3ĶæĩŧęĄ×~š­¯ģd7ÚĘķĖŽ|Úöp„’¤ôaŦRt–}ĮĨz;Øü8Į>ø<ÛyÔyŪ´Š+Má^ÎËRv:ápâz„Šīļ[ܸéūkŽkT§{\hוG]ÅxՙVFĄG=č‰zĖw XÔãNgõ‡!xŧÔ sŽÅ¨­:ĩ3â¸ĪÂčöÄQjŗYKĪŋŌÃ=ب[‡ũŊyÔ¯’â͸¯o}ûīĩ=yßąô­_tčsĪšę;Ÿ}ë´íí5NXú+ ¸Ø8UĮō„ëĀ5Î&yšĢgųé4p?ĘčĮ4pŌ‘Õĸ¸>ßI‰˙šF[´MãņHˇxEæv@0™čæ°Ōč†5.ô–8HĘįl—ėXuįÎ`0l´īõŅEú퍝ûQSâG!.fĩrvR%J ĮĸŠˆĶ2ĨėŽË|>ÃWĘĢL ËårŦ@ū’´L`9W–sm‰u#&څCE–qŧ“ņ۟ČívköĄŪŸkŅZ$ąrîãX÷sEPÎfĶĶéÔ—ŗ? ™ÉÍ=š‘O{Žā—éúĩÄĖ 3Š ēVú$ÁĘ Ŋ5´'[5j3ęîëlāqŅŦbŠ4ĮØížS= =úŖĻ&+!=Ļ–qt Ģ,OPĸrtΜįēÔˇfŒúFOč‡D.ëzŊvÎåŋv5N.yĨØí˛Ŧ™_M'lĄūHNzœ›ōųũ<ĮW(HöXÖāTÃ˛<‹‚n~A UņBTš°"`Õ: yŽQĘŋ7Î_Ŧ™ž:q•¤\¯xˇÃ2Ģæ$¯Ąs˜aš@—„‚ęXÛ¸ĘVŪ Ȝų{r#Úˇ6ŪāĄ~gb#F­Ū™¨_AžZyÔĩ¸_jD&WŽīN]ņËøa´ŲĮ‰/ĸ4ŨŦ×ādJx5X+(Į,SΊR€ÚÂâ†Eõī÷q–1I,,đŠ3åoËâeÅĩovė> 9–ģŨnĒîįjĩ„’VŧƸœũi´…ÕŌŽ|ÚsEË5æ„ŧ_­_.Ãí6ŌuÂŊ—SÔu9?×XާočųZŊÜcQúD’`]…ChĪŦ͚7ÁTĀ_†DßH%MĒÂ(ČĮ‚”ëõĒŖ6ëT1ęFĩ=‡æ¸čŪg’˛)īD\ĮĨpqQ“Sęébä—ŒQgŽŋ&hZ{Ö*,Im–gŽæÚa7ĸaÅÕå6bSff5Ž—ã˛íÍļ(/TZÛčƒa˙ĢyÔËb ›RŠžü‘˛CÕķėqũæ\=Ŗū.Ōš×–/\\‚Éd6›Ēŗ0Â×(+gã¸ĐœÖęŦ~Ģ‹âĪsķ|§ŧĘĖŋgQG×öŖî”}ubÅÚ BĪ@‘? šĮ”ŽÕŦŅ1į¸t+ Éacԍ<ęKŖ.#ÕUõ5îĒįĘĒåQÃŒ9Ž•S o‡8ž"=$8•Å"PfAu’%E)i ĄŦcø?ē]åhM”ōhöĮæ2ŋ׊¯œŽ2¨÷­Š¸ēûĶlĢ–Øā+@XÂEÄ:ŋ“quŠĮ,•>Ǟ—ƒ*GŊŌÎdj¸ú-}3ƛT‘vÅGŽyhÜUĮeLĮ•Ëøy}ŧ€î:€šÉŌ7Ī™vbķFŒ:kŒëšG-HĄūmTõpĖ'õ„ŧhč1Ŋä|:ĮENĩ'ē0ΰBG€Íhŗæ*Ú\T|däꏄ›˛ël腊†åyfëu[p (î´ C ^™}ĐögŖ6퍕üpū[cWÍģØˆÂ’]ėģ´;čXzđÎcé1‡Í>ëq5Œ<ûQãÛĶņ¸XĖąØ’~dŦ3Ŗ*ĪY‘ĸÁU `Ō'—1a:‹G#="yžÆųn´këáînŖh^åQË펞ˇë¤Å_¨ŋ^ae 7›Ž°Â*†ëū;NqĨč8.įü0gÎÜĘŖîģŪ Ŗ.Ęlj3F]*U¤ēąuVÂnH#3öęžĻF4rä*Û¸¯'=Z‡G…e6VOĸĨJ𤅅ūîīė‰ŲCų€ÛbĄŌbGZ¯[1ûf~k÷§!;äQ7%F1ŸĪu \ĨRdqŧS>!úHŖžŖnĖĒŨ7sŧ>îŠ ‘ú ˜ĐÔeUŒ:“‰7”ŽbÔŽúŗZĸ…ĩׇ'9§Q.+ēõđôŸūЁë‹LÚ÷Ģ:Rvĩ<„( aDĖ(“æ¸ÁIéŧEą^­ā ×dKÃŌS§°î”采ķTöIūb3ߒl0˜íōA$_ôémķ¨?ŠŲ8xņ@Ų_ã#ũ˙Í1ÚyÔ8+õÅ.S ĖĘ5•ßę|cÚWÖ>75_†áņp(ÔŠ]:Ž*įYæc¤™Gí<¯kyÔyŽ…ĸŧ#ޟŋÎ:ŖhŖžO‰čQ#˛Gˇåã“˛cäQwWs­`Μšâvõá°'˙™Üf%÷’‡+'öeŒš˛;ėĩhĨí<ęĖíúŦž{C™GÉŋŖŠpĸÔ¯UujæQgéjUæŖÃe¯Ę×Ĩ ĄœķŦ’ôˆœTVgŖY}׎ņxt:i;­WyÔĢLåQëžéq9ûĶh+wíGmĪ*ĮŊ 9Ģ2ŊŨR[ĐC—ŗéTÛc€×Q“ĢŸĨpįd.ŋoæx}ÜyŒŽũTOŌ=ĢjSzNGNŨrŠKéäęĩ1ŸÖü8ō¨ui߲ëˆēpȇéŽg{ŨëãÚtŋ-%ŌøwDqū5á,šhQŖ8Xtq,ĒŊ>čMģ”]§ôØŅ¨žĩYĒÚ hŠíĶō¨YÚ˛xB=CÕųĻŌŽQãdGJPŖä@}BĄmü;x8cÔøvZítZe}Đžs{¯áBc?jįyíĢ“’ë°ÔŧˈÆ:Ŗ÷úĐģ9.ƒá„Ŗ&7ÔNą#É §Š(g[ÆÁÂåē”ō!eVÔ퍋ëÉö^-QSŸÕmĩ+…Ūë#)õi įvĸЏæá[ĩ•ĐmpAuĒ Ë]/0Ē“‘ŖŊ>(eÂá4ŖčjKŠmņFÕβŸ¤W[[œãröĮjˈ|úį PûQOFr`5#ĖčŌH=3ŽíQËE’ô”tM[:ûfŽ×ĮÍcdí×ŨÜëƒėQ˙Xí.E$QÁj|{ŨëÃķ× ŖÎûQ߈–kŽáS—Ēë¤ņŖ”īŦšĪÖĮ$Ë(nnäQŋƒcđ¸<ęâ æĖŋ•ˇäQ3gΜy;×1jŊļÄņNúŌĨ#} út*åIʓôˇáŒ–1敪>{cÔš/šQCîz_ßJĘį+ũ›āĘŖÎ_Ō“Æ_Nvuyē}$įTqéʑ>‘#-=jâpĩU(ģ¨bÔ* ]n ×ČŖÎĘõšPuîČ V`^ÁŽ|öš+ ĮjÕ7×ŧ흉¯čũ×ņj—`()ęyÔÁœųWs;š9sæĖ;ō¨ž×G!cÔņA§|TiÚĩŽøAĨ[ËŦŠQˡWNu÷Ŋ>(ļȲ&]‘Īî5L§SJ\y‹ą -s;F})cÔ/9Rö_ZęûhÉyÔ,˙ ä5ƒÁ¸ĩyÔđ¨÷uwēŠQW‘j•G-!ĖuŪ/ēP1XF˜;ōÉsE(”§×ČŖ~aėŋ&”§’Qķ‰ĸWĩŒQ/W…¸ôQ*ØxQ‹ÛY…õč/æĖ™(/^U~’|‡šeÎ|@N—ž<Ëė_;ŨŸwÁ=ŠŋĪū~Yƒņ ,ZŨ)"}ŠåQŸí3šœjŸÔđåT5&E•Ü1MBģ—(k{āĖg"ÚWĮež…„™4æÎ—ʝΏI.cÔa#F]6Ē$sæĖ™3gūt.ūXĩÜëC‡[(ķ¨Ģß<Ëāøáz~QWdÕ5Åz ÷•ļ•͏™G­ˇŗpFqQŠō¨ŗ4uZ64ēĪŗŲLģÍ {!œĶąƒ 3ptŠŊŦ~úŌ›G]Í­3Ú.~ŗuÎŖū›.ēŖžŖ1nQ/Į$Ãį ¤ŒQ‡+ÎŖfΜ9sæ/äâ3ķ¨;ĸ‘Ģ|ŠöŖĢM§×ë•=NĶ”ö˛˜Ô÷ŖÆ%ƁÚĶ_iũÚĩׇŽÍäpãi7 ōØ/í1ę*Ō‹#2Ni7iÛ>Ë2JYAcUšėŪ¤Ė w–5öú˜ˇīõáė¨öúX{}ČÖĮŽÖƒ€ZįŊ>ū&ÍŖfõxRzԇSJN5ĖæĄÜõ‘퍙3gΜų̏øę:FÍyÔĖ™3gÎü•üĢķ¨}Ą’[ !÷ß™øg1TõЌQyÔã Ö,ŠQ¤ĖTu#F]6Ē$sæĖ™3gūt.ž:šÁ`ŧCæQg5Zč(sæĖ™3gūL.1ęJ¯.ÚUO>ˇ˜ ˜…Ģ5įQ3 ã…_Gmžw›€ëöb>KŒVĢĨ0Ū{ˆÃp¤ļĒ6÷ŖîŽ–—žÛ–æ›Ä qŧĶ5ĐfÎŋzyÍÍFģŽ;x3oÅđyÔUŒz i!}lō´Åų6bÔ¯ŽT0gΜ9ķŋÆÅWįQ;Ū™†¸l“ˇßīõëü0jũŽ|‹ëú˛zgbwØoBlą´ëŸÍfŨk÷ ~FßĐîķĖx<1z€Đ;_ČŠV1ę įQ3 ã…_G=ēVĩßĐmWyōu§ĶŠéĖo\īL¤÷*B‰R¤ÄôRč{ąXčx,,7›M–ef+æ‹ŧut-ĸ]üRŋržƒRŽLĨn( ¯ ™–ĮãÁû=‰??qc Ų|û¤¯-ü–æŗŠ Ŧî˙ÕŌ>ęÁxQöÂ/UüR œG™1ꩊQ—Ÿ2FŊÚœG͜9sæĖ_ĮÅWįQˇ'*ĀĄ…HŪŖđ¤yŦV+rqĩ‡_JJxŋđĀá"bē´G ŋú‹Š~A@J|K.7ēMÉdŲxŸ ümTĨ=|ŨstŒōC ĩ=”iš6”¨œRÎųϞۖ&āîęüŗõmQŦ^ČQ;ÛBũäŪīļ[ŨmiŸn=ˆŖõeõ—Æ×`°<ęŦŖ–yԁö¨)R]ˆKŖæjyÔ%Į/UQ8ŨŲÆ2ŸËŊ>0!ē˙ÚrZģq¸ÖĪ{}|%Ūšöú8ģō¨1ęĮĸ ģŨ?ã˙ūûOŨeīM›8–_ũû÷ŋ| DüW4Y–áL ũĘŦ5ŖÎ˛æũúFq!‹§FqyWBâ[ĒđMĸ1Ė™3gÎÜäߝGÍ肎ûQw‡Ęúč”]đ~Ô_Š_ĘŖÎŸ’G;kô¤Čs8Āø%›_áŽ_Ą­(ŠāZĘŖnÃ÷‰°wÔ\\kö‡ÍzĩĸâĨÆU!ƒÁ`0ŪߝGÍč•в~ŧ ˛Ũn;žķ‘ß™ø­ø†<ę<ßĢŋO9mō<ûųų)*—Ø´A‘4•wäu[e̚UņFũđ´)˛ . S“JÍ{Dc˜3gΜšÉŋ;šņ›€74›É “Ųlfžŧ†ņ1XõëöŖĻ4 Rv`ˇÛáļDåŽũüû÷?û$IŠzØŲA7jϞ”ã‘Ϟ8xßČspŌJ“WƒÁ`ŧž;šÁ`ŧįQˇėGũ„ø ƒņÍđ­ŊœGÍ`0Į€yÔ§yÔÃīGMģįUšÍå}ŪŲŖąW|]ÚjƉZHQDo×eÛ5kM öˇ,Tøœ4úÄ†Ļ¯>ø ãéHÔ˜‰‹īŲūâ,ŋÅÂÉ˙ßÚûŨyÔö›qŨžĪįŖŅWąåriž÷3†!}eîGŨž—ž;-íŨĄãx§kč¸WÆ  —×Ül´ûčēƒ÷úøV ™GUûQwĪŖ6—¸!8`ŠXįķøĢ>ƒÁx"°n“Ëŋíå_TĘŅîėÅá2?\fûËx'õ?ÛĢ ž]Ÿ.ûLē،Įá[{1j­˙ŽÉ7a&KģūŲlÖŊ†Ņq?ęgô­āũ¨ŋ”GmŽ9wæQg}ō¨ų‰ ¯>ø ã‰ʋŪ^fņe˛“žķQ)Ą'‰ĪXšŲŌģ6ô0[9jũ(|k/ŨO_šG=ēVÕ^âzŊ6ĶÎMā*OžŽųÖB`ãzg"ŊWJ”"%Ļ—BßđŪu<–›ÍÆÜböæËģut-ęw&ęw&IRŊ3ņĒÔ …áĩ!Ķōx<ØŖsŋ3ņį'ŽcŒ¤!7Ū2élKž3q&ߙˆ˛ē˙WKcø¨ãEŲ ŋ3ņKá‹QwÍŖžÆ¨åF';ēhËŖÖšø;üÕŸÁ`< 촌—žswŽs˛“5,6FWøÖŪŨ냜jŸÔ°ķ¨+ŊŒļÁD_}:Ļ4hGemÜtāM—˜,1ĪÚ;ĨoɋɲŒ|{3J\5P§ŲÎę4•hHƒÉ¤´TÅ[rÂĮã1|FŸÍyŗ|_[Ļrd _+'•%ę×ņy|‹>øēĮøP4bÔEĪ<ęĸ¸ą×GÚŖ~y”øĩüÕŸÁ` ,t”ÂŽņį;dVȌÔ5Ÿåbt…oíũÄ<ęîh‰QÑÅ[įQˇÄ¨ •ë;#R:=pSiXū¯J˙˜ßę°íjĩĘT$Ųv_Ņ+Ø õŲlFŅfęŊ!b>‡ōaĻVH–e9×ÅoöŲykа´Û2 ĖRļeã¸ŧ${œņT –Gíۏ:į0!ē˙ÚrZģq¸ÖĪ{}|%ę1jšļܟGqu?ūęƒĪ`0†–8ÚŖ#D§ĩÄgu,CßËã%¯įįŽöļæQ?ŅŖf'ę}Đq?ęîPYRŖ ŪúKņü<ęs‡w&žEÄø÷ųĢ>ƒÁģTæ<Ī÷ÃD§‘ęõéē[u_âT*íđ­ŊVuŠ˙Ž5Ŗ TBËúņzt‚ĘvģíøÎG~gâˇĸŠQWëĖyÔĄŪúÜ7F­ÕøSüÕŸÁ` ‰ŠJĪØ§ƒE§íŨ?VĮ2 ~}ãö2ŽÄ4*fQļØf‹( ĨL!C)Ķ0J–ÛtšMÖÛĶz{ÜėN)›í!ÚŖ’užŨˇģÃnŒ÷Xņ“4ËáƒâƒëūsL¨ãÃøl÷E?–˛9œ@âã)>&û>é1ɎivĘō$+˛BdÅ9/ÎR ŒQææBĘB…ú…¸Î@_/ġö~w5ã7oh6“&ŗŲĖ|y ãâ÷ō¨ ÎŖnōW|ƒ1$æ=.NÛõGĮlŊ9ülÎ:j 9ŠÎæÛåûšË6˙ĒÖ‡īPķÚ­ĄąŪ5ųŖ>#5pôŸI%'QDâōWķ¨ ÆK0du.ԇķ¨9šÁøs€ŖK¯Ftē!“ũV„aē^$‡Ũqŋ;ėļûŨ.ŪívÛxU2Ō2ŽĸŊú6ŅaW›Ŗ”ōs"š4$4ËčnN‹(m˛i”OŖtGå¯n ßdķMŠl›DĘ( ‰oޚ…˛™GėÕ'•ĩmPalō@Ié ̚+YTm•rđ™qû0Rūŋ ËGâŸáį˙ĢôĪ…ķ¨ Æ/â=öŖ~‹ˆņīķW|ƒ1’BzqĶø‰ŅiÍŗí u˛^dÉ)=ŗä¨äwōęsHŽûô¸OqzŒO‡>Įx‹ĪAÉũ.Rw[’ø\8šÁ`ü"žGÍûQˇđW|ƒ1Žšô¨Ŋ'ąŖ‘ô¨OŅ2M¤ķ™)ÉŧÁ/œGÍ`0~ƒåQ7bԜGŨŋúā3ŒÁ°KĨGŊ:>7:]ōU(–Ë}´ØGá~ŗ(?š¯Ky؄Zî7áÕ& c”ŨB†ņvšÛJCîß­vģe)ãõ>^ö>ĮäF~¤fŗW_íwĢ}ŧ‚1,•TŧüŦëŸ ä>†ÜHoö˛žōs€Ü­!ģõ1^w›c,?§ũöúQĄéä;ų9ÜB'‰Īé(߯á[{?"} Ãplímî5=–_-õWöÛõČqÁd:âšūK`0ū†ËŖ&wZĀsvæQ§œGÍyÔ Æ÷bŖ^n¸MžŖNĶäŧ\Ëe6ŸĨķi 9›Jž˜]ųLę“ųLs§Ēa–ĩhæ†fnØ,æl:Ôcˇĩhĩ™uokzyŋ<ęî@‚ę-*pūqÅÕ;76=Æ(‹qûm‰¤IÔ Md Ģ(fŗŲ‰jã 2:÷įQœGÍyÔ Æ7#ToKŒĶ§įQ'ûH„ĄX/ãõĸülÂxJi|v›… A+)ųFZîĨĨ _“ܯHÎĢJ.Ĩ<@ĘĪ돜ÂŲqĩ8.Ļ'Åô* žQ6â̊kŲĐČzŒ:˚•>ŧjPj/ËΤTe#ÛĨ>œ¤œŸæĶd1+åbš(͉xĨŋ|rõzŊÚnˇÎ¯lˇYûØæ[ųJ2^,æ:ŽĻiĮm“ F/põ ųĢ>ƒÁ 3ĩõ)zŒ:ߎÎa˜KˇyqØīއ˜ĨS^Ū/ē;&“‰öhĨqiи/F=›Mõ+E@Æãņ°Ŋe0—Ąķ¨SÎŖîÃ_}đ Æ`ī¤GåîéyÔëĨX„É&ÜEáņ°S$K‡ŧŧSõHĨ>ëügRRpɔ†Ŋ÷}ÖVõęfu’$0Ð õūk;”Í`0Į yԂ9ē{Œēö•íQW8ÕĶé4‚Ũn‡Ęę&ƒÁ¸‚ķ¨_Č_}đ Æ0€¯ûŸz!øsŖĶįKGį0,Va:ŸîãíËãĀī,/ŸœGŊ\.ã8v~e§vhĀg6_„ MÃŽĩÎa0bā.a¸úāJ{šĩׇ ˜Íįs”Ŋ(wœöĘCY8Ō—ĘĮÆë7ÆĀ`ü1lkY÷įQ+wÚĖŖž\wĪĢŨ¯yÔf[ŸŨC…›Í°iŗZ­ ÄęĪ÷ã˛Ÿa¸^¯a éĶčúuÍp­ą|AO^´i#‹¯V˛øj%‹ĢŊCŠÂinõ˙ÕŸÁ` ƒc.cÔôz—įæQ¯—2åcŗ;iŧAøåÅô¨ëk¯Ŗ.õīŖž¨M9p%Âh2iîGŨR ƒšÍfŖŅÜ%¯ĘÍÆå Ž4Ē‚äˇĖ0OB-:~?ęku1|ŒÚXķ˙ū™ÜŨ7ĸÄpšĶ*’LąĢÅ]}uõ¨+`uĸČ6ŠK¯›4ĒÂŦŌ´ãÕŸÁ` ƒ˙ŲģZ0e™(ēqã­F#Ņj4ŠD"‘:‘H¤F+‘H¤‰ÆyĪĖ…qt]Åş{ž}æ›ī2 ƒėÂņŧ‡{My—ĮfųĀ­ÖWŒ:÷ÖŠđŸA~æVŪ¨Q?Q3›e0ŪņļŌ¨ođQk:Ũ÷QõFMnÛÉvS?I|—ˇã ĀžįfƒZī´!¤ĄÄjL#8ëļ4ãûķ“ÁCī^ūû÷ī[C9:˛ cžŋžHß(u˙ÅÖc ˙§õO}ņ Æ8ˆöJŖŲc}Ôû]Ē^K ŧ|ĩLSņ :đ3ˇ’õĐŊˇĢQˇņ§Ō¨ ÆkĄŅ¨­{ί|ÔÕõ>ę‡iÔX’ã8E„Vģ%ZjŠœĐ]ŠN#Đî˜ÜDŠĸÀ"ļjmQô“ČeL}ņ Æ8På]b]ŪåĄÕ“PŊ–ŠD“+ĀĪßĘįķQŗFÍ`ŧ1F÷Qį|ԇs>ęÛÕiŦü6˲Nŧ1]” DnŅŦģ´M*R#ũųAĄ;jsŠJeŅĮÍķL9CĘRO¨#™Žü´~S+ļĀŽpäå"ķ¸ūŌå]"‹8ÖmBō2õ)NjķÍc*ÔŽWoŦwÛ$ }"R-Š˛Ô§8E>yŒ´s}œŪ{‡s}<™šÁ`ŧNr}ŒāŖÎGMJuOŖ>§\‹$IVĢU'MÁ÷ũ­&üxؑ¤ėy%ũÃĐoßL4‘ã‹„Žëę›mMËåōQuh&ŒcQžęHÂxÂAL}ņ Æ8 ō.¸Ũ=6×Gčôk‰Šđ'W€Ÿŋ•īåŖf}›ÁxrŒęŖ>5ꮏúLŽû|ÔŨŒv:N/E˛MéīČGmĨļsōŗÉîšų“$Æ<íĻ !ʧ‡H#‰ke}ŠčËŽųëŸúâ3Œqđm—wyŒˇŲÚ÷j×ÍüušD“ģ”Ÿŋ•ėŖf0ˆą|ÔŲEuņā\ Ā$;ŋĻžø cTE§įÉ#ÕéƒÄW{õZĸīeëeK§×Ÿš•īåŖfšÁxrŒįŖnč4~䊏ēx¤úĨûS_|ƒ1˛J1ęeúØj‰ûMLÕˇūęāįoå§ú¨ŗ,[,ŽëNŊßW$Š"i­˙ræíAĖfÍ.˜ę ä=ãī0–:;įŖ>jÔãû¨_S_|ƒ1ļĨbÔŪöąu‘FĒZbäoB÷āįoå§ú¨įŗŲ+’É@WL“Öú/T‡<CÂ+ũ:Õ¸+d0.c,un|ÔUĮG}xœúÕûS_|ƒ1âŦ)īōPu)Tũņ"ōŌ8xøų[ų‘>jPĘŲ?…™ūÖ$Vã|=ĪGõ<×đm|ëÕj>Ÿīļ[ŖîÚʰé›Ũ1›Ų[A‹9Ŋ¯döŠÂs"ˆ­8„ã8E"bJ@p-Â0ė¯ßĘGÚGĮ×Ē:älŠ_nęė‹˙ńl•aü%FķQˇå]ÎÖL,˙ÂGũZ˜úâ3ŒîŖŽķĮjÔu¤u.<ũZâô đķˇōS}Ԇˆ‚mâ1Mũ(ŠĀoĨ&ĸBD ‚ IT*Č8ŽíŊĖTĻvJģcäÍ ­Bt°iŲŌfD0xoĨķhŲûĐ73€Ž+îŦß=IÔEKsŌâ‰Æƒ,—ËΞRģG|ĪģōCc0îĮX>ę–NKÛG­Jč 1ízj•xÚūÔŸÁ`ŒwĢõ&ŦēTũņ}čî6OĄ?+ßÎGM¤ú\k`ĢÍeû Y,FŨu ¤/Ø4ûwIŖļGÍyp¤ã,:ú3Ö`/@ i;§fæīŦŋŗxŒY´‹ˇa‘đ㒰Ë|>īf0„‘|Ô2+”FMüšÕ¨5ŖŽ(¨øļŌ¨AŋŲGŨbę‹Ī`0FĀrŖuV=VŖ>ø~ízû`=šöû*­üTõ9ĩYÛ!šļ?ā˛FũĖž†ĩž9`~öđe°%˙ƒöæŲ9ˇŪ?PĨŌ ÷zIŗÎžÃ`<#ú¨ķŽzž~cąĻÃ0˜}ÔėŖf0Ū ŗ¸)īō85n›*užįm‚ÕäÚīĢ´ō#}ÔŌb§6Ŋ”v[ŖžčŖļ%îūVyĸQw=ŌRi˛sŖÍķŧŋ’ēˇ~ŗÕÖ¨—Ë%ÉzöH›™c0kԌŋĸ>j[ŖVŽƒ4t:+0Jē'õ§‹ÔS_|ƒ1¨ŧËCĢ%æû­Ō¨oy“kŋ¯ŌJöQ[ mE䊥5Vä0 âXų¨…6%Ūl6āĪØjf06ė"΃ čĪoúļÚŒ”Š; ëõzpÍ?ú¨ihÍâķ9žQÎ=3reYJØGÍøcŒčŖÆOaų¨gsĨQīķ \šZ sũ€}ÔėŖf0Ū¸á}™ō.ķQįÛXiÔĄ—DŪäÚīĢ´ōí|ÔWbPmÆųúžæ ŒG<UŽõz>Ÿ#bÔ]<ôø|ėúģjÔR`“ëÃÁįą0;%ˆ “ëcpũVŽĩ‘Ę19Žâ8‹­îС$\PŦôpÎõÁøcŒâŖŽĩM–éâ¨Q/đ‹ŋWtēÔ¤ēTŒÚ *öQˇ˜úâ3Œ{ĄĘģÄēŧË#5ę" Uy—ČK“prí÷UZųŠ>ęÛpC9•_Ąķ~b&õ(¨85ãĪ1šēęú¨IŖŪe%¸4ũ€RƒQŗš}Ô ÆÛüëĢ-īō8u•¸{–‘ˇIÅäÚīĢ´ōS}ÔˇÁ~ûotāw]÷ō—S3qpÍDÆßc5žîŸÕ¨ķ#Ŗņ^{~Å>ęS_|ƒq/â\™¨ÃŨc5ę:öžWcų˜^~ūV~Ēú6ę–Nš'6>ę–QīˆQ÷4j[øĀūÔŸÁ`܋p¯4ę8{ŦZ •:/Ū3hŋ¯ŌJKŖîÜ{5ęō]|Ô cØuy‡:'ē˛5jëãÔG}ĸQŸhåö§žø ã^¨ō.ąÜÕ¨eäžD1ūûūÔŸÁ`܋åFiÔûōą>jzĩëî"÷´ßWiĨfԃ÷ŪŽFmâŦQ3Œ›Aĩ}ĪšÅG­5ęĸ¯QĢ*äĩ.§hōQ8ĖŠúYãŋīO}ņ ÆŊ˜'My—ĮjÔž{đŧ-kÔŋÖ¨‡īŊxøČFŖ.í8kÔ ãfŒâŖÆˇ}KŖVĶ.MÍDå)k]˙e fâä*ņ´ũŠ/>ƒÁ¸ßąŌ¨§NŖ¯ÄˆMPĮū†}ÔėŖžv>g]9üšų|]ÍįëyŪ|6[ô6Ų0)8˛,sœÅzŊž!+ˆyí‘Sp0Ū#ú¨R]7õÂQuŅŧą¨Z ķėš‰6ÅøīûS_|ƒqjŠęĻŧËÃ4ęĸ,ŗČÍ×ĢDøĪ ũžJ+?ÕG=X3QęŌ-TúDęOÆi+°€ââ‰~.{3âô´''2|CVŗĐ ģ"ƒņNÅG]kuĄétŖQ;Î|á Ŗ…k’¯Á¨^pĸQOŽO۟øÚ3ŒûWŠQ;Éŗ|ā'ÛīŠp¯–i>ƒöû*­üTĩŠ~اžV]īĻūøe˜R†Zę>*Ū´•˛LcNĪuÄ z°Z­¨ē"ˇ÷•\ʐņžĪG}Ô¨KÍąuĢžÜ‚QWėŖn1õÅg0wa¯õzķX:Ûm*o•¯Wi=ƒöû*­üTõ93xžGũÅbqÎæaã‰Ûŗ™ųÁ“D}ÎhMq–Ā÷÷ēÔ8øÃŌqú+Á„f Æ;a$ĩíô¨ÍlųĒŽ›ō Eŋŋ{Ÿ„K-Ļ f0^ãų¨ãGyÔ¨—’4ęēi[š}ÔMę‹Ī`0îLŒöÕ¨ķTė“pē“Ģž¯ÕJöQŸ§ÍWjÔö }z<¸DÂ÷}GeНŸ[ɕ|žÁx-ŒãŖ>étŖQ;§ĩ&Õ}úNĨˇ(Š žžžúc°BÜ1°iĩZ…:Á¯S ’įųršD-úöüI’ānđũũv‹Ķ,ËÎîSčŨ1ˇôõzrôA}öQ3ī`§uœ=ÖG'aíy`Ô“Ģž¯ÕJöQŸ…¤7Mͧ2o#Úŗ™ųíV[ŖÆãPĸëļZnædšņŽËGŨž–¨Hĩl}ÔRkÔæGkÔ҈>jpW×u‰wÆ€Ų !p^QŌĢĘzØÉß÷ÂPŊ)†úöü˜–nļ8GÜ ÚŨOÖāyf÷ũÁČåõOzå ÆŊđ4ŖŪÎõĩJFíMŽúžV+ŲG}^ Æs ´Ory1ׇōQˇĮôQĶëhéFŠI8ųjđ6#WĢ•1:˛šņŽ×GMĖYZ>겞¤QâI&Ÿ§`ŠulÜŽ”؃xžĢohiLgbÔ¸í”ú(ãęŨŗJiŨŨChBÉ25!û¨ŒˇÆrŖõŽŧEyžž_ _iÔkÔˇhÔīäŖžƒĘpxZQĻŽÅâl>j“ëCų¨MŽ×Ęõū0ŸĢD`xz’v„Īvé8ëõZrŽÆûb,ucųōQ̟ƒ­QėŖîĢĮūIŖFûũũ¸Îßķ,ĄŅ7˙äGc:ķ`fÄqĒč›Ũņ]_ą;ĮĨž­–*įūÔŸÁ`܅EĒ &֏ը+Ą4ęļŧËôÚīĢ´ōS}ÔãÂäŖœšņÆÉG­iŗ­Q;'uų5õĪų¨Éíyž­NyŽ?g|}nH/‚*^ölŠŖ(RƒÛ89ˇ!~BČ{öQ3ī ĐiüTõc}ÔU¤4ę4ō&W}_Ģ•ŸęŖēfb8Öl\3‘ņÆŅG]ūšú58Īsú‡'-&E”ģ1iä9úƒķˇōõ1ŪpøÖ4‚€1]mäōúM‚ũØG8‘—ˆt r0j'‘ˆIOĻ>ZaE„ŽÜ<ĻŽ”F _Dūi*'RŸ‚V„ĮøîŖf0“`,ĩĄĶ]ĩÅ´˙ŌGíēn–eEQ„aAÕžlHŌ¨=×%gˆy‘ĐĖŖīveŠéårYRdŋŖą"ÕģU:‚~ŲDö§û¨Œ7Fu_B.’ĒĶM?Pu"XŖžEŖū@5ƒÁ˜ãú¨ËÚöQˇuģéAų¨;>jR¤ã8žéÔöÄu7 ņ¨4jĨ čcŒJ§2ė¨ĮL‰õhđiDšīō6{žIž×°šÁxW¨äą\ĻtP7­fÔiėßé+ū´V˛šÁ`ü!ÆôQˇ?˛ņQĢ„“Ši7i@ZŖG÷QÛ}PY­9įãúĶ^zƒqvĨԍ׏fŲC}ÔĩīQĮŸ\õ}­V˛šÁ`ü!ÆõQ“F-ĩF=_8ĨQ7t­Ō¨ũ°*ē2møü‡õ§Ŋô ã¨äąôÃ}úÕ¨}ŋvŨ4 'W}_ĢÅ5:wī­ĢÖGŨŧœ^ąFÍ`0îčú5hseų¨gsŌ¨kĒē.+­Qû'õä*ņ´ũi/=ƒÁ¸áĻø2 wĩįÕUũ 5nj~ßK’prÕ÷ĩZųŠ>j“/ÚgęŽ×s…Yøvęizh6›aÛšŦԗq9ëõå‘&âēëūôĩySĩ´ŧËJ”Ŋ/åžž:ŸI$uõĮY`ßūgø#ĖŅ9ŸÉĮBlFđQĢ­F R-[ZJič4Z ÃāĘäú ˙4Šņß÷'Ŋō ã.xqĻJéÁķŗMō :Ī2ĨQû^šŠÉUß×jĨÉõŅģ÷Z>ęŌŽŋ‡FŨ'ŽžįáąM4oŗŲ˜’…•ŽŨ€ˆŧX9ņ2.TfüqäåHg+(‡ëēŖŦ¤“sÛT]ŋūkB˙čœsûcqÔ¨K[ŖžŅG]Y>ęFŖ.k%S딌ē¯Ql;íĨg0÷Āķ/!S?Š]/Ãų¨ŗũViԁbÔ“Ģž¯ÕJËGŨi}ԍFŊy}ú'6h¸Ÿ)#ŪžōĢÕ #—ËĨMæŖ0í4<\jōŠĘ#ę öĸ >^’žÁ`{AŖĻ57дާ68ŪŦÜ,R \ØG7…ĨcS’ö5ķĐVSŌŗęBŨÚWr]ČO…íŖļ5ę_ų¨Īhԋš>ĘēÔmŖQŗēíO{é Æ=X‰â[Čm(@ĘȐFŊß* Œ,qrÕ÷ĩZųŠ>ęËújQëՊúāĀįlA=ÆĶ¤š‚B0pđR|VĻ:9ˆ%âRĢߎÎņ%5Ĩ$ʍe“ĄB^T¤­įg5jŦĮ¨ëfyh—KįžXI’¨_´fÍ60'ąâÁõØģ› |ß}ģtŽŽ ÍRŸƒą|Ô¤NWē#m5kÔŦQ3īGT_BîETģnķrâ|ÔŲ&Vuč=ƒęûZ­ŧMŖ~b5‘ęs­Á0ÎzŊ^÷ äezé8 ŧã|ĻŽáę8ĸĄŲļl+ēöš‡}Ô˙ūÍįŗsūä> îŦ„öÂz:YÚéQwö2ë1ģŖÜ}đčįŽÅxoē¯Q˙ÚGŨž–XéßwãŖ.ēõQ{GuÕāYãŋīO{é Æ=˜‹Ãˇģ88¨7ĨQgitđüJ• Ÿ^õ}­V˛ú8kOĒMä‚FOÃ÷ũåŌ™iPp*Ã`¤TBī= ?w"?jÔaš/˛ņ*ûāęā×÷ĩŋ_\ŋūËsâčøp.ũÜąīą|ԕ6Q5jËG­Hĩz9‘5jÖ¨Œ÷ÁL¨|ÔiÔžĢŒ:åūčJuž„ A`ÔĪ úžV+ŲGmA™=Öë<Īí hašĻƒ“€K§iBʏĨQ;×kÔ}=ųGõšŲžįšįæršL’„sy_ķ­áĸFŨuz›9íV[Ŗ>9úēÎõgb4õ9ē>”:×ZlaĩŨŸöŌ3Œģ ŧ*ô7ЍUĨĖķGhԅÖĀKÖ¨o͍ĪÜ{?ÍGĮˇëēũ‡NY ~xžKMŅ Ú\‹Å”€ŌĘšÕöQ›tƒ*.ö"u‘įƒ#;ŗæÕjehsgŧ~IpM<4˜žaI—÷5o_ĸ=ëŖn?ÛAu÷Î>ęĪÄ(>ꃭQŖ^8s­Që7R­5ę|ÔÛN|í Æ­Č‹2Øl…ŽcX…ŠQīĶø!ų¨ã vŊR°FÍ>ękŅņ*#§Ų0yžS.‹Åi>j<âÁĶ›L<ĘõafŗûžīS6 bėō˛FŨ*ŊĨŽ}LŲ¤ûã‹ĸ z˜sŽ'WË[,čËÂāžVŽõå\ƒë1š>\+ׇ:ú|ččŽCGį\Ÿ‰q|Ô˛Ņ¨+KŖ^hē:V~945û¨Ûū´—žÁ`܌Í~˙-dÅ oĩđÁ¨ŗ$z„F] ˙āy…`ú&ú#}ԌßÂäŖ%įŖūTŒâŖ>õQŖvãú0¤ē>S3ņcۉ¯=ƒÁ¸ņfFí‡bˇMJĄ4ę\đQW ë*ß5kÔėŖf< ÚÜ2`š \3ņc1ŽZiŅCĒ%iÔ:aΑikŒēbu۟öŌ3Œ›§[0ę0 oEŦ4ęJ3ęņ3~DJŖÎXŖžMŖ>sī}o5ƒÁ˜Ŗų¨5sŽmuëú0¤Ãŧ bš5jãÕŚQ‡Ņn›dąŌ¨ĢĐ„ZeēvŨ=û¨ŲGÍ`0žcų¨F]5jUe‰86ĩJŖŠ}ÔmÚKĪ`0nF”ėŖ(Rä-ņuĄp˙!u 4ę-kÔˇiÔėŖf0…ą|Ô6s––ē˛6ąFÍ5ƒņ„z31 ÃŨ6I‰Qãį>jEÔ]wûĪ úžV+ŲGÍ`0ū#ú¨ëAZ6ObÚ`Ôû¨Ûū„םÁ`Ü?ÎÁ¨c‚ŧĨi:}đ}ÅĶFר}E×7ą˙ Ēīkĩ’}Ô ã1ŽÚrwÔ§>ęÚÖ¨%kÔŦQ3īW(F-ĸ`ˇM6›˜ŠŧTy1˛ē–ĒŖëĻqđ ĒīkĩōS}ÔvŨmÎtŊ^Īfŗų|îë¯~fÎÚķ<ÚdįŖžįŠž_3ŌD\wŨsĖĄ=ûGËģŦDŲûR^č+A…l¤ŽĖâ8 ũY]{RũŖsŽÅx>ę#s–§>ęÚbÚėŖļûS^xƒqÖqŠ5j"ēiz žŲ&WŖ.ĢZĢß^šDĪ úžV+?ÕGŨ'Ž`Ņxn4„T$Eę§°ŠÕ‚MxŽûmÍÄëҝ„xũČˑÎVPŗō;WԁÉGm*’_˙5Ąô’ķQ*FôQÛĖų:ĩ­”؟øÚ3Œ[ሊ5)ĸĩđjˇ-ō2ž:ĪseĪöŊM*žAõ}­V5ęîŊwHŖ.z“OũËu/f?ąÁ~…î>đ”_­Vš\.m2 ÕL¤ēŠb/ âã%éØļ—j&ūŗj;ļUûãÍĘÍōĮɲėžVÍD÷rÍDSe’æĄ­ĻfĸgÕL8ēĩ¯äš‰Ÿ ËG]Úõī}Ôg4jy´O|ÔÄāËōcÛ ¯;ƒÁ¸ qPŒē1c¤•đteÃ`\:Ûīt0ęøTß×jĨņQ÷Úõ[kÔ6’$1B48đ9›GDņ´ŠĻ  ŧŸ•ŠÜ b‰8:āՎŽC!5Ĩ$ʍe“ĄB^T¤­ōßg5jŦ,Ŋŗ<´ËĨsa_Ŧ§L'nÖls+\Ŋģ9‘@kūúčÛĨ3ptLh–ĘøœŅ¨oņQŸhÔ§>ęúÄG]œú¨oWz‹ĸƒāëëĢ?õøMôUņ<Īq[@-úĶ‹įĮßv˙ūūF‹3EäëęčyŽģÆā }DԄ:‚xžį?ŽâkĪ`0nÅŋ–Q“"ZÄJŖ.Ŗ`\õ~›*{v ’Q?ƒęûZ­ÔŽÁ{¯ĨQvüÉ}ÔDĒĪĩį<Ā3-Ą:ÎÂč9WÚ$ ŊÄž}Ūq>SĮpu|ΆfÛ °­čÚköQ˙û7ŸĪÎų“û4¸ŗÚ ëAŋŋģvzԝŊĖzĖîhw<úšc1Ūb“•úÔõqĒQKËG]žú¨īĐxÁ]]×ŋíoEĢ$nŒ_xD|Ī×MD@Âҝôëa¨Hx:bfĀî8MlJĶ&ˆôâ™ ÃPí~Œ&Â5ƒņ–øŽ%ušF¤ˆf‰Ō¨U‘—Q5ęũ&VučOŽ÷žb+/hÔõ‡jÔ …ļų‚FOÃ÷ũåŌ™iPp*Ã`¤DāõŪús'ōŖF§ĒQ’eãUöÁÕÁ‡/īkŋ¸~ũ—įÄŅņá\8úšc1ŪöQ×§ÂË#|Ô­F=8ĻĀŨŋķčã×;o•dÅąĢĒéίuē˜Ī­ŖĮ¨Ũŗ }ü™ŖoEJ´sŊ×åõO|í ÆMĀ­ėKș8h3†VD•‘Ŗ)ō2žzŸDēŖ7šŪûŠ­dõš-Ų-Ä3np ¸tš&¤üXĩsŊFŨדôQŸ›āyŽyn.—Ë$Ih1—÷5ß.jÔ]§ˇ™sŅnĩ5擪ŠëŦQ&FķQ[o ĘFŖÖŒēëŖËSuiéˇõI=C Ŧũīīosķ¤~'ԟû"ŽsE_˙#Õ?üījĩĘ4‘VĮ-éC+i ē-Iô°5ísëŸđē3Œ›;æw,įQeŅ4Q• U^ŽÖá6J›ŖŽŧÉõŪWlĨÖīŊUëŖ.›{ø›kÔ*ׇŪ´Pų¨[‹oY ~xÆIMŅ Žk% QZ9ÃĀmĩIg1¨âb/ōQy>8˛1kÆÖĐæÎxũ’āšx,iVčcI—÷5o_ĸ=ëŖn?ÛAu÷Î>ęĪÄČ>ęÖŨĄ}Ôę Z=z45õ}ÔÔ/Š×øZMtˇlAÔˇéΏĨŌî&Ž qs HKČËsũĮõO{é ÆmØî÷_B:QiŅ$ j_?ę˛ŅGĮuąF}ŗF=|ī}Qõ•čx•e›zŽ“N‡a`ĢĮyžS.‹Åi>j<â1ØŅ/"a“‰‡Cš>Ėlv4ž˛ac——5ęVéÅUX.—”Mē?`˛Ŧ`ʞ\-oŅ8Ã÷ĩr}Ŧ/įú\ÉõáZš>ÔŅįCGw::įúøLŒįŖ>’jIuĪõ€~WC>ę{”ęËj0ū‰Üâ&įJ[&KFE­6#ۚ4ēķā~ۙŋ´[ĶHuÉFōÃúMÚĸØG8‘'ˆ4ũŽå**DˆČW­ęHiÔ[Ąd<'‘ˆIj >ZaEŽSŖĘG}<–*+“ļúņ­ :>ęūŊˇīŖ.ßHŖfü&õ((9õ§ÂhÔå=>ęžF­ûJŖnĢ“ĩ߸>íŖÖ¯îU20ĐĩzWēŌz‘đ$âēöœí‹J‘Æw^ą5jL"DD;čS$ŠN"ėŖf0ŪqēũŌr[­„bÔyčŖ—ŽUR>Ö¨oÖ¨‡īŊīíŖfüÚÜ2`š \3ņc1–ē÷fâŌŧ™hH5ū×õîŖ&÷—I§3ãe¤$ƒ ëÜAŽQ’—Ë&Ų]ĄÕf[=Ļä{؝\ĶvDWÚjŌņĩ‡Č#ėŖf0Ū"Ų|ĮŌ‹öļkˇJ|­'‡#ú¨+Ą4ę"f5û¨YŖf0žãú¨–zvÔ¨ÔâK›fÔ#û¨í>¨,žŽ;įãú_{ƒqĸDiԁØÛŠhžz”;zDu)–žąF}ģF=|ī}o5ƒÁ˜Ŗø¨ĨĨQĶ?uhšŪL<œjÔA5ļúuû^wƒq3Âx÷ËPlmEtŋņe¨rG¨QךQīcÎG}ŖF}îŪË>jƒ1:ÆōQÛOŠ}ÔFŖĻŸVŖĘą}ԯ۟øÚ3Œ›Ä*×G$6ļ"šĻž t=đĩą*'ŠQīXŖž]Ŗž÷˛šÁ`ŒŽņ}ÔzZĨQĪmúĐhÔ^èåx>ę×íOxŨ ÆÍđEö-¤Š­ˆ&i Cīāųu^ŒV9Q3ę-kÔėŖfšÁxzü‰ZžjÔôQŋVâkĪ`0n‚'ō/!cŅÕEeŦ_NÜnĮōQמŊĶØŸ\ī}ÅV~ĒÚŽģMĀ™ēëõ\aākšU÷°ÔYĒf:Uĩúz\(zŪiW'¤ibf dÎ*^ķãA¯?ģëÁÉ@ŪôQˇų¨5ŠŽF]ąēíOxŨ ÆÍX‹â[ČDÄ]TĒʉ~žˆŅ4j_7ß$áäzī+ļōS}Ô5=mbq›ÍƔķÃ)›Z-؊įēßÖLŧũJˆFöį_­V×Ī0"ŽĖAũˆĩá¸7|ΌįĮã|ԃų¨ŲGm÷'žö ã&,Eõ%d‹ŽFRúčpu ŪįyĩīĨI8šŪûŠ­üTõė'Yĩ_Ąģ<å‰ëRÂX†j&R]Eąņņ’ôíēŽŅc12Šĸĸ(ėŖØ…ŧēŽ#â¸ø_´Ļäb–e:8ŗƒæ@žw<=rˇÛöĪn¸Nâŋišâ,p‚tĘvõÉsĮÂīŌzĩÂ.Ø×Ŧ˙8Ō:}˃ķÅž’‹*ž)FôQ7Ū=íų|ÔėŖ>ö'ŧî ãf,Dũ­uÔŅEëPEÃQ4ę<Ī•Fí{›4ž\ī}ÅV~Ēú˛Q„ ú`įlA=ÆĶŧ”‚Ē hƒ"âŗ2ŒŧqŠÕoĮq(ˆ­Dšąl2WĐČō´ž ĢjĢU†á›•caäAkÆ#ˆ?ŠN“́49Ú‘ķūH ģÆb=‚´zs"ļF=x,ĖOô>‰cŗ~3Ō>}úęA}Ũo˙Ĩ€ņ6xœz~Ėõ!KîãōQŋVâkĪ`0nÂ\´FvtŅrÔ`”@īnuļß):PŒzrŊ÷[ųv>j"ÕįZƒžÚgŊ^¯ûdō2 Qtœnx›ĶHҰSÚJ,ZjnOæ[%nä€ēwt'û~2"°ŖsöĒ‘z÷ žđų|ŌYŗũšõIūšcŲÁ™uú&¸hGb~ŖĪc+ÖpnyŒÅ(>ęƒbÎZŽļj&‚TŖ›•uVÔÔVÎG}ԟøÚ3Œ›đOHĨQ+3Ɖ.ší‚ƒâĀū(õ~›ĒĖ!Ą÷ zī+ļō‚ēҍw*hÅß[ŖŽ•Č3Æ yQŖŽ´×wštfdāvĐųoĻ=zīöV#ÛAPh%šO_ą*ŒÁŅWĢŠÍ´$!ČözāņK…Zkd¤GŽÍî?ŽyđĢAgd˙Xö{¯ūČÎu™Ä=Îx(FņQWõ!¯d^ ÕĒi•Z}G“›ŦÚfÕ&+ŅU}’ëÃÖ šzûŦūÔŸÁ`܂oŨ›4ęčĸÛ}XģŽ˛j÷û¨÷›XiÔĄ÷ zī+ļŌhÔŊ{ī€F­ãoėŖVfŊgNĶtp°Ų4MHųą4jįzēŸÎÂl].—´žNŧ¯Žƒlw0ØßÚé ĐõŅŨŨŨ:ĶīhÔũŠĖ™ĸ5ëiĪ‘5ę÷ƒØ´>ęæžs‹ēŦģŧŪ‡]qؗJ]!5 Û\Ņiõƒ[Wuh}Ôę;2û¨§žø ã×ĀÃŒzFŊéæúH÷åģSžļû5ę$:x~ąF}ģF}îŪ[}˜oãĩ°Q¸Ų€ņ;‹NĐæ X,æød(ŜQSmĩņ'ęŊÆH\äy$&97oöU\ėBį›e™ã,Lü*ø^`(+D_ 4ÖîÁ‘6”ēũƒÁø5ŠĸŖžGU_Mŗ¤ÖeU‘—ģ}ÔybĒJ°F}‡F}æŪ‹§OŖQãrZq­Q?¯úJØ*H5m"§™+ | Û`•‹Ķ|ÔxÄĪįsGįôĀ&‡r}˜Ųėžīû˜ÖfÎļŠÃđd9¤Ķ됔ëÃ|•īh3x8Æ ŽëØ?Pģûlé8ũ Øō\ŽuĶĮoĻ"9}đX*×ĮZåúĀbÖoF.Ož8įį\o ËGŨ|UŋÁGMõîDŖvHŖ&ušHĩŌ¨9ĩ՟úâ3Œ_c—Š‚‰KQ čĸģô TŊzˇŋ_Ŗ.båĘ.kÔˇkÔįîŊīíŖf\ƒ+ķQ_íú¸Ę]q>ę7Å(>ęŽF-Û|ԇƒL÷ŠQkē(ĘZW!g5û¨ŒWE˛Ũ‚Q¯ĸbHM+Ą4ę<Žī÷Q—"Āŗ`š}Ԍ@ZÂûį1•8ޝŦųČ5ߏöQkē RMuÉ>ęļ?õÅg0ŋFŧQŒÚų .Z%Tä%ē_Ŗ.#•Ũ:gúúÜŊˇzk5ã/6´Z)‡Éjĩ˛‹×0>õQ×äŖÖt:55û¨ÛūÔŸÁ`ü"QŒÚ‹öƒēh‘(ē ũû}ÔUä㎙ÅŦQŗšÁ`ŧūČGŨhÔuĢQ}ÔĨĨ|Z;õÅg0ŋF”ėąÔEs]ˆŧüû5ę:Tõž5ęģ5ę~k|ÔeC§›8kÔ ãf<ĐGŊhs}ėÛ\ėŖ>íO}ņ ƯƊQ‡ŅvPŨĨ:×~îöQƒQ׎ģ‹ũgĐ{_ą•ėŖf0ˆŋÉG­ŠŧP>jËGÍõÔŸÁ`üAŧŖb;¨‹nRŋö=UäĨĒīÕ¨1įmb˙ôŪWlåyēīŖ.YŖf0÷Ą§QįŖÖ5ĩ)ōĸķQ‡ėŖ6ũŠ/>ƒÁø5|‘iFęĸIĸę†Ģ"/Ey—ē–JčvŨM<ƒŪûŠ­üTuŋō ÎtŊ^Īfŗų|îãëžU÷gíym˛ķQ_sEĪGöŗC§ibf¸2WÆ( â5?ôúŗģœëã]1’Zö}ÔøÁ¯ĖލˇØ”×ÛŦÂĻ­™Č>jÖ¨Œ—„`ÔIœ ęĸé&Ē#īāųõnF]VŨ}/MÂgĐ{_ą•ŸęŖî“@°h<ˇB×u)ŽS6ĩZ° Īõō$_™„™Föį_­V×Ī0"ŽĖGũˆĩUœúM1Žē:ė|Ô `ԈŠÆ05˜}Ômę‹Ī`0~•ĐŒZÄįÔŅ:V9:Ę$šĮGe91ęM*žAī}ÅV~Ēzö“ŦjXb†qŽÁSž¸Ž]ĩˆ†j&R]Eąņņ’ô önôXŒŒĸČN1‡ņvņnŖŽãˆĻfĸŠ9˜eY[3ņ4ōŧãė‘ģŨļvÃ5˙ũKĶg¤SîT™<–Ē™¸R5ą¯Y˙q¤uú˜į‹}%×L|SŒæŖ.ę}yâŖVĩT›´k ¨jŠõ_ø¨ü!ëŋƒøƒÅ˙~‘"Īņįöũũ}{†$Žņ‚atß@¤ŗ;"šŪ}´č›MH‘Ëëœúâ3Œ_c%Tō4įÔŅ*Qūį" īҍŗŨN1ęЛ\é}ŨV~Ēú˛QA9“Z¸s6 ˆSEo âa ŠˆĪĘ0RđFÄŅÁŗŌq b+Qn,›Ė4Ÿ3f6GÁcS†oVŽ…‘?­ Ŧ &§irĩ#įũ‘6@w˙Ä>z,iõæDlzđX˜Ÿč=hƒYŋiŸ>}õ >Žî{Ūā'Īx]<ŌGŨ0jCĒ1Ė ĸŋņQ㗜ž ã÷_ W4øt ūĻđW1hҎįÁŸ9NÜ|č&@$܃]pē? ?a5ƒņfXhFÄá9u´LŊÚõĒ0¸ĮGŊKc0ę*ô&Wz_ˇ•oįŖ&R}Ž5čû¨Û¸R[gaôœ+- †(bß>ˇ ŧM‰i$>gÃNi+ąhtđđ%noĢÄíP÷Žîbß.Ô[ė,ÍHŊûOø|>éŦŲūÜú$˙ÜąėāĖ:}\´#1ŋŅįąk8ˇ<ƋbÄ|ÔûŽzi5‘ęę Ŋ€4ę‡û¨éËŦi)ņ1‚ßį<Ī´ļœĄ?8ΛHŖîlÅ_\Ž•m´čF.¯sę‹Ī`0~š8(: ĪŠŖYĒ4j•Šã:JŖŽüɕŪ×må{ų¨¯ĮD5ãŖž Q“×wštfdāvĐųŧŪûŸŊÕČļAZIîĶWŦ cpôÕjEj3-I˛Ŋ^#xT˜é(ÔZ##=rmv˙q̓_ :#ûĮ˛Ø{õGvŽË$îqÆCņČ|ԎŨÕϞ–JŖū5°įyßßߋųėqr€ ŽŋĐ,Û͘˛E_Á.[Ŗv/õîßøšœeYyĸZÔĮVåĘĶûę>û¨Œwč41ęŗęčÆĢ=WņaåÕŊÕG‡ĒöbÄõ}5û¨´\œ{p Ølš&¤üXĩsŊFŨOgaļ*§eQ˜$ƒ*1dÃhŧƒÁūÖÎHgh€Ö¨îîæč։˜~GŖîOeέY˙āH{~|ŒŦQŋÆĖGŨõQ+:Ģ:5õ_ø¨AhÉ>ŋÛ&Žŋb|/ÆmáHzÛņũy0˜ž#yNģ“jŨčDČIĮļ):kÔ ÆÛá;–˙Ä!MŖsęhšzJ W–7kÔE(×GĖ5û¨á\úĐ`}ĘGŨ:xņDÄķ‘lĀāxčG‹ĩúw[J1g¸íŖ6ūäAŊ׉éĄŲ؊Iđ5oöU\ėB βĖq&Hæį<Ī eŁčK‚ÆÚ=8Ō†ōQˇ×â˛FŊZ­ŒüuîXtĻö+–ƒ#íųŲGũ–ø5ũ4uqĒQˇ¤tܖĖĪDĄúsĸˆ6i(ĩ9Ī2eŌš쟍ÍVd>Ÿá´dÁ$Įˆ^Āåuš—Ŧc áGž6"âäKČš¨7ŠØmų" ĸPĩ¤‹ĸ'~)F]í÷⯑P{é6&šú'Eē?όT5ķ\xfæÁcĄOqĶ#ē×âéĶõQëöM4j+CŠĻ”zŽ“N‡a`ĢĮ |Žëâáĩ8ÍGG<;úŨ|l2ņp(ׇ™ÍîƒÆcZ›9ÛĻÓåJL¯CRŽķ5ŧŖÍāá˜÷ q.ũĩģĪ–ŽĶĪ€-Īåú8ņQ7}üV`*’ĶĨr}ŦUŽ| fũfäōä‹Ãq~Îõņ–8ú¨Û¯ę7įŖV֎žš4jŨ^đQÛ*Á8}ü2ã×[ąV-2—úeC,ŧ´4j|K"Âčđø"a3ŧßĶK‹¤H÷"j Éā틍•įu"?Ŧsę‹Ī`0~‡ŧ¨žcšˆĒÖZ0 Žn6ąJ ‡;@ēšYŖŽ#ĨQį1įú¸_Ŗ¸÷6>ęņQ7ņ÷Ш×āĘ|Ô×Cģ>ޞFWœúMŅjÔÍũ¤Õ¨¯÷Q7õš|ÔSiÔøŪŊZ­(ũV[iA˜âc¤$7Éî'×Ú˛­c069ún™5ũo…8ú1/$v"—×9õÅg0ŋÃvŸ} šŒJRAĪĩUĸ4j•@īVu(Nž‹ŲG}{+/hÔÆGũŽ5ãhCKx˙<Æ ’peÍGŽ™øŽ8§Q‘úÄGŨjÔÚGŊ3>ęTÜ÷îO}ņ Æīlvßą\Gųeu´ÚhZ'Đģ1ãG ĘÄėöQåŖ>š÷žņQ—ŦQ3~ °ĄÕJ9LVĢ•]ŧ†ņ8õQ+^ũ;uų¤>ę—h§žø ãwéöKH/Ę.ĢŖYęQÅÃ[ÔiüÔRíîyiėOŽôžn+?ÕGÍ`0&Á>jĪōQ—‡ŦШŠũKõKô§žø ãwˆ’íw,ũh˙ƒFĒč|_™ ~¯QįyĨöõŊMr6ŖˇWkÔ÷^öQ3ŒŅņŽ>ę—h§žø ãwãŨ—ĄØ^VGĶÔĢ@¯(ođQgÛ-• §Œ"Ī ÷žb+ŲGÍ`0ūãú¨÷ėŖf5ƒņžđÅū;–‘Ø\VGĶMPGŪÁķë}v‹F ė[ËģL¯÷žb+ŲGÍ`0ūėŖž°úâ3ŒßÁ‹ķ/!c‘^VG7Ąčš^™lnđQ—q€}+á=ƒŌûē­|quŠSŧÎ{™ĸí\ĶsĩÉ7›úÕMdœoĮâ/xˆ;į*'2Œë1r>jöQ˙Ļ?õÅg0ŋÃZßB&"ūQ#­ī@ ô~¯Q"Ŋ¯J7ŊŌûē­|>õõP‹i̍€ ã‰k2w’ãyęē.õûÕMĖ|Ŋ^ÛûbNDj Ė`*ļ0ŒÛ0Ąē˛4ęŌR >§?õÅg0ŋÃJ”_BĻąøQ#-SĨQSŊŽ ŊŨŌM]ՃJuQ¤.“%ŦQŖQ÷īŊ}5áy4ę0 LąÎú´Ųđdģ*_9līģZ­ōŧ9YtŽĖĨĖ`0ÎÁhÔÍ=‡}Ô؟úâ3žüOŒ…¨ŋŖŽ~ÔHķM“@¯Ŗ?Ë4UūjĪ;DŅ FŊ;™‡ulc÷”Ū×måķų¨¯Įbą0^Ž:5͞įQ˙‚FŨßw>ŸÛuFđŋwŦ—Á`LāŖ.N}Ô}õāsúS_|ƓOųõZŽV2IĻ^ c3qør˙gījÁ”eĸč7­F#‘j4ŠD"‘J$ŠF"‘J$ŠD"‘÷Ė\GūV]]]Ŋ÷á›īžÃü\fPg3î÷Š\@O€ęĒ>ąĐE™ÛArp‹ƒØdŧNŌ1ƒ]:véė kÎ+0Ŋ7m%ĸžüîU:j<ôüĮqÔk)}VúgĘ$¤žjåg÷ŗ騝KtÔĒî¤ŋÜ#Û%Æ:jÖQŗŊŠ9Ž€ĶĻ)RŪ{ë%íËo%Gí|ˑ#Ģqmĩ€^ĮBûžuȊÔĢiSEg¨˛ÎŌĒąėøč%öūxü^­ÍéõÄwoõō:ęË9ęŗScD=›ˇĖQŗąŨÕXGũD˙Ų“ĪöJ†Güá @u PíēĪˆmhŊ€Ķ+¯ŽÂī9ę(˛kO?Ē$ëXčĸ;węÜŽ‘Ú’ÁÎ ]Gúa}°jß ŊÃĶ9ŪŋžļYGmÛöÜúci‡2Ķ4õ°á#gŽî~ŋ˲Œ|ÖQŗąũÜ~GG-SÖQŗŽšmŪŽG¤}_@kbĒgĒ7|ĖŨ´ĩâ6~ģŧŦ jïäÆ+ßqÔ´€že•QÜąĐAØXVœZUėąÕ„âláG'Žēi+ĮF™*´ĸˆwKüh5âŲnˇxŌļß­õĄ-ßAt ā4üD>m•ņZllŗ{é¨Ķyĩúq"ë¨ūŗ'˙/Yķ}‘?nx\Eãņ‡Ī†įĩ;ŗũƒ+Äâ3~ˆÛRt@ņm,ÉÄŌyĻWėq¤Æ3{ž`ĄĢĒļÄęi`Į–ģfHá2ņe(átžU‚ĩļYxxŽ÷¯§í+é¨o°<ĪtˇÛázÔ ĩpQģŨnŊ^!o(3¨E¯GÍÆv/ģ§Žē¸:›å¨YGÍ:ę[ wÎ&͈čtGCkÛ¨]mxG菠#āÔn'<4%´čē˙ũū_1 æ3e'm”Ÿfíļ‘_Iģ Ä”žžĀĮ%Œ3\ŅÁĪ/äHĢHbfš€^C‹ûvÚačDS$bŸņÆ˛Ë¸ÛW1 ŽTæčņ*wä¨'ž{ŸĨŖæ=ŲØŪØXGũD˙Ų“˙+ę6¯.åœzͨ]ų÷ æ3Pí.ĀļģšĄe/ģŊŠ ¨ílÃ[ĨxΚfåúnŌ=zISYŽÕ7ũ>cëĻ…Ų¯rûŌ˙ä ”ōĩ ¨Z#9€Öų•xđŌ’\÷Ę?1ŪëPĖ~õ<ŌÛ ņžāĨr¤y$uÔļU×Mm t]Įv~´"! žYË5Ģ ĮWĢ|Ôg’—`z˙nÚ.騇{&’ŊGÍÆÆöˇė÷ÖŖ.ĻtÔ]§§¯ģōŸ=ųKöؒôlķ—ÄŸ´~֖ķ=cPy/>RĒëŨƒæ9ěЍ§×Oniķ(Ēw×%+š~ô‚ ;¸ģ Ļ‘ĄÔˇíĖ ÖĐĨõíl$žŊÁpi˜uĪ3ãcĘ ųĢŊ ĨÕ~HŒŽwGŠˇŅu Ų¨F ĨŒdÛß9čâ—ß”ša*îFĀáË8Ōôh5žU'‡Qc FēΝ(ØŅŲ(pJ)üGYyŨHԝG‡§ŗģī‘ļ:G}ūŨÛë¨Ī9ęōá:jæ¨ŲØŪØÎ8ęō>:ęvŧĩ8Õ°Žzā?bB ‚Đ­­äÍ¨‡â¯íw 3”2ĀĄu ÖWĪ=" t’Ã$Eî.XŽH<4Bšé¸Ę´’üg xÔFļ/Iƒ×Ŗ’ĀÛ¤d@aĀīB+˜7=+DMuë&ŨuQÎņØ]—}lü´1ÂĻkĮĮĮęēK‚%,ŠŠFØE~U0W5Sô¸QĒ`X¨&h° ũP‘Zp’qo咑–ŗŖ¯ÕŒĢ ¤M#|œ|Ī`~q6*D<8đ‰¸–$Ÿ4ËĒĀ.eJŖƒØũđ 1ķáP'NÛ:˙œFûÚg37JŊn•ŖĪĢ|܍Ŗžûîũ:j66ļŋe#uyĨŽēœ×Q DupZqÔ¤Ŗ>_ëŖīúĶüGL(ņxÛč û]n ‰ŗíŽ 3ÜÔ^gĀHĚŪ{ „ߎOŦõāE=Ė5Γ`~ Ž›W“ 9hũôÔ˛ĸø%2äŋ‘ŽwĨĘdĩˆ–FɔC­ÚIû\‡âTrA„iŪtp:nęēŠ*ą˛W”5[ŋ^y ŊƒxĀ„õ÷M!Ē/Y!=ôĘ ĖK^APlÛŋO–cđņéŠ>O1ąÖB,]ˆY%Šė@æņ€”FyW}÷Ũoq–ē@•É֒ū@kpbų3 D܎Æ)ÅķĢYŖ˜Č>îƒR.F\ȑ#û…‰å–SĮN;I¸×ËDŪĄNíÂq+°­Æ˛Ę#¯ōqoŽzęģw¨Ŗîķ_™Ŗf~›íÅ­ã¨Õ÷LyĨŽzŽŖ:ę­Ō)Š DÍ:ęŪ1Ąxlz,\Éĩ¤UWŅK$K xW~‡pÖˇj˜ž¤ xoŧC\U $F&Ā*Ą-ä;fÃōĩ¤=‰‰E-ķxjՍô ĨZk¸qŨcūV㟑‰q@ä8I7>ĢžšÎŦę!+KrĶ—A;n:û‚CÁHun$-āt%Sü?ÍbË7ũJąÄÔT<ƒQ,Į9.Î. /ôڂ3KŋV&sŲ­w§Ž āW=¸.”Äāė¤ĘEi›Uz8žu:ôģîo¤…_˜îg­ÛŠĩ^§˜Uj„"H´ĖīÄMfŽ›lüšÎâÔm/›­Ø‚\ĖÅQlA~GEnŗØKŽžX€:qÂĀÖËÄĮ ‰všë Û˛jĪJ;Čũī_O[ÖQŗąąũĸ=PGŊ5đˆZI¤Đ øÍ:ęŪĄŪ°{ėG?O€¤åbÅŧîĀɀiÄÔˇī)J ØĢtšJĨLZč93éáÖFōáDB"øšōHļ×=‰ōQqi`ÄÁFų°MÜēÛ¨SĄœøįs†œÔÅ €€ë\„8Mũû%ĩÄ@Ē@tīÅâ0Ũ49v\Į ąĶĩLÉomģŪíĶ(äŦétƒ3ĀĨÔĻ~O^x]J…N—Š1ēø'"Ė ­äXáęĐn TQīJ ĩčĩe×_˛7õ–æĮ^ÖߡĻîL\šø9g*îĸRû{A]Vë ¤*H`;tŽkŋQĒĄp5Ũqč\ȑÆqØdŽÜŌEq¸—‰<Ģ*ėæčÔĄ]Ĩvä[¯ĀîžGÚž—Žš9j6ļˇę¨7[ ę$+ĶŦJ˛*Í+<ß,Įeĩō1ĄŠÍ‹%Øp‰$!˛bFęžĢ,z†viDyęŧ™Ÿ†Õ˛)ú+<ĘÛŊŽØI–ØK:HŋMš­ËkęK\­d*äNē$Fų˛ņÉ6ū§7‹Ēū&æK|ƒqØæ‚ødÁü[`sU”'vēgĒĀls'0aŨPxx lŦWÂÕNzZgc:žēÁŒ`ŪIžîJš–:ORßŨōƒ| ŲmßûJę@) !É 2é?í+/jĮCP]â=%IJĮŗdëU4ūĀá—ãj4+æËoŽĄ}9Sχ*ļëĖÉ#+Š&wZ ŌpŸ§V™Y‰k ūėî{¤í§ę¨ŗ,Ûnˇ‡Ãá؁\gr%~¯Õâ_^y{ŌÖ뮊'ցŖÕđŲū‚=PGŊˆ:MË$í@5BĮQŗŽē|”ŽzĀãÅRšĐ¨šÎĀ§âœpˆĢÜĢ?ĩΰ‚€¸Jų@të>OŦęö-ôĢ~ö=ߨXĮ(aøé7‘ j Ąo|Æâ"+@P @”tÅBÖ ×ŖˇF+=^Øûå)Ŋk ă#–áUSĀiÛŽĪŲéZĨ– Š›č¨ˇ†đŧėtŊÄN¯Gšs–ÍņXûžXGÂvš0ĒKÁå#˜tĀčūbŠ8ņ–qZ EģsžeČŋOqÕIJëf4ž/˜jņ–‚‘­…ĮĨ¸n˛ōę‘LüĘĩ(Ú¯žî­|.cĸÄįÅ+z`|)S„û 4ÃhžŲNŖÄÚĨ3 YAũŽzęģ÷Ŋuԛõú/‚IĮqJI4Šøv‡œ3Â1Åj—I6ļßąę¨7ÁQ'y’âČJÁQÛ/¤ŖÎŗĖ4Í˙ū!…¯— ƒņ}}!Åe"įßš!'ĪsÃ0P)üɜå1Ąß2xĨÄuAÖ 9Āļ5yŽ.ųDēŽWZ5zÛĢX¸ņŽU‘WĩäW—Û<ų(žĻu’Ôi&ük˜ÉZÆm„ũúÕūD:ŠÍžŨ¯›&ËY%#j7H]¯‚::6¸Š\¨–bBÁB‡Ņ€>ųqD]īvu–ęVrEAķØąîx™íÛoDá(ęHZZk‚ÖC&? ¯Ûųâe\ Hhí´üq›5 >­­!^ĒZŖ<:_\;pĩí:Nĸî^ŧ*" ŧĘ9Qĩ÷ŠƒWnŧzã7†WY^ᅅ_~ãø)­&ũ ,§Ëiû‘:j@ĘõJØZūk"ĢqŊ–eŖZÖAám|Īėw;<æ’8VėŽÎ +_UGkĒ:Îâų¸ŨnļÛ­žŨšįēh™8‹.đ@‰ĩ$æÂuŨqüĒwÚ>Rī=îÅîkĶ0˛,ÔÅ?Ņ KeØ~͍ŖŪl¤Ō¤H’‚˜ęGũ:j|?¸ōĨ¯Æđõ2øØâz‹ĸˆÂĪJ"ęA;ĸē+D,HáWe—SǜībxĄž˜ē ŌJ˛ģažÄŲ.0މdY1ã9é´kɈF–ļƒųšÂėā,ņ˜ãXä,ü-ŖJôâŋ<3Eœa¤îŸ:‘÷›b§Į>žĸ(ˇËËžĪØr˙ėe“ōƒãq%~–ŧûĘéõÔwī{ë¨ÚTۋ{ž|ÛJ ęûeâŲ†bŅû ôZĒ)åãĄFÕi3čŦīûppĘėa3rĐ‘f’U]2øĒexb*įzC1ĄHŠM ž`<đˆišƒē­TØmįYļ?mÔQođ™J’FÃķ Į œČu׉/>FYšĶl‹_UGqéúYûž—ąũ ė+§—¤íÛé¨ TĪĨĘtļY=h¤ŗcwíVeWŒSëÕG­—TœķdIÃØøgÄ / >ŊŅčĻÚÄ?eļ}đēi üLJŒ ŗą=ČŦŖnR)ųHŗˆúÕtÔ___Ā˚?,|\*|ų§¤ūšÛí˛,­zŒMF ö0įģ1Ąˇđ{ä÷$0°*Š<öü  ā 9A Ā0ÎÄĀHÄØ82č %č•ĩcņüŠãÆę÷žąĐŠŨ…/°P!pN)R ŲH 2ōŅ;b \ŠMøČAžcŸÆĶ–ķâiW8učâhŽn}tšØ9K#ģ QŪ>kG͑>_ĸe[ļėtųb{§ÄŸ@Ėfķ‚ËGęÕŠ+S9"?§4¨čĀ(aC1tTKĖ) 'ŽHe­H.(y-Ž?“ļŸĒŖžc›ĨĸKĮ–9jÔSuj)9!~ļ,K˙Iyķz œëŊ;"Ĩ4āŊ i=¨ģ ۃė‘:jšÖG§ĶŦz5õË TĪsņUųEž;ŽM9„“¯(SNŠå,Įđˆ =™Wú'–Øęyfs>ųįeląŅ›ØĀ"ëV [:ģ>Úu`מ-6ev%lķl@ŠīĢ'˛ú.ĩdŋ¨ÚuŦՅ“J`†.=ōŅĩ Įębpdã2"6yuˆÖ“ž#Ô][˛_[ö+ĶŧKĢSØv=đ3ą.qČÖÎb›HĢéyŅĶ0šh ãhŊëņd§s9â‰Ŧ*”c‹qÃuaHSģĘúō™äĀĒ|eõņŦėQ<ļũ*:–ūe¯ëԎĨŽKöE.į/ŨLYzËDû˘3-æĶ\¸ƒ9šŸQ™lXĻ@™ĖCæ¨˙LÚ~¤ŽēÕĐŠ/'Š]Cį¨uÔ:Å=>۞qÔCt+4Éš‘æųÄŸqÔŖøÕYŖ6M“¤§zI™Ŗ0sÔlŋi_ēJåŌyI^Šõ¨“×ĐQãŗĻDđ'ËëųČ!FZŠF„ęß$9ÉHr)#ų.† hÍ~˜ĶZ‡ÖļŽNqxVë[íŅnģÍqäũA˙LåÛmėˆb8āN[šmíļ•#Ķyŋų•;q”äœĸÍĻršÚi„/SQËéÎĸzŪĮŸ ĩÅ?eØĩÔ%Z CéV…S”v\üjįU&Ō°Ü‹Cœ˛ÜÎ3ģ˜R 1ˇJ&síۊÜÎ ĩŽÕáXâÂN3'Īŧ"ņ{ ŽŖ×j)Ž*ōĘČKN~´ķČ*"Ģ †¯ņ˛Đ„v RˇMŨ&uÛÔk+úJ +*öAŊ ^…đöČÉ[ŪÅVË*• QĢĘē[TíÃj‡4.¨•FâŠđ†‚ƧIēmâIGl¨]ÄVz<äĮCīŗä&‡,}Q­Jö‚’ÕŅiŽNŲ8ŠđG‡$ÚãˆÃ]îĶ`_‡Üˇ*OâåĨcŗŊ6đĢĀ-ŽNšXä°8āŠ‚r•û8ßgŠ•Įvu´Åōt"BUą‡ëXL  VPėŨĘtKĶ/waŠ‡$““…Āp!ąŧŽXŒ|W+ŗ“ÜB_˜_ŋ2EwÅ>ɍ ĩ‚đ>ŸSÎų…œ–uÔCëyiĒņRRd×uh¸|ß×!ņņx~ÆYՂ’aKZɡ¯|]G­Jļûũ~2æouÔ'RüV>ĩiÍ=Ur§IJXGÍöË6ĐQ—Wę¨ËīÖŖˆZlī"RÚ3ą8፟hxYÆg_ü´ĐqāNŅ/ņ…€áAqÔøĀšÔ~Ø8Îacccc{5ĢF:j˛7ᨧØf\¯mÛ@žx~áO™b­ũ~ŗŲ GąģxŌmĨa|ôÆÕ'9ęV`ĩևĘžG`ú’ ēŠĩ>&ã×ÖúØ+ĒŖÃØÆŌ![LĸaÎk}°ũ˛8ę;ę¨%G]ÔŨFäúž‰¯ĄŖĻÅîVĢĶbw:{ŒO(-Ŧ'xėę,ã@Ė6ü¯¯/Z|eō\ËÉķg]ûėŗĪ>û >Ņ˜É›ę¨oŗļSšĘĘķß'ŽM­G}ãõ¨Ų~ߨŖŪnš-‚Õ¯ÆQŗąąąą} )õ[rÔˇ™ūëŋģ[]ׇÃaųĨ@í™xã=Ų~ß­ŖÎz8GíŧŽš}öŲgŸũôß[G}›=ŽŖŪívôˇŨĩĪÆö"öpuöē:j6666ļ´÷ÖQŗąą=ÅŽŖÎ&Öúî™Č)§œrĘ)§ŋ•Në¨?›ŖfccûĄé¨åq˙õ¨åäj=ę)ŽZg ØgŸ}öŲg˙~ŋgbŌsÔ]>sÔlll7[ĪQwß'ŠŖžXG]Îę¨7ã=gÖŖæ”SN9å”ĶßJ§÷LdŽší6ā¨Ë{ę¨7@ÔI’¨ĸÖ8ę¤į¨_ˆĩ`Ÿ}öŲg˙üŽZ1ÕīÁQëë9˝ÃWHđDvGßͰ”{(lÖëíč”nj Ž,Ë cģßīoXDũė‘—ā`{c;×Q‹ũX¯ĶQ— :jÉQ'E’tĒnXGÍ)§œrĘésS]GMBDzŊG=šgb+¯—ļ>iåķ×čw`Á“OôšÕ›‘OÃE,Y{ĶĒ *’Rî€vmu6ļ?a×QOsÔŦŖfŸ}öŲg˙9ū@G]‰Ēëō]tÔj÷Ã1ôÕöõvôíÚįLme(ŠîãMgi•i´iŠâ<Øív´ģ"m,Ž×my+Cļ÷ĩꨉŖÎēŸ%&YÅ:jN9å”SNŸžtÔeŸ˙Ū5Z°,‹|< įdēĄ<ĄbŊ5Õ>°q†pĒÍYÛĻ?C?˜†1Ž Ē0ØØŪÉŠŖ–uÖÁé$—k}8ŦŖfŸ}öŲg˙™ū™Ž¤$ĄįŅ‹sÔĒįRes:jWKĀöœIĨG­Z´OŧY+u#đĮÕĮ ŧ•Ėöda6ļŋnÔQËõ¨ûí]x=jN9å”SN_!ŦG]ãéT×ÕģsÔē]ČQë-ŒáņäYŒ§mۆP~næ"šĪŗąũ-{ Žˆē•H[Āiq°Žš}öŲgŸũ§ûĩÚ9ņÅ9ę mAG­  7Šĸo›RŋFÔ[Síoûŗ:Gmšf†×uļ\ĩÉ5ÛģÚ#uԒŖŌôĩ@ԏÖQgiz8ėņ  į#LšÚĪų–u(ō|\×÷}|ĀMÃĀũ<’T„!~ŦĄįãícŋÛm6cmYVžįãēAˆ0ä×Ņ}Fcŋ<Öähr4ŒģŽÆ+İ™ž‘ …ą<#ŅŊfäĪ۟Bú(k!NTŋ×ũų 1\ûA`âvB”kÄy¸°¯ģߟy–î>×ߟGĮ ë¨?g­ŨpáøVÁ“ŧ]\ëCč¨û~'uÔôķF¤ôÆV‚đRūŌ_\ĒänˇC&ųŦŖf{W{ Žēį¨ļãŧ×Q?l=j|HņAÆįšžoU>žiP ayøfÔÅõâ›ßÉrąMã‡ņdYНРđéAų#ŠBô‚ü  ãŦ.S<ž‹__€p?‰A†1._BŽÆnPa¨Ņ0Mã'1”ōŊŨ" ˙b3rĘxN.ÎfäŦn7#Ŗ›‘ž?ņ@ÄAÂāü$†Rģ?_!†k?#Ãs]į)Œ'ܟ{\ÆsîOš”~4œŸÄPj÷įŖcŦG]IÕGųō:ę m’Ŋ‰'ãœ[Z­õŅNé¨ÕZm­āŧÜāË _Ą˜MzyÁ ›†šky­ļ÷ĩę¨ ÁQ'’ŖÆg€ˇ(üĩm[žį}S˛(đŲäã3Žø\Ö#øņ†åŅ÷į+Ä0w" Õ{Ųß!7§¯2#ˇŪŸb|Ôh”;Å0ĐQĢü÷ā¨īkj=ęģXÉëQŗŊ¯=ZGKK8Ŗ¨j ęÛtÔv˜­ė^;•Áģp–eËí pūíōM͐PVWĶÍă/mĮŽVĢjŊÂWyÎō‘m6-Œév’ŗ0ē|ää=;4`ĢF~Qįv“¯šlUvöŦŒ6ŗíhaœō Ã(Naäķ1ˆĶmˇ__íjUã+w*æíf“‹0CQvfÅ_+Ü) ÅÕ1”§9Ë×f¤(ĻæúėūtĶ•™ŦŒØöžyfžZI=÷ž÷įápđ}ÁŖ^ˆ^ꚉiƗyÅM÷įc(Ŋcj‰áĮ^œMŽÛ͟‘Ãa Ɉ,Ä ¸ß<ŽS?ɂ,?N–šųūÆ\ ŽyîgŽX’Ūã27ߟCėÄwŖ1{-y“gm†#¯ķÉ2ß۟?ū7ĐQĢü÷ā¨īkrĪD÷^­ņž‰lolÖQ“äĮ_“u# _¯ŖŠūg%ępĸl\f ”쨯nˇsžąÔˆ“w-E8ũõ¯ūú×Ĩøį¨ŒäO–ø"t´aų$u ŋá.Ģ"§Ûü_“ũŖT‚ęņhœøĢéŅ(gF#žt4œū÷OøįôhĖŗg?pú_üī_ōO¤ņ?üķÚhFDĮé‘’[‹38ũo›üÛÆt8ŪsîO§ˇ[:c›Mq}7ߟŧéČŏ֯vģĐPô)ˆx‹NÆŌũy‡âlëŖīØđ“­Õׯ@S29#…cģ–Ģ’‘gvFœö’ĖåP}Įûķl4 cĄ…¸‡Ķ8ŧ4§xõ›īO„4~ÉhMAp:mSĒ›üÚûķį1¨t°ĩĘgŽšíf{¤ŽZŦG­ā4ŖžNGŊ˛¤c§"Å?ĮezĒgē<÷ûũ°k\‚īa|'{ōīžsņvZÂéîĪ a™ ÂÄæôĩä" !œla˜&ÂØĐŖįŽE˛Ķ˙萠úk\f™qĘOŖ1Ņ~<ékė´†¨ÛÕj\ĻGÅđ–Ļc•\CŲĪHØÍȰL?#[ß÷—îOķ§qŦŒx\æîĪØ0ĸxvJû}ķũ)QĪ#FTŽÆt 031ˇZF<Ąũ^¸?īCN¨FjøņU1,ߟ‡ũŊKŊwŪT§Į3N%–N=J­Įenž?1žŧŲû“€´JũŠûđæûSÂ_ŧ?u8MéU÷į]bPū@G­ō™ŖfccģŲŊ5)¨;Žēę8ꁎšlÁ—ˆútĸ”ąŦũžeÜÂŪíĖDÛkŽ/b?æĘÔëÕGŊú—ą„8͟l?•{ŗ&} ×K¯*seĄuŽēÍWã2ōĪ Á8ŋėFcGŖą<ū:4.=‰¨õ24‹ú„¨“ĢøŠĒ~F”Fz.†RÎČŌũi(8- 5ö¸Ė/ܟ€Đ=1,žˇÔËÜ|yXöŦéBœ€ĐZ[ėq™šûķn1ø;M ûōĘīîĪ3õ| °$ ˆVLõ¸ĖÍ÷§âØË˛\ŽGąĶäY6.sķũš‘ŖQŽl܎Ōę¸üūŧW Ęč¨U>sÔlll7qÔúwÎU:ęō{ĩ€Ķ?ÔQ Շd§‰Šļ‚t\mn6úI¸ž܏§Ō‚6<ŗP Õ¯Ĩ0mqÔļ5.ƒF´0NųŅYíãQ"Æŗ éĻą0&un)8-Tš5.3._ cē}ëpˆĮķҘˆ˙5–5P}ŒËāNŲnF įß1+ĩtŽÚΖbhĄÆūėÆļßÍHŠfd:†R¨>2ŖļÜįܟŠëęu6Ĩ­ŊųūÜī÷ŽãĐr ˌ¨›š:GíĻĢa¨ŅxP Ū1Ķ9j÷˜.Äpígä´˛DQhTųDĖi~Ô9ę,īxî÷;×qH'ļĖØĮyŽsÔĮķÕ<č7ߟSŖ1=nyëuŪäã2ęū|P Ęg5ÛŨíņ:ę‰ĩ>”ŽēëTûž›ôņ§~™hú#æœJZĪVĪ'Ąžvߜ=ƒ‡Ģ0˙×k &zGXb6lK0Õ̝Jûe⠌CŽžĒįbXõŒŽ‚jxˆ˜Ļša˜DūĪIQļdĒW€ĶUĪÚ Ę'’āĸĨAUūzj4*9Ĩ Ķa†ĸĘ'ÛGŋT¯V89“1ãVŅūēs ÷ĻÖ*Y­âātQ.Å0Đ͎g„ōÍ^‡ŠĪnöųäũée+C°ĶįpúWīOP´`Ē #ķŧû۟y.T܃Տ'ÛĮ,x™gƙ˜=œžŊ?ƒRž`§{8}ˇĪČxõãšöK ljŠ&8}ĮûSČû­ĶzÔ 1”Ŋ”:Âé;ܟskAÛĮ?Ô/‹œžž?ƒō'uÔ%sÔlll?0]GMö€õ¨ZņC¨>h=ę¤×QëüĀ/úxŒĘ?qūvŋatŒĘķbčG#āī§fäŠ÷ߟ/CÉ÷įš˙ ÷įc¨&9ęâMtÔjŊheiŋ™^;Į֗ž.å_HéÕlnUęe[^õzš¤Ę9öã2ōjE)…ˇŧŽŸ^—ÖžžĐäz&^+wŸ1Œ­|ÃŊôĸÆŊķz&kguqīõ¨ņA.%Ō–L5Š䞉×ę¨ŲgŸ}öŲg˙^ū{ë¨ĮĀÕļŦDYđiī*ĘĮ8lˇ[䴋;'.ÛÂΌߖ\Μä8w‰d`jÍmĩëúå¯ ãŪK^sûSíŅ:j™)Ná˜Ũ3ņŲĖûėŗĪ>ûŸãw:ę¤į¨ûü7á¨ŋCƒ ûŠmÄĮFš(’Ŋé`Ūs]ĀN…Ã[ >Åöˆ2“$­ÄįD}+ÂvŖĻ˜;FZ$ĢÉō*ržaY–-ÔEījcĮIęXí IuU;tVí iiûBNôŽÕmy_ČOĩŽŖîŋOĒōJu9¯Ŗ6L8™ÄØ8€¨ĢĻĩ$G}­Žš}öŲgŸ}öīåŋˇŽz™_ĨõŪÉž“y8ŽCOj<íĒ)Ķ÷} p"ÄÔîä–Čo%û „I™8Ka“ ĸ]d¤ĩ-Îg9jÄŖØuũr|Ą." ÃRŗnh“Pņdûė?Ķ˙45`ž%Õ*gŖÆŲļMË.)Đ8‰Ā'™a Rĩ6‹B§?á¨]×U/m§UļÕ‡—ëęī—ĮŋÜ&zĮā,ô>×Û{ÛŖuÔiy:N5ë¨ŲgŸ}öŲ’˙:j!öØīķüėę Ŗ(šlX:ŠBzõĐ8jãrŽzĖ'ĢŖžkŒ–FėÃ3Ã0¤`–ëǎ†EŽz¨ôVmnûŗ:G}ÖûģÎõgÚÃuÔ'ŽēU5ë¨ŲgŸ}öŲ–˙i:jã¨YGÍ>ûėŗĪū“ü?ĒŖžĐZåSÎųjd´?äö|=j<⁠šĻNŠ|wj­՚îÛļMĢabo—9ęžé-ÅĻN&­&=.ųĸôĐæF6.ÂÛnéea˛ŽļÖĮ~y­ÉxÔZm­ŅûfĒwàŪy­Ī´_ŌQWg5ë¨ŲgŸ}öŲ–˙Ū:jļkM­G}+y=ęOĩ_ĐQgŦŖfŸ}öŲg˙eü÷ÖQŗ]kRÜ2!šÍxĪďĩ_ŌQŸ8j¯d5ûėŗĪ>ûĪķß[GÍÆÆöc5ûėŗĪ>ûåŋˇŽší)Æ:jöŲgŸ}ö?Ęg5ÛŨuÔėŗĪ>ûė”Ī:j66ļģë¨ŲgŸ}öŲ˙(ŸuÔlllw7ÖQŗĪ>ûėŗ˙Qū{ë¨õ}ˇÉđÜŪī÷ëõzŗŲØļ­ī{ˆëļ,‹NéëQ_ns›ž_RRåûq™ĶÚë…ˇŧƝ^—Ö…žĐh#›VîĖb[9V—^Ô¸w^ëãcuÔėŗĪ>ûė”˙Ū:ę1pŠ&" æû>m’Ōʧ°Ú̧đ\ˇû=/ˇņNˆ——\ΜäP‘˙0’Šõ¨ÕŽä—ŋ&Œ{/y=ęO5ÖQŗĪ>ûėŗ˙Qū{ë¨×ߥÁņŨcÃS~ˇÛĄ¤iš:˜÷ĻöL¤}‘I’ÎVâsĸžaģ´gâJÛÛąßÕq\^EŽÂ3 #˲…ēڞ‰‡å=Õ.“ԝU{&Zڞ‰Ŋku[Ū3ņSuÔėŗĪ>ûė”˙Ū:ęe~5 CEDĪÉ<ĮĄ'5žöՔéû>8bjįnKäÃŽ¤Lœ%ȍ°IPŅ.2ŌÚößŗ5âJ„‡Ô4…ēˆ—LŽbÖ m*žŒG¯Ž.đœŋė=6‰ŪŅ  •ísŒuÔėŗĪ>ûė”˙GuÔĒįResāĩ¤P cĢÔČĘ$ŧDŨ1(ŸÉQXãŦ`ļÎ댮ķ´ŽzĩÚlÖsúä1 DBĩüquŠô¨ĩT<Ē:ŌÉę“ŊĪõÅöŪÆ:jöŲgŸ}ö?Ę˙4ĩ2ĀB]ŧĀQcˆlÛ6Mc-2'ø$3 DJ^֞āŸ9ßrÔŽë*&ší´Ę6°:đđr]ũũâōø—ÛDīœ…Ūįúb{oc5ûėŗĪ>ûåŗŽšĀBėÉ2ĀŌQŌ̇ÆQ—sÔc>ų[õ\kd–uĀ$õá™aR0ËuÕ[Ã"G=TzĢ6ˇũYŖ>ë}Š]gŽú3uÔėŗĪ>ûė”˙i:jąÖ‡ŦX(tÔŊď~?<Ī[ á8'‰õBËĘ)Žë¨Õr“,.j‘ŽēČķɒƒķnˇ+{Ø<(/$¸' ŒbđŌr]õëK¤ŗ:ę~l'uÔãęßöÎ:ęĪ4ÖQŗĪ>ûėŗ˙QūÕQ_h­rÛ¯GŊ‘‹NģŽŖŗĮyžĶZÛķõ¨ņˆGaCŽéS*ߝZëCĩĻû€ņ´!öv™Ŗî™^˃iš´šô¸<æ‹$+hs#ám;eød]m­ũōZ“ņ¨ĩ>ÚZĸ÷ÍTī†AŊķZŸiŦŖfŸ}öŲg˙Ŗü÷ÖQŗ]kj=ęģXÉëQĒąŽš}öŲgŸũōß[GÍv­Iq˄ ä6ã=?ÖXGÍ>ûėŗĪūGųī­Ŗfcc{ŠąŽš}öŲgŸũōß[GÍÆÆöc5ûėŗĪ>ûåŗŽšíîÆ:jöŲgŸ}ö?Ęg5ÛŨuÔėŗĪ>ûė”Ī:j66ļģë¨ŲgŸ}öŲ˙(˙ŊuÔúžÛdxnöû°ĩãØúž‡¸n˲ÖrŠj}=ęËmaĶķqI}'q˛( U ´˜ķīm^ķm§—_Ũ寋ŧĢąŽš}öŲgŸũōß[G=ągĸe%’Č‚<Õv~ĩW Îâšn÷{&^nãJŽÛßív—ˇpGģp ęGĆ~ogļ×7ÖQŗĪ>ûėŗ˙Qū{ë¨×ßŅĒãēĮ†§ûėŗĪūGųī­Ŗ^*Đ’ô8'ķp‡žÔxÚ—R&Đ/8b ‘7"ŋ•ėˇa”‰ŗš6‰+¨dyžŸ đ6šR_EŽĀH‚T•GfžįƒL4NIpîõ%7ã’ēî*ũ‰Ū{āûÄÕĢ Ņ9ęÉžĐ>Áû0TüǤ~ųôęA>zˇûŋ°ŊąŽš}öŲgŸũō˙¨Žš@õ\ĒlŦŖV†ĢŪī÷c0šl (Æ×ŧ‰Š$zTč”Ίn%ļ'ņƒÎ÷€îU€ũ8)ØØnģ’˛ú‚&|ŗŲ4M=ˆYˇ1ȟëKĪ\k—¯2ˇ}I´¯øyœE sáąũQc5ûėŗĪ>û嚎š Ķ’‚j•ŗĀQ“Ö×4ĩ4ƜDāzĻVrĩ–š Y{ĨŸU´­ã8…d’ĮđQĄ zßívÄ6SHžīlī÷Č<ŊP/”j%=Yr¯ĒķäĢÁ ä¸/Ŋ€^k\r0/OQŗ=ÔXGÍ>ûėŗĪūGų¨ŖbŧfŽĸh˛ Ų( éÕC㨍Ë9ęņręŦišĪ ĖŽl(Žw2s|vPԘ* 9ę“ēģë]ģå8ęqSęJ‘Ēø'Kęíc™Ŗ~?cõĩūŋsĶËāËĘu]Ę|zœėŗĪ>ûėOúŸĻŖÆ3Wi-tÃŖ؏dĀĀxpœ~ ŠívƒĄ%æ›Ē먕>y’īUBâ"ĪĮ%ŅĀšúuá˜ÅEēŪ,Ë cĢ2I¯‚G­‚Ŧčˆ^ čųģPR7ĄŖîĮs™ŖŪív‡åžčJõŸXN–ÔÛgõ[ë¨oöņyĄĪšĘ˙úúÂKˇ@ÔĪŽ}öŲgŸũ9˙ę¨/4}… bMģœķ•+Čųļ*ˇįëQãŋŲl šĻNŠ|wj­՚îÛļfuäŦ‹:Nn§Xbú9$­õĄ^%€;ú< %Į<Ž;ę̝Mï€ŨέõqĻŖî|ÜhŠčôÉžÄZ{ąÖDůJšg/§öy­ˇ´_ŌQ—o¨ŖĻ|Æų@ÔO}öŲgŸũ9˙ŊuÔl—Ø…ëQ_nRõq‘4š×Ŗ~W{¸Žš2eŠboŖŖĻÕu&ËHÕĮĢÄÉ>ûėŗĪūĀo5Û%&-îĪÛQ• .Üķ‘÷L|W{ Žˆē‘ėtĒGũ§uÔd‡Ã! ÃÉ2ŦŖfŸ}öŲe˙ŊuÔlŋi@CģP˜ėv;}ķļ´Gꨡ‚Ŗ&5ˆLOõ×QgYēŲlæĘ\ŽŖV›UšqįpįüÅ/Ũāčˆ#,=íÁOã?)žVŠ`'ŋ{ß[GÍÆÆö{ ŽZrÔKįUZ ­Ģēđûīë¨mÛō}ŽĖå:ęgO>Û;XÕ´EŨĻ•8~įoÉu+ú ķÖMÛũąŨ„í:hŋpøé6%ŸbsßŊŦŖfccģģ=PGŊŲQ'i™fU’U@ÔeõŸŊĢSˆĸ/nÜh5‰VĸŅh5V"‘H5‰V"ŅH5ķÎĖ•ŲYA—Ũ•ôœĪoŪ}Ãü!O9œwŧs^Ž7c÷Qįy>™LŽy~ĢM{uߟ ˆą,Z¨ėŋPŗV”īŅĨÜ6ä6x ĀÛŖL͒OķÚr)?ÖGą°ÅN­RĩÜ´D˙ˆđ[ÜúîĨš ˆ‡ŖKĩgū[­8T¤ÚjÔOāŖ~HÜ÷Å'bŦ_}¯tā ¨ŦáąÁîRZ{zy•|Mqާ›Ä•ōl^‹ŊZ§úĐū¨™vy֍ËŨQyU_, 4ŊŌB÷ēŋÆŗáđh‰ĀÆ÷FąAMûSģõŨK5AG‡>jŗ'Qšæ‡Ã1Å++´FŊÚ<úQqߟ ˆQŦR4áüdˆëY—ōÂ7đÔpWĐėŦEn°“aŧQĻļ-noĒ,ŨĒßÂĸ1&ęwšîu5ī—q’k^WļßĖøxMcũ¤xË¤˙ĖBc/´ÁbĀËĪī ¨~`’%ßúî}nu}įAܡįķųd2Á}yĩZšû書r)‡Ü|ÔíqkĶķÆ–õėĐIÛZæĘxdķš/'mvíÁ\Ί}ÔSŖQ§Į4=õŠĢžŅ…|‚TƒvĸÁ/5į!” ä{c\ÁË=#õĒ>ęûF…8Ž­@ŠÛô-›ĮzŊ–;ĩėč-•`ŋ`ā"ˆYF ۈzāÕžīK%Ž 寞Å\!-ņ&cd; ø6†˛ ߎ JÛ•yž_Ubp™ČķmÕrZoét×úOÜŲŖ0­ŪžˆĢQ7΅ņ…ŪĮQd×o[ē§/cöÕrŲøÎãÅŖ|ÔiƒZr}:­]'ú¨éŖ&â÷đŒøœ­Ôā“1W§…Ļ:Mt~q_w§<$VOįŖR}Ģ´¨û¨Ģz­ļúžgŨ-- –(ĸoģŪĨÄŌīŗe§rTX4ŧųÂí]•¸šČuŋre€Ø×+EģģôōŧKKĶũŽ'Üüzyĩf÷}Ģ“ü[sš•įômĨWĩÄøVŸĮQŦáÖōˆ‘ĸķ|ÔŲé`RįĨųéiōQ?*îûâ1>ā öâ>÷¯šTĪåŖn;5ˆnÜÖG}GŖ¯īlæO ¤˛‘ģ•NËw!đĻ÷ģ{ÔĘļëõúh”ä:}ÅĒĐŗA jŗ,) Cíų• ŗĖ"ĨĶrkZÎm÷/×ÜøhpÕ˛>—ÛĀíUoyu]zqĸÛ|ÔJ Ö›ŧjú¨ ‚čŲIģ…ũd@jđ0cEõ­—gÁnl6›$ąAãCœkz•BrŠžËGŨÍš>ĖÔ`}ÚG]9xqë÷08‚õ‡ÅzŠ7DRĖYîú¨­?šQīĩFâcž×[bsûëÂēŠ‹.˛ā,Ë|ßŗ•b~ÎķÜRVL$¨´ÖîÆ–.´ēē÷5ę ėÍúÖ\rĻîO,[ēãĶGũ”čÂG}ĩg4ęŠNŖDŗåzK5}ÔAüāŌ˙$-Ū`ÔāaÆęé|Ô-áf¨ÕTōQOMŌéÍfíĒĮ |‹ÅŦŌûœˇx4öMN˛õ›Ļ\v47Į°.svM–'Ģ&•X~)š>ėcxG•ÁÃˇŋ+ÄšÔ'ĒēOfž_Ī€­nåúøäŖžÄøwĄDNoœKįú˜ë\xCėúmËŲ§‡ņ™ëã)ŅšZ[>Ž5jú¨ ú¨ ‚ø$å˛|ĶöŽšT¯ęŖ&Ú e>ęö0ŽVÖhæŖ~VtëŖ>ĢTû¨OôQĶGMÄīQĒKŧ^TßqÅęU}ÔDCËæ÷ãXƒJE-÷|äž‰ĪŠŽ}ÔUŽ“›ëƒ>jAߟ ˆ‘!;i:ėúW€‡_ĒWõQ °Ą Đ“ Ž4sž6:ôQ7æŖ^3õGÜ÷Å'bdˆĖn‰ë´xøązU5AŊ KõÍ=éŖ.čŖ&âûhš["˒>j‚ ū]ú¨§ZŖŪįiĒI5õĮž‰ôQ›?úžøAŒ Ķīė–øâąĸš ˆ?D‡>ęŠŅ¨A§SPhmü°5}Ԃž/>AcÂŲė–øęß'öŽŋTôQņ‡čŌGm4jM§k5}Ô}ÔA|˛[âŒģ%ļ‹ÕČ}Ôúöē\Nk™ĸŨ\ĶS}heÕwK´9qžQšųĸë5AüįŖ6?Kwû‚_ÕņtŸúB§ų‰>jú¨ ‚ø16­QGŲ āáĮjĖ>ęÍfEQãĄ:mļŦØŨ•īRķšņĘMÄ/Ņy>jŊgĸŪŪ…ų¨ëqߟ ˆ1!ØéŸ%P€‡_Ēáų¨ÛÃķ<ëå¸Â•ĒŒûérš”øKÂ\W¤ŠQÄŖĐĄÚķ¤fōTīđRŌGM5A?Æ4Öõą„<üX ÉG=1Ögë–JųĄĸ[:íoŨšzŨÆGmûÖGûÁéQG‡>jßhÔ`ÚúHuy*Õbü>jŧK‹ÅÂ|ŸŊ‡áÖm“įųfŗų÷ī}ÔA<gŗ˙ø{Ôŋö;–RŲGŨ^Ŗūt¨Î¨ŠQÄ_ĄCĩį‹F×žŌ¨ã÷Q¯W+Đfį,Ë@­Ũ6oooķųŒš>j‚ ‹Üė?îÅCQ€‡Ģ1û¨WĢU’$‡î¨ĘŗŲĖŨ1jî÷ĨFMÂ#}ÔŠv}ÔŠŅ¨5ŠÎNhĻí‘û¨=Ī™žĶÆ0ęVcö}ņ ‚ ŌB3ęÅžíw,ĨŗZî5¸Ķǝr}¸@ŗų|.7ĐiÄrˇĩ FMŨĄkõžĸĶxO%õØ}Ô ĖËåōíím:âĒˇŅŒš>j‚ Š(׌zE~Ŧ†äŖūō<_,`ŧžwúN/Üĸƒ ˜LŪQÖ7”ĄFMŨáá>ękZčt~Úez‡-hÜG .†[|Ŗô<¯Ū†>j‚ ŽÍA3jI7xøĨžš{&ÄŖkĩĨĶxizšģz2™H \ ŸÛ´÷QÛtŖ‘Ö°†5ŦŠ×xaFŊ ÷a‹ ģ #ÄόD••XęŲFŲGMÄčĐšÚü,ņJŖĩzĩZÉŖD†žī×ÛĐGMÄÃ13ɨŗS˙ÚīXJ5<55j‚xbtīŖ~*užįA€6{ž—ŌGMğ`iF-ɨËÁx•‡Ģ‘û¨ŋ‹øPÎã"ˆŽ”cƌ˙"ÆįŸ>ÕŠÚ—\åŪÍõ1~õãžŋ ‚Ū Ŗ‚ö;–R=—ú~_ÜĐWģ2Ėô/X#–,Yūa‰Ī>}ø ūE>jCĒ÷Ī’úņŋZ ‚x)œÎzˇÄi< xøąz%õ<.ƒNŗÜĢK–,˙°Äį.<œõg°;ĩė™X\ !ŖöQ?0îû;˜ ˆq@oīŠY2íw,Ĩz.õũžAtŒ2ÜÜĪ—ũž1cÆãͧ?ƒú¨Fm*å(š-EŖŗúņŋZ ‚x)¤…Ö¨e{—?PwŸ#V¯äŖ´F}Ɲ}šĶ7wŧEŠKÍ(ĘÕ^ŗwũĀwĮŅ]LŒĄ´b?€ķbų %>}Õ¨;ņQĪĮR}ÂÜhÔôQôQŅIŽ5ęU:íw,Ĩz%uÃėB ņ×u:šņ­2?ŠMúiũJ}›̊„ozĀ!œËW(CGŖvŋsëŖŽ5zM5}ÔA|øēūĒía@ đđcõJ>ę 2ĩaĄ.âė|<^ē+ĨŽ9jíēDÍbWŽ÷g‘‚ŖLw\˜zexėUŧÚk‚ĒģŸb{Īzø¯ČĖŽë÷š (Ŋyē>ēÚëŊ팲’Ģ”X€ÔWëˇŗkŗŖËÉ4Įĩޟ;Ú`ĀĐŽŠ%Ë.ËíáŦ?ƒú¨ĩF*R}Ô˙øÁ¨ ú¨ĢāqßĻA<3ÖŠÖ¨ãlÚīXJõb>j°G‡—ˆ•fŧg‰ŗâƒúâŽlZęĘô¨§æ¤…¨ŠZģņAwWB}Ķâc|tÅM2í˛‘1ĩÉĶ4X§’lûʘˆĨ^ÚK=čzãúĨ ÚīÍ:QÚžč‚Å€Ā㊗ûĒ/b ˆĮ‡ö”Œwk×G÷>j]S‘jmëZoéŖĻš ˆoaš×õî8 xøąz:ĩęÆ2ˆ‹mZ.ŧôÃÂ<9!F°IK‰ņžŦö:ÖąáŸˆĪüS÷Bß 5ã¸ņĨeĸŸņeË~Ũ1Ĩ úb :/A5&âõūÛ6ã^mzöĢžî:m_Ä8_ü3°mÜöŒ?6Ƨī|ÔĸQKi5jú¨ ú¨ ‚hĀl˜˜ƒĐ~ĮRĒWķQÎ KDkôōĒũÜiy+ĄŦŸãĢîã×Į´ãK4aF™Ÿ>Æˇm”!ΎÖ˙1æî#ŪėKŊ…čĮ’>õĩ#ÔįbĖøáąĢQģß9Ũø¨?kÔôQ›?žķmJÄëÂO´F *Ōĸû|ąz1õÖČŅķØŅø4¯ŠÄĨVk/ą-ĩö›œ +øÔKZ.œDžÁ߯eSË0Õ.nÄ(7ûrŪ¸’ŨéÖúí:Ņæ\E´Ķ>–Oë´}Q^׿bÉōáå&-˙ĖGíhÔۂ>ę*č勗 ˆŅAļ /ĘAhŋc)Ջų¨5ŖŽ ‡ˆę[LuĶ/´™9ĶbÚfƊKŊÜĩQ)ŋ%´ô5δäP˜J3&$™n‰YP_߯ûüŧ7cŽ÷ļå…Į™ū!¤´w‰ŽĩúöúąNsoū+ܒįõNĶæËâM_ą”HŒĨbØú\Œ?<ÖŽ?ņQgôQ߈üÕJÄKažĶÛģ”}ĢžãŠÕĶų¨ī ˆ PeÜŲCDQVŒ´¯Ė¨8xsĸC)õËä$94ĸÃÉö Ķ“<’€âÚĀf3C°OĻ{}|7Š[j }i‰|ŪLôQ㖘 Ŋn­ßŦS™Ÿ4¯–ŖĘÎ‚Ë jzpl4<–zđųmzĒĪŒåÃK|účŖî7ūĶ/\‚ Æ |‘ž…ZĻî]õWŠ^ÉG-÷ô…Üâ#įvß:–A~ÖˇMŧÚiįÉ'*ōš xچ ũŽĨT¯äŖĀWįņŅčfĮĀ”ßŠÕEŖūIßû1 q9.ŒÆgˏßxnMMôQĶGM/O0ę é_õWŦ^ÉGMDī š>j‚ †ŒÔ0ęÅžÕw\Ĩz%5AŊƒ>jú¨ ‚2’ŖfÔĢ}˙Ēī¸bõJ>j‚ zĮÃ|Ô9}ÔߎûžøAŒqŽõæĐŋę;ŽRŊ˜š ˆ~A5}ÔA aĻõöĐŋę;ŽXŊĒz2y—āũũą.ßß§ĶézŊ.ËŌ6͎īår:™xĩC.đFmˇ[Y–ųž7ŸĪíøí5H€ĄÎrmâéđ@uJõ7ãž/>A#Āæ uœõ¯úŽĢT¯ęŖļôu2™¸õ8ßÅb!1Ūßķvģ2œwôÕjÕ8ęåí'2lĮoģŧĪ`īßíNŖ}ÔôQ1dŦRèķūUßqÅęU}ԓ÷úúPÅl7›uE_…ûũfŗ‘1]Å[Ž–e ŠŽ1—‹…•¸A‚ @ĨīûY–]õ5Soč`!žôQ÷÷}ņ ‚–{ͨ÷ĮūUßq•ęU}Ôˇ4jŒ°\.%ö<ī–ÍÃÚ +vGŗãƒĮqŒĨ8C€õj%˙ ū0ķũúJ0 ]A<čŖūYŒ§û˙ū]Õįyž^¯ëõˇâž/>A#@°ĶŒ:=ö¯úŽ+VOįŖR}Ģ´¸åŖŪ8fé+˛} ÆéQÚŅŽÆ-JÄõîuތ˛Ũؘ Æú¨ãK_`ÎWõooo‹Åĸ^+îûâ1Ė ŖÆˇhīĒī¸JEõmÚÜRŖvG¨ĶãÆŖx'WĢ•īû`ãˇVŌ’Īĸ@õâõzŊŨn´ŨÔæV}=îûâ1xąfÔųŠÕw\ąĸúöOAz“$ųr(ûkDw4;žWu5ęŲlĮąĐuW-ˇcRŖ&žôQ7Î| 0ĖšĄÍ­úzÜ÷Å'b˜F}:÷¯úŽĢTôQߖ‚q˛¸‘áNŽîæúĐ>ęjŪFĩüŧĨü€QŽÁA›Ã0´-ƒ °ˇ<ú¨‰g}ÔߍƒŲ˜øņ–_š>j‚ ĐiaÔŊĢžãŠÕĶų¨[ĸQŽ#ĪsÉÔáy7ķQÛ\ĒÉGms},œ\āĶŠNô%Hģ<ŧāŨųū|>WĖõAj‚ z}Ô=Æ}_|‚ †|yžEz“—!¨žã*}ÔAü!åŖN_ĪGũû¸ī‹OÄБZŖ^ėĄúŽ+VôQņ‡ ēĮ¸ī‹OÄĐą;jzš„ę;ŽRŅGMÄ‚>jú¨ ‚,’\kÔëtĒī¸bõĒ>j›/Ú÷íÅ|>՘Ŧ×+7õ´ž‰/—“ÉĮneĨžûY¯īˇ´5‹ÅŧŪ1ÎEJYŪũ<~n_É}Ũ&ŸÉV™Ũg|ßCßú{ø%ėėĖgō˛ š>j‚ ‹(Ķõö0Õw\ĨzUu¸Ž–ËÔYˆwģŨ˛īƒįy¨QwwNŧ;;3~Ųō~ÍÕQPŽÅbņ•\ÁæÜļģŽˇL¨Ī^0įöĢ‚>jú¨ ‚,ļ™Ö¨ÃlĒī¸bõĒ>ęÉWlĐr?ģx¸ËA€–ŗŲĖ%ķÛÍ´ĶōpeȧŪŅTŠĨS~.Ō78°līhÔ˛æ‹"­‹÷ÆövåvyžīgYv§/fˇ;6JĮv_HékĮ‘Ŗv_ČĨŗ/dÃėN_Å}!_ôQ÷÷}ņ ‚:6Ŋyœ BõWŠ^ÕG}__ÅCÄ<$žeķX¯×r§ÆŨ¤Z*Ã0AĖîNb‰zeÔo0LŠÄQĄÜXļ*Ô]EÚŲâüĻFõXuŨ.ålæß鋕ÄqŒĨ]ŗ Œ)Ŧ¸q=nw{"ëÕęp™}?ķfĮ€vŠÄë€>jú¨ ‚,VŠÖ¨“|Ēī¸bõt>j!ÕˇJ‹;`œõ|>¯Čû°ôŌ÷ø•ķYËÕ1ŖĨŲŽė*ē}ÔīīĶéä–?šNƒ¯V"Ŋ°ÄõîÆéQ^õ˛ëąŨQ6voœũÖ\Äsƒ>jú¨ ‚,{íŖŪĄúŽĢTôQhŪŌĒm͍oŅjĩšÍü‰T62đFeŒtbذéŨ ?_Õ|ŠQo6û  .^å¸:øđũžîķEûõßŗãÍš3û­šˆį}ÔôQ1XĖwZŖÆ÷g׊îķŊ>jÚė1Ÿįų§ŗ-L’¤qpé$‰åŅÃҍũöu]OūŌG}k4Árš(*%j6›Åq,‹šß×>5ÜÕ¨¯ŪvL¯:ęjԟfoR׊Qŋ&ūÆG­_ĮŗÕ¨éŖô}ņ ‚:f‰öQgÅ Tßq•Š>ę ¸į.‹zę9ÜúAüp?W†"XWš>ę×Äø¨…NŠ3}ÔôQņ-x‰Ö¨§A¨žãŠÕĶų¨[âĘĢüQķ9† ĪsÉeá}ÎG[^]ú¨5ŖÎ.tú“FMuA5A-0‰ĩFīØ!¨žã*ÕĢú¨‰īÂæŖ~ æŖ~UtéŖö´FmÜ Rĸ5}ÔôQŅī‘Ö¨Ëū”ŪņÆęU}ÔÄwaĖ- †Ÿ{&ž,:ôQ{>ūMi.m^hp*•ĻßôQWAߟ ˆĄã-Ô¤ēwŊwŒĨzU5AŊ CõÔŖNŗâRŧ0ÉŠŌ¨éŖ.čŖ&⠀‚QOĸĄ¨žãŠÕĢú¨ ‚čú¨MōœÃĄHR}*ĪĸQĶG]ĐGMÄW8FíÅũëŊc,}ÔAü!ãŖ>5ų¨uG|eåéá¨_YĄ5ę}ÔôQŅ ĮR3j?Šę;ŽXŅGMÄĸCõÔ;—øĘ:âKK”jĢQĶG-čûâ1hä†QģūõŪ1–Š>j‚ ū]ú¨õNCZŖ6¤ú“FMuA5A__§`ÔķŨPTßqÅęU}ÔîžÛܡįķųd2Á}yĩZšû書r)‡Ü|ÔíqkĶķ6-mÍb1¯ˇųČĄ=y—åŨĪqįö•ŧĐ-!Ų(ŗ3‹ī{æŊj{RõŲ™ëãeŅŊZ[>YFM5}ÔA´GjõrßŋŪ;ÆRŊĒēN\ÁĸEČÂ0”MR”š ÛŊZp÷õUĩgb{ÔwBlßō~ÍÕQPģō_Žä 6ĩŨ‘ŧũcB}ö‚ų¨_]ú¨MŽĄĶ0ę}ÔôQŅIŽõj?Õw\ązUõä+6XߥģÜåƒ @ËŲlæ’ųmĶž‰˛¯"*ÅŌŠ ?éØ ļ÷öL|wövŦvuŦˇˇ+ˇËķ}?˲;}=÷÷L´ģLĘ8rÔtöLl˜Ũé̏gâĢĸķ|ÔŲIŋrÉõņ ų¨ã(Â÷ÉÛۛ|Ĩ¸mō<Įįčßŋ-Įėûâ1hĆQoũëŊc,ÕĢú¨īëĢq[!wą[6õz-wjÜíAĒĨ2 C0pÄėÎŨ –¨G€"ĻTâ¨Pn,[ ęŽ"íl˙}SŖÆzĀŌ¯–‡r6ķīôÅJpĘrâvÍ.0ϰâÆõ¸Ũí‰ŦæofßĪü†Ų1 ]*ņ:č:ĩĄĶešëōcĪÄ1û¨įķyj” |žđĄsۀfã¨fÔôQņk„™fÔÛÃPTßqÅęé|ÔBĒo•ˇ<Ā#ĄúžgŨČ-m–^ĸo_9Ÿ%°\īŗĨŲŽė*ē}ÔīīĶéä–?šNƒ¯V"Ŋ°ÄõîÆéQ^õ˛ëąŨQ6voœũÖ\ÄsãA>ęs“ÚhÔĮŌnDn5ę§đQ“8ÆGĻŪŒš>j‚ ~pi0ę(ī_īcŠčŖŽ´Đu#ßҍņ­VĢŲ˟He#oT†ÁH…Ā›Ū úķU͗õfŗąJ˛ēx•WžÉĶ{ŋ¯û|Ņ~ũ÷ĮÄėxsîĖ~k.âšŅĄOvĘėB^‘ęzü>jĐæˇˇ7ŧQõ6ÆõA5AŋÅ*Ռ:ΆĸúŽ+VôQßjP‘=ĐBėÆ6āŌIËŖ‡ŖQûí5ęēžüĨúÖh‚årQTJÔl6‹ãXsŋ¯}j¸ĢQ_;Ŋí˜^uÔÕ¨?ÍŪ¤ŽSŖ~MtîŖžĐé2u4ęQû¨/8ˇÛ-ž^ęmčŖ&â!XFŊ;ö¯÷ŽąTôQWĐš>LwĐBíŖŽ,ž¸‘øá~Ž D°ū°XOņ†HZ9ËĀ]ĩMgҍâĸ—ø¨yŪØōĒÆŽ9{sŧjo~$8 Œfˆą¤û}í¯/QŪôQWīmŖēŪũËŲéŖ~MtíŖNŸÎGmããEŽžŽoīŖļ?˛Ž°†5ŦaÄ^X€Qī ŖŪ†QÅόD‰•XęE•eˇz:uK\y•U•zj’No6kW=Îķ\rYxŸķQãÆžų>ŲúMSŽ;šƒÆK6 aėęžF])Ŋ¸ŗŲL˛I×Ûãz‰ecNÍāzyŪÅŪØ×Éõ1ŋŸëŖq=6×ĮÂÉõĄgŸ6Íîû2;s}ŧ&:÷Qg§4+tŽüô>j|Ŧ䗉øōÁ‡ˇŪ†>j‚ ‚`§5ęėÔŋŪ;ÆRŊĒšø.l>ꇠ`>ęWEįų¨Ģ-ȟ&u…xBmÎpnĩ6ôQņxņ…Q÷îIcŦ^ÕGM|ÆÜŌ`ų¸gâËâ>ęôĘG=uöL4¤Úî™ø>ę‡Ä}_|‚ ŠaÔEŲŋŪ;ÆRŊĒš ˆ^ĐĨZ˙B6ŨįizÔ¯ŦøĐ¨ŸÂGũû¸ī‹OÄ ņiF}*‡ĸúŽ+V¯ęŖ&ĸtéŖ6ĩĻĶų•F=jõãž/>AƒÆ›aÔCĐ{ĮX*ú¨ ‚øCtīŖÎScüø¤QŲGũī‹OÄpJø/T“h@Ēī¸bE5Aˆ}ÔĸQgZ6?NĪ”š>j‚ :ÅéŦ5ęi<ŊwŒĨĸš ˆ?D÷ų¨/t:ÍMŽ'ĘGũû¸ī‹Oįp,Õæ 9Ø0ėŋPųɀTßqÅęU}ÔîžÛܡķųTc˛^¯Ü}õM|šœ˜TÕn>ęö¸ŗéyŊĨ쓏 Ib;‚$sūČæ5_NÚūėڃÉ@žõQ7ė™Xmīâî™HuA5AŒ^ĸ}Îŗ]'ƒį'­QĪ’AčŊc,ÕĢú¨öL\.S#d!Ūívv;?ŧv¯Å}}Uí™Øõī´ŦAûˆ–9¨ģXæũÁûL ú¨=Ÿ^Ŋ;šĄĶéąüШéŖ6ô}ņ ‚ø9'­!ƒQ˙3)Ŗ;žę;ŽXŊĒzō•ŦZߥģÜå…ëÎf3—Ėo›öL”}Q)–NeøšHߋÅÂęąhšŨnÎ˙ĪĸŊ쑎U×1#æÅ_QÚ-ŗ,3•ˇŌN´\~LäļLĶ}ũėš÷I|O’g”SvwŸŧ5WY–ķ @ôĩë˙héœ>ÆÁųĸ¯âϊOŠ.}ÔFŖ>ĘĄO5}Ԃž/>A?G˜]rqāĩéāŪ¸?ęņ—ûAčŊc,ÕĢú¨ī@hÁ%{ŧeķX¯×r§ÆŨŧT*Á~ÁĀEŗŒŧõʨߞīK%Ž 寞Å\!-‹Īû ‚oc(ËđíĘą0ņ‡ ´íQ™įųU%—‰ 9ßV-§õ–.@w­˙ĝ= CŅę퉸uã\_č}EvũļĨ{úōč!1f_U˙S@< :ôQzoj)™š>j‚x,öZC—Fé%?9ę‘×é€TßqÅęé|ÔBĒo•uĩÎz>Ÿ×Éä}Xĸčû Ü%đ.%––˜Ņ˛S9*,Zn/æW%Ž&ōAŨ¯\ öõJɁpéåy—–ĻûO¸Ų/ŖŧZŗûžÕIū­šÜʉsúļŌĢZb|ĢĪã(ÖpkyÄHŅĩZĶiũŗD]ĸ™nLu÷}ņ ‚ø9ÄD—}XΏ?Ę/ę÷ôŪ1–Š>ęĪÃ\Cĩ­šŖQ‹×w6ķ'RŲČĀŨJ§åûÄx&Līw÷¨•m×ëõŅ(ÉuúŠUĄ f‚@ÔfYR† Ûķ9*? d)–[ĶrnģšæÆGƒĢ–õšÜn¯zËĢëŌ‹{œč]įŖŪŠ}Vė2”§ãŠŖĻš>j‚x;åÅ:ĮŨĖPëÃŖ‘ˇ™Ö¨Ãl@Ēī¸bEĩmö¨ŅKpæ$iū˙°Ų$‰åŅÃҍũöu=…=:›Íd=WõuudÃjŧ•õŖW-ũϞ§ķõėÎ‰ØøJŖŽeĪĨ]cKw|ŧÔ¨ŸŨų¨§ž_žĪāŌ ÕR‚x‹FMĩ ī‹OÄÂ=u°9č8z4[´:ÎĄ÷ŽąTôQWĀ=×z-\āÖî'6`p<ë*…įMņ†HŠ9ĢĻē>jëOnÔ{­‘ø˜įõ–äÜūē°Žâĸ‹œo–ežīŲJņĢāšĀRVL$¨´ÖîÆ–.´ēz?īkÔA؛õ­šäLŨŸX6ļtĮ§ú)Ņz2õ𝖤yr8&éqw8~hÔôQôQĈ‘LŪŧD+ĸ‰ąg,2 ü ĢTkÔģã€TßqÅęé|Ô-áf¨ÕôRķ9s…”dŦŌûœˇøétꛜ8dë7Mš>ėhnŧZ­0Ŧ˜]S‡åÉĒI%–ŸCJŽû(ŪQeđđ­׹>QÕ}2ķũzlu+×Į'õ%Æŋ %rzã\:×Į\įúĀb×o[Î>=8|ŒĪ\O‰î|ÔøČ•į3¸4Hõΐj­Q/éŖūˆûžøAüûâÂĸAŪrÃŽūãÄų^ģ?BīcŠ^ÕGM´AË|Ôía\­ŦŅĖGũŦčÎG-uœæBĒ“ÃņX”`ÔôQĶGMcG”iēkqŧGZOžļūÁN‰oÔßĢĩ¯ĢWõQm` -›ßc *QĩÜķ‘{&>+ēôQkZĢ͇Ŗ”VŖĻZĐ÷Å'⇸x§ŗ‹"Ēœ=xŸ/ÖS䧞•Ūņ–ęU}ÔÄ_l(´Ã$‚#͜¯Î}ԚNRŌGM5A<ƒąË/ŠčÚxžã‡Ē›“XY”R}ĮĢWõQŅ ēôQûøZÛg'I‡’ų¨éŖ&ˆį@°ģdĖET~œ¸|čeCÆŪ•Ūņ–Š>j‚ ūįŖÎNûü´7åQgĪÛĐGM5AŒScÉ8ž.Šh~2;'Æ|ãŋ‡ũ+Ŋã}ÔAü!:ÎG­ŌcYmD~:• Œē ē úžøAüV@ļŠčDj4~iō]{ņm ļ<Ÿã°Œļįh[n×įíēÜŦÎëUš^7+ŖõáĻ ×įhSF›sŧ-ãíG¯pƒŖgÛw#em„ČôÚEå.Ōež“đĶ8YÆˇŗ  ZîãķaWĻÉ9Mt_TÆ2¯Y•Ėģu։ž-Ņ}1ÚËúeU8ģUuŽR^V¸Ņ+A_™7‰dĩŠ>j‚ ūŨų¨§z#!eé4‚ât^ŠFMuA5AŒĨԊ4(´ĢˆÎd÷ õąÔŖųņ %öjJšX–ËeU.j垛ršŧŽičûyœÕUû[ãÔę¯âëqjëŧĩļåũsl^zUu}įAܡįķųd2™N§ĢÕĘŨ÷PßėK9äæŖn[›ž7ļŦg‡N’ØŽĐ2WÆC ›×|9iûŗkæúxVtįŖŠÆ?C§/¤ē¨4jú¨ ú¨ b´Đt7R~ōI7ŽÅJ>f ŲA,ŊA>Ī EËå9Z•áēŒôKôįRĢĮĄ)Í_ĩbŦĩ_ũ’–áåetŨuiÕ]Ũ+2¯¸ "ŖBåYĢĘFOWĻÄĢj3 MZ–NKũÚ,/éâĖ{ŅĖˋÖ]õ WgŨ~ŠGĀ õĸcIģØžÎ;ŗNg…åemĨzUu‚E‹„a¸X,¤īƒŨ̇p_˙Ažä–I˜Ĩe}ü ڏđ@´ĖGŨÅژúYŅĄÚ{Nĩl*úīßŋzŲnÕĸš žø–ûWŅ]ĢĨæØķŨcĻ­Ķ;Čėję´ĐéÅR3ÕCZķ2ĪX6–ęU}ԓ¯dUËq/‹ĸ¨ą îōÂuŨ] mĶž‰˛¯"*ÅŌŠ ?éėŨęąhšŨnŨshīnŪmÕuĖh÷L´{fYVí™øQi'Z.?&r[ĻiÆ›÷L|O’g”SžÚe˛q.Ŋgb ÷LD_ģū–Îécœ/ú*î™ø¤čÚGŨ˜ëcÔ>ꡡˇų|^1ęOmpßö}Ôņ”HŽZŖ^í¯ÕcA:Ryų )Ēd>f9•Úėą\ž×Ë2Ũę˜ëR^Œ?ĮęU}Ô÷ q[ėņ–ÍcŊ^˝Zvô–J°_0pÄ,#oD=đjß÷ĨG…rcŲbސ–x“1˛wL ež]9&ū”ļ=*ķ<ŋĒÄā2‘!įÛĒå´ŪŌčŽõŸ¸ŗGa(ZŊ=WŖnœ ã ŊŖČŽßļtO_=$Æė+cL"ž ĖGũŗøB››ÚčCôQÄķ"Ę´FŊ9\g™XėuũCŦÔĄŲ“S|ōQ‡Û‹y÷ŽŋTOįŖR}Ģ´¨û¨Ģz­ļúžgŨ-- –(ĸoģŪĨÄŌīŗe§rTX4ŧųÂí]•¸šČuŋre€Ø×+=ũŋáØ÷ŧKKĶũŽ'|:žĪå՚Ũ÷­NōoÍåVNœĶˇ•^Õã[}Gą†[Ë#FŠ.ķQë=ÁĨ“ôBĒ힉OāŖFŨ؇čŖ&ˆ'Æú ĩhđę+zkę7øŋÜ­aÔQ¸\žÁ¨ãíāáĮęš|ÔíqGŖ‘ÍÛú¨īhÔâõÍü‰T62pˇŌių.Ūô~wZŲvŊ^ÍeĒĶWŦ m0{ĸ6˒Â0ŲžĪQųĄ0Ë,R:-ˇĻåÜv˙r͍W-ësš Ü^õ–W×Ĩ÷8Ņ)ēķQ‹F§9u‚ōp<%õØ}Ô7ú¨%Ļš žËJ‹žĘÂ.†úāVęĨŲ“1ɝņ7k-PoWŊkŋc)}ÔˇT\œģą Øl’ÄōčáhÔ~{ēžÎÍfxįm’F•X˛a5ŪÆĘúŅĢ–~SŖQ¸ģ/ŗ;'bã+ē>”=S”vũ-Ũņņ6RŖ~>téŖ6uE§XzÔ>jAŖZâö>jû“ČkXځ×LÃR6L Ŗ8 #”Û0Â÷^aRŪim9Š9—0Î*ڇfäs•ßãnâíFdØdĢc)E•ŊŽyí6ęš|ÔíҜëÃL Ö§}ԕƒˇ~p?ąƒã!XXŦ§xC$ŜeāŽÚú“õ^k$–ė_Å įöׅu]dÁY–ųžg+ÅüœįšĨŦ˜H $iĀ–.´ēē÷5ę Šę?”oÍ%gęūIJąĨ;>}ÔO‰Î}ԝÖ5}ÔôQÄøáÅZ‹– ¯ōEOÍ>/ŋ˙qĸŸčq˛Ķe|o/õ´ßą”ęé|Ô-áf¨ÕTōQOMŌéÍfíĒĮ |‹ÅŦŌûœˇx4öMN˛õ›Ļ\v47Į°.svM–'Ģ&•X~)š>ėcxG•ÁÃˇŋ+ÄšÔ'ĒēOfž_Ī€­nåúøäŖžÄøwĄDNoœKįú˜ë\xCėúmËŲ§‡ņ™ëã)ņ(õūFŽŨĄØŽģ eA5}Ôņ OMģ^Üŋ~\–]΋ō2˛Úu˛-ŋé%~åXŊĒšhƒ–ų¨Ûø>ZYŖ™úYŅa>ęŠÉG F’@īCŖŋú!qߟ ˆoCö›w3ŒĖ/ — pŋ‡f ›ƒZś2^•ŲaÚīXJõĒ>jĸ ŒĄeķûqŦA%Šĸ–{>rĪÄgEįų¨ķr_mōbōQë=ŸĀGũ¸ī‹OġQFíÅMģžUnö:œÆŋšâtÖ28šŒŧKt–í˛wÕw\ązU5ņ— í0 ‚āH3įkŖ;ĩhÔāŌ˛9¨uq:/eĪÄņû¨÷}ņ ‚ø6dđYRÛͰŠ'‘V˜‹ōįS€–ëœ!I5æÖdųˆÖŊĢžã*ÕĢú¨ ‚čŨų¨AĒq/HĨ9¤KÜbDŖ~õCâž/>A߯ž¸ø: Ũ=•§¸™ĢTk+u¤â_đ˛Ų“qi÷d4û$–û¨wÕw\ąĸš ˆ?D‡>jĪÃí@Ôékš>jķGߟ ˆoCœŌëtē,‹mYlĘ"R-¯$× ķüVęĐėɸ5{2â{ķ˛Obvč]õWŠčŖ&âAuqߟ ˆoCv3éUåîŦéôV—§ÔjÔ6+õąL/Ûģč1÷ģķrUn*õ`āáĮŠ>j‚ ū]įŖ6‰>N’ņÃäú ú#îûâņmŨķŗ*­Q— okœĪÖS=3Ų¤LÍŧDkԙäģŽļ嘨 úŽĢT#÷QëÛër9­eŠvsMOõĄ•=Tß-Ņæ@ÆųFQäæ‹ÆÍ\2QcÕju>—ŸA<9˜š>j‚ Úcn2N§ĮSYlÎETžËķ)Ö1¨uåĻ+gČP”ę=R‹}•?dŗúČDŨˇę;ŽX ĪGŨXĄ_íĸ2Œ;ŽÍ`|•ôXˆąÄõŨm ˜ų|>wûb@Ų|DÆˇƒņ3tįŖžÚ3ŧúX05}Ô1nxfī•ė˜ŗĮŒZ˙8QÛ?b›ņ#3ŋ^œD?_Øøjo˛|`tĐéåĸ<뇠úŽĢTÃķQģģæŨĮfŗļ[Ō_ĄN›-OŽĨZ×ûÖ!âgčŌG F}6túBĒ­FMĩ ī‹Oġ11ŒúTėĘbmtéŌüDqsÖÆŌēŠ5ņŽ´sãģ˜íŒ^˜qō\3ęÍōBûV}ĮĢáų¨īÚ+x¸V^Ž+\Q_ÜO—Ëå­ņ¯jnŅfę‚>j‚%ôn†‘2Y>Ö`ԚN—§ō”ãĮÁfüØ.ų:žt”ņOf˙ņ2‰4ŖŪŽz×{ĮXĒ!ų¨'ÆúlũĪR)?TtK§ũMŸæŖ^ˇņQÛžcÎįķ,ËžuFA\ĄKĩÕ¨ģC€>jú¨ bÔ(.ģžĩ"} Īû]†įCZ–ųYŌčUuZč–^ōŊņ÷&õ|Weĸ7zˇÄd3Õw\ąžē úĶĄ:ŖūJŖÆ“Åbą˙8&â7čÜG}¸Đi”&×}ÔôQÄX‘]v3Ô6ĩĘíFŋÂm R]„:7u™ŲŒī‘ĻĮßڔô&1jtž:;í˛BJ“š>jú¨˙ŗw†đĘę`ŋņD#•¸h´F*Ņh´‰F+‘h¤‰D*‘HäžÛ`N“ãņÄīy~^î˙cDx}ĪÃAKU;›aTÔÅĄŽOõ9¤XēψútŦÅķ‰uķaôD†ŲŖ‚\žķĶŲ•=ŠĢ(Ÿ+xP%o’õ]7īįŖž.ę cL‹clŦ]TmģŨʛ …ĶÄŊäŗž-ŨĘŠÂX&‚ īę÷|ÔŽËj>5ŸŪ…/Ķō6g"|Ô|Ô´<…b>ÄCœņ1¨ĪĮęrŽĘĸē\xš:ē4UXå‡JŖW7g1jĮnōä‰rj'lSÜÕ5nMÔoī]â˛y'õʲLŽÍXę>jZ  zÃkĖ]ĶĮC&UQÔEÎųtŦ 1‹by‘9jēâą3ˇR‡i“WMYķW=Ū8O€Ÿ´‘¨Ã#‰úŧŸŦy?5æL„ Öīų¨]ÆčĻЅĶüuËQÃG-ū7÷‡AĐ÷Dáî§&JŖ*Ũķŧ4wä|GÜPĢ|΃ęĒ”yæŗČiSœĖ—aË|¸é+_ۓt‰„i›ŖŽ÷AĩÛqõä{—¸lŪĪG AĐëˇ}Ô×| G ĩÔÜ>AßĶúÂã$ ëëĄē\jēĒ•E-‚ę:ę>jZščĘö%§wÉ÷uČ-";]¨LuG|܏ôȍ鹎蟧Úxļš]úÕίƒ đvǍĩc7ĢÆ1áŲīũĩ‰úĀGĸŽĸÃė™Ūår5A¨_úŠņ¨áŖ† OPVōˆš…%čCŽōĄį¨iI/Ѝûj/Ƒ–¯ũž:žĒđ\EquMĒ,įßņxõŌ_-g*ggÎíäãI*6ôĢëeöLīr— |ÔũĄ~ĶG}›3‘/ĩ9áŖ–šûÇ čâĶ žš]”WɁΓ¨e§%×qĖG–ščÎ<Ã,g)¯KąTLWÃĶ鰏Ĩš:-sËŋh^jŠÉŠ…Kđ™Ūår5A¨ßķQ;ŽK7‡ķ5Ŗˆš/ŠÚģ5|Ô|Ô´0ÄøŌû(áf ŠŲŒė´˜%¨öâ•îĢl_I%béĒ,;č¸(ĢÃqsČŋø$ŒŧåĶĩĸŠĨ›ķžŠƒęzz‡Līr—ÍÂ}ÔüöęûŽã0ˇ?ĩGÚåĢĩj`ōn–FÚß0 õņ¨éæžŨn‡îÕn˜ę‚~¨ßôQ‹5¨3™ŠV9ęEû¨ŗ,Ûl6___›õšX¯C˙<˙ũ÷|Ôôy ŽbÄ$Žĸđ.;M!ÜáPKĮ5æ9ŌđÄĮ‘Ļˆ:;VŊė´ÎtŨ=wĮŒÕaYûÎėÔņą ß(ëģ,nŪĪG=]ÔÃ5cQÄ^ĩĪ™H÷åŨn'؜üE•Pd.ãgĩŠ”/Ļķ‘‚mÕAĪéˇ}Ô2G-ˇõ’}ÔūnGa3íÅ~ŋ'ÖëP˜M—,Š¨áŖ† ĪŖÛ% Ÿyü–N“J8=ø’X†Á§#Æ<¨Îã~vZgē,†įÄĪ!îvՉ ĒëĄJįĪņ.}ŲŧŸZeŒępØS”;¸Ę ›UœlļßËZ[æ[´LnAЍų¨ã(’NiZD|ĩËHf°Ŗ‰>jõXb$}ÔÁâ}ÔôK?MSbZēŽkÖõ¤6įūđ!š*>ЇSēJēQ>’¤>Ÿ[×ôáĐÆr*SJAõ™ã}ŅĒS•]ĒôR‹Ĩ|ÕŲU´#<ÕIÂuLDZ;éšzLīršy?ĩ}q]Œn #6Œ^čK÷Sß÷ĮÚī•Œ…ÍįķY5AĐsôQ‹đų†§K,O‡áE葏ZŒõAątZDb)ÆúXüxÔ] Zqŋ/œÖfÍž­ųØŗ•XÖˇe­-ĢŽŽŪV,ĢvŠĘÅߐē3 h_•xÕíVĩĒYÉĩuŨcõ*xōŒ–5}˛yŠ^MÖqÁßKÔŠîßQoŗĒENĩyײļmuĢß-Í­ QŋĐŪQ´ß­Ĩ^ņ?ŋWí’˙4Ŧømŗ[–üÚ.Åũ“×Éō*ÍĘ4¯žä¯LļPÖũ#ЎnŋēãSŅĄ é⤉ŖTŠŪÚQ-K}īĒō~Í×`ũč–ōķŊmUČ>”m?{ŸWeėWīŊî>¯ģΎj?eíŗܯn¯åģķs&ŋqŋeŲEŊ}ŋ}î’ĩķ§ÛĒŲ—îŨģã Ūũî3íŽ[˙Hįä5įcqlΟüt⁴LMĶ+ ‡3ĨÉĩŽN<įLq˛\š¯”^Į*9ĩuná4–åŖv„õYųŸeĄ|PQ_jõG3Ɔz?ÅG­ļ5*pYx‚&ĒīŖ.KŠĨéE!uw *Ä5'—Ę„x<}>ķuYNšOōÂgxųˆņ¨ŋžžŠî…œ:Ķ}Ô_§6ë%įSûŌ–+ŖdJ)%īö^%§Ŋהvžz¯'KŪūŊ^õšŋLJĪ}ĻŊåęĨ%á>’~éZ‹w>sˆ%SJĄ]tŽĸcĶë –Įęzäž žĶ –AuNĢGß(Ķģ\nŪĪGũ9ęģUfĀXjŲÆŧęZ¤?ĸHuø¨Ûą>ōvŊvŦÅG ƒÁāEķ\>j‚>Xã>ę~8-ꨇķi9ęZΖ%|†—Ī ƒÁ‹æš|ÔČQCĐkĖG]uIęģ+RTĶōĄZŒG]Ÿãô|ÍÎ1Ÿ9ņ6gâ’}Ô`0 ^4/ÂG Aв4îŖ.­'NÉQŗĒĸˆšÂéôBAõ5ãŽ>j0 ĪÉđQCôrû¨Ģ^P-ÖļA5-ú¨eŽ:ŒRTgTįEI5|Ô`0 ž‘áŖ† čå˛û¨‡Ãéoį¨3™Ŗ.ē5|Ô`0 ž‹áŖ† čå˛û¨ģj0 ĪÅsų¨§‹z¸f,ŠĸæŅœ‰I’ėv;ÉfĀŦJ(2—ķš˜īåû;Ë<ŒM”ŨG­‚jq ęŽ;âGũäņ¨ NķĨú5 ƒgäš|Ô*cüP‡Ã> ÃÁUfØŦâaŗũ^ÖÚÜö|>ĶŨÜ>#AS4æŖŽ„ÆFü(ú9ęŅ9Ål‰Ÿ91)Ĝ‰‡>j0 ĪĮsų¨§ތ?ˆT Žęå“é~ęûūXûŊ’ŪļôƒBæˇ‘Ŗ† Ÿëz0¨æ&´Į>jūd"Ÿ…ŧ §åœ‰žĖQÃG ƒÁ♸ī}Ԏ°>+˙ŗ,”*ęK­ūhˆkø¨÷S|Ôj[Åt<Ī“?.Ŗ† ŸkÜG=Ŗ.Օ§(l>ęšį¨™ČQwá´ČQÃG ƒÁāšy.õoä¨īV™õxŽúpØ_¯ņÃ6!š¨15ũzå'V7'ĩ>"áTõ5—'Ōō6Ö|ÔVN’dģŨēŽC¯Ā÷Ķ4•åz–ÃuŨŨn—e™Ü–Jzí´%|œÃ.Îëõš>˛oõ‡>ßÍfCWZږ1넧sŨÍzMŋÂÛĄîGŗoŗsw„é;ž:ÂyŽ2?ōûōßOՎگ<·ƒÃ˙ųŊū؏ž‰’KŗÎ] īqli!•ĸ3‡Cß׏Ą:‡éĐû÷įv¯u<éŧĨ3Îa~~§?öĪåîØ 6ëä™hÁčÛėœ\¯÷G85ĪyngÚšŨkGí×ĶßSûņéeeÛúģķC~ut/\e ËéŠM{¤ūIL%cÛöÎ˙é}ƒ hP“|ÔĨéŖ.Ļų¨kŠĨåĐyúœ‰đQ[˜b S)x { ŊÎaHˇEY.zˇ!ĪŖ(ōkŠ ĖrWŧĩŲˇ™ŸĀ"úWFg īīŽažĶ9Cį¤ü—~nÚgAílˇt§ÔíiûÛmZĻ.Ôę¤YŽZx“c+Eß8ãˇ_Oũ\ŌpīܖRûõô÷túņG¸ )~đŨų!ĪåŖž.ę }ĘtímõĄ‹ĒÉOŧá4ąŧÛ*m‹5ũ\c>ęēļû¨Ë‰ãQ‡qzŽS1sbšŠ9áŖgēīĮąŦš^NG’ß­TŠué+Ë xnŧo–ß—SüFQœĨŸ2P—ˇąũđ~Ŋ[ĩ=Â#Y5Ŋŧ=ū 0¸mīXYú3åøĐį˜$׹:oõWēī‹xJ?ķ6ŠĪ R¤ĮíyČOāÍÆ|_Ëšg˙\Ķģt€ĮßģŲųv„'œK]œlû+ĀÃķpđēņ­ãC\{„ŸũŒ^ˋš.°ō/ŒõĮŖļl•$‰ø•ŊĸĨ9ĄĖØļđQCĐĪeķQÛÆŖļæ¨ëۜ‰a”¨W^”[?(ūa5)8§ĢāJ¯ũ%k¯å÷ucŨŸÂûí˜ÔívkĪ ēŽ›gí/&ÚÄuœ‰}Ļ OĮ#u†bš6´ž¯Cå"ĸn›v]gŦÍ?Í Ō9•ŗ*éĩä)ŨĢ#đ`;æ_v]u띃Û~ĢĪ}Đ2A=Vį˙ WyP§Ģ:[UYPvąŌëĐŨ_˜ôŗTĩ_cT:¯dĖÃĪų,ŖĶōģ}~øšˆü˙ÖRįO3¨y^Å7ô"(ģ}×ëĐ ,ŊÎ%íãŪö;}~x|ÆūÂōķīÎĶ<—s&BĐËâŖļŒG=ÅGí¸.EÔ§ËUž(ĸÎō‚"ęŲGíŸĶ˙üëģ˜/ũ˜ĸkŗŽ3ž)ęų¨[‡Ē5ƒĒ—ëׇ}ϚܧōŋļAĐÕˇ:TBåŌO")o‘A üōëŋۋ‚ęáūäƒíô}ÔžŸOđQĢmŸëķÃããyŪõ[ęüŲ_D8ũ_“ũGKzQP=p §įMÕûŠų¨Ũ đģeŖÔ[kĪžK?—M›>ĩ|ū.ƒĘÃé˙ūS¯*†úãä#íôFüî[}Ô?üž><>m‚úgYî×ō\>j‚>X6ĩæ ‹\\xōâaŽZú¨]VVUØEÔôR9ęÖGŊ Ž"–n_ôĪÁ j6!ƒÚkßČRæzŽZ–ķĩHņMéŗ!ķ—6ō¯í÷u(¨Ļrę0ũŧĸ–ß ƒZđėôו§ųŌȞŠŸŲŽ%Û&ŒÜĘķŧˇ_Og2íĮ'Ž"õ—kßūâŽĶ• §Û :[™učLQܡúŠŽž’xķ–Ŗ&Šõˇúl˙\ôôŠĨov­āŲi-ĸn´Ãĸę¨#lļc9—ã88÷ûõô÷Ô~|Ô~ú3ú žËG5}°ŒG­Õ|eT÷ĮŖ.‡|ԎČQŸcT‹e–;™ŖūW}Ô_m,ËL5EÔfēû„|PŽīer֌ŅĩZ•'ɕJ ‘zčŖdĒÉ7œ‹‡Â,õ웿p›éŖ.W_w9ęĄėY{„ŋ™mcōwåt¯×lâļvļ‘ ļeP‹?ü+…Đ2;ŨÕ_fß÷ĮF•ąôsŊ^'šĪöĘīZž‡ĘĪņQÛŲūščįųXŋĖ ö#ę¯/ŗŽīīÚ#üs‰Ž§îođ+ž§öãs;Â?ČrŋœáŖ† hY˛ø¨Gųā×q zėŖvųpš!ÅŌ"œĩõ?ëŖ¤ëŖ{ųįÔŦCa›ëē—ķ9ĄŦëÃžÉĄúxÕt›ƒoúXY&Įú˜ØgšašĻԋ}Ä"ĒŅëP¯D¨“Ķ­š}ūq¤ÍŋôQ—Ap—Ŗ|ŗNī‡#ŖŠôÚ§ŗŨķÔNÔūy&Ķr|¤Å÷a;öW€*ķõu•fúv͝ŗ8ŧ–‘(Ī7ūôĨöčPS /Cyō¸ëãUT50‚ŊŋĖ Öž¯GÔĩp}ôę¨#\hGøášÔaúU(°wéNųá÷Ôr|äūa–û7>j‚^Ž1ĩz0ą(îFų‹i>jžŖŽÂđ"ƒęs›Ŗū§ĮŖĻ}§ úKø=ü0Ģ/īwr°Ų ,cöęEíđŅŨŗ„m9ũöąGMŅË`9ũ´ÚŦ×ĖuOZFWešé-ŪnĨbËŲ3¨’yŊúâŲéĀĢęČ#ė:bÄī °ŒŲĢs˙OÎÔŲ3üŊãŖ\ąA-ūô¯9Õ"SũEĐŽ5ęË8jpjķDūߔĩôQSD]Vį0â/QįbôŧâöQƒÁ`0xv†‚ —ËîŖÖįIäę‚ęūxԃ>ę•Ã#ęãų"‚ę EÔyé ƒÁsō"|ԅ0ךŽÃÜūxÔúˆ.û} V ĖBṺ́Hû†aÎÄû™C{ čŗ5æŖž§Õ)ųņ>öQ‹õ…"j^1ψēčrÔ˙Ŧ ƒÁŗķ\>ęéĸŽ‹ĸ¨y4gb’$ģŨN˛9Q‹*ĄČ\ząÔ*Ė“A¯Õ$uwÁÉÛ z’Ú•9ęCČ#ę0ŠôõŋęŖƒÁ`đė<—ZeŒęp؇a8¸Ę ›UllļßË<÷rÔ;AЍû¨õr..9RŲãuëŖĻˆēŒŽ<ĸŽÎŅåĖsÔ>|Ô`0 ž•įōQObŸt¸\ÕË-ĶũÔ÷ũąö{%úļüq_F7jgŊ^Įq<ącÉâŖ–—ļ4ׂę,›2ĩĖQ_adæ¨áŖƒÁ`đLü÷>j}Æ[•I–*ęK­ū¨%ÃđQī§ø¨Õļfƒt4vģ]šĻßŨ)‚tY}ԅ”ŧæ¨pú[>ęčtŽxŽ:ޏēōáŖƒÁ`đŦ<—ú7rÔwĢˈzäĖÂĻš"jz ø¨;ËGĨyQReø¨Á`0<#/bĘ}Įs>ã­éŖ.{>jÖæ¨šå#ŽЍUŽ>j0 ĪÅsų¨1g"}°ė>jŽŧ Ē -¨6}Ô2G-‚jéúXģŽSW"ĸ–(No9jø¨Á`0<÷|Ô*SũV9j‚–Ĩ1u›?æåšæ ÎÆsԆÚu),)œž$qœRD]”ŋáŖƒÁ`đ|Ŧû¨E’'‘÷#ä¨!zZvĩŧÚ¨˜ZÕc>ękŅå¨Ųš?™XÕŅåG<œŽŽYÎ]đQƒÁ`0xNîų¨éŸü–÷f>j‚–Ĩ>j~ĩé‡ĶŲÃĩôQģŒ"ę8Nã8‹¯ytÍ)đ–9jø¨Á`0<÷}Ô2ŽFŽ‚ 衏ZĪQgZŽÚē/Tˇ>j—Å×,Nrņ*ō˛‚ ƒÁķōZüŲTŪŖ† čiŲ}Ôš&é ÎQwãQĮ<¨îrÔl]ÕÍ5-(–ŽĶ2N‹ĸĒáŖƒÁ`đŧÜē’Žä¨!úl>jf÷†ÎJér4ėŖV9jļv™ČQ§å•Vņˆē䎏ũ>j0 ĪČ=ĩš9ņ­rÔÔ#ß÷]Įanj0 ĪÄ=ĩ*˙Ŋĩ#ŦĪĘ˙, 僊úRĢī˜H>ęũĩÚVį 䥠FTCĐeķQƒæ‰h:ĨEaø¨Ŗ´ė‚jÍGŨ4b=G 5 ƒįäūxÔ]ųsÔwĢˈz{8ļívģU•éh¨Į!zNc>j98įāĐyƒ>j-G]ߍõ‘7đQƒÁ`0ø}x.õtQgcrD;ûXē¨ÅÉ´m#ÂibyˇUŌˇĨUÛ­G÷zj˙xŸ_Úwú5æŖV9ęĸ §ķÖMĘYČ-9ęęŪG}… ƒÁoÃsų¨‘† ֘š~ŋËÂÁ?žđQ_sø¨Á`0žīŖ.áŖƒÁ`đÜ<—9jú`ų¨UŽZÛšf˙ā5ÅÛĮŖ† ƒÁoŋđQC´,Ų}Ôō*d>œhÍQ÷ĮŖ† ƒÁīÃđQCôr=ôQĢ ēЂjЎáŖƒÁ`đ>j‚^.‹ēėŽBŨəz8>j0 /”áŖ† čå˛û¨UyoÄø¨Á`0ŧP^„šzäûžë8ĖíGíŦ¸gåōUZ50 y7K#ío†úxÔz;rųÛ{AŸ-‹z,œ~˜ŖÆxÔ`0 ~[žËG=]ÔÃ5cQ5æLL’DM nNūĸJ(2ßnˇcķ-R#Ē}‚ž“ÅG-.;ębs Ē)ĸ† ƒÁ åš|ÔĶķ‡Ã> ÃÁUfØŦâdŗũ^ÖzlžEĪķh÷'ö ‚ Aų¨ģˆˇPW}Äø¨Á`0ŧPžËGmŸ@\cLy9zęå™é~ęûūXûŊ’Á5ÔôY|ÔĨ¸ėtÅwA5|Ô`0 ^(˙Ŋēõ*¯îËōAE}ŠÕļg4>ęũĩÚÖlpģŨŌqøîAÔ͘z0G]t9jQë9ę >j0 /ƒįōQ˙FŽún•Q?ĘQ§iĒRÜũDc>ęFFŧ"æU×"õˆb 5 ƒ—ÉīīŖ‚€îŃĢ,aųfŗŅŗÍÄTbß jz•ú¨o?íyÖöŅĪQÃG ƒÁā…đ\>ęéĸÎ0Æâ8nõĄ‹ĒQ„LÛ6"œ&–w[ĨŪļHPCĐ 5æŖn†rÔ*¨æ#[ÂG ƒÁāō"ÆŖÎ˛lˇÛQ ĖXXvuU–77uW >j0 /—áŖ† hYzāŖ@>j0 /–áŖ† čå˛û¨+-Ē֞Pėû¨rÔëû5_ÂG ƒÁāų>j‚^.‹ēĒ*-œžÅĶôß7|Ô|Ô`0 ~#†‚ —끏z0¨îų¨÷đQƒÁ`0x1ŧ5õČ÷}×q˜ÛÚYq9ÎĘåĢĩj`ōn–F~×C}ę>j0 ŋĪåŖž.ęᚱ(ŠšGs&&I˛Ûí$›“ŋ¨ŠĖˇÛ­ž-m%īõ2ØV@ôœ,>j ¨oAõũ#ŠOų¨kø¨Á`0<;ĪåŖVã‡:öåŽ2Ãf'›í÷˛ÖúļŊČÜŸÜ‚ )ķQ7<ĸž ž×ķQ÷sÔ<ĸ.ģ™\āŖƒÁ`đûō\>jûâēcc6Œ^$L÷Sß÷ĮÚī•čÛApš\‘?NAāOėAƒzÎGM_Áīú¨īÆú€ ƒÁ3ņßû¨a}VūgY(TԗZũҌąáŖŪOņQĢmĶVëõZļC5ũP6õČô.´õQSü\45|Ô`0 ~WžËGũ9ęģUfDmÍQĶí^ú¨/—‹JtCôœžē ƒÁËä÷÷Q+K†)KXžŲlōüö‹€˜JÆļ…‚^ĢqĩĖQZŽ:Z9jЍSåŖnāŖƒÁ`đÛō\>ęéĸÎ0Æâ8nõĄ‹Ēmˇ[Úļá4ąŧÛ*õÆú8N*GŊÛmeO čŸŅõˆņƒo5 ƒȋ:Ë2 z)fŦ?ĩeĢ$I<Īsœ-Í ez>jŠŌ15ŊJõā(üú“ÃG ƒÁāĨō\>jĖ™A,ģēËNwWŖŧ Ē›>j0 /’įōQCôÁ˛ŒGm>Č˙ˇrÔÂGÃG ƒÁāˇįš|ÔČQCĐËîŖÖįIäę‚ęF÷Qæ¨áŖƒÁ`đ[ō"|Ô-Kvu҆ĶęŠD—Ÿ,‡ ƒÁ‹eø¨!zš&ų¨ģ NŪÕü‚5 ƒ—ČđQCôrŲ}ÔEĢ\\r¤˛uëŖŽāŖƒÁ`đ›3|ÔŊ\6u10tž Ē›īú¨ų>j0 ĪĪ‹đQS|ßw‡šũņ¨—ãŦ\ž*PĢf!īfiy°°7–5ŨÄcŪf#§’ č'ķQ7<‡\HÉkNMgƒ>ę6Gmú¨ ø¨Á`0üF<—z獇kÆĸ(j͙˜$Énˇ“lNūĸJ(2ßnˇúļišR .ŗ[žįõfW„ č잸¨īŽB<¨V)jūū[>ę+|Ô`0 ~žËG­2Æu8ėÃ0\e†Í*N6Ûīe­õm)ĀŽų횋ĸkĖBA?”ÕG]H=ÎQ—r|ŧvŠû¨ø¨Á`0üN<—Ú>¸.ÆØØ´āŊ5ŨO}ßkŋWĸoëyžŠ¨IŽëNėAƒ˛ø¨õëŒåCÄÔßõQ7ũ5|Ô`0 ž‰˙ŪGíëŗō?ËBų ĸžÔę;f#R†z?ÅG­ļULīx8č8ĐæAXŪ‚ )˛ø¨ÕuFåƒÂiúoĖG}5|ÔmŽš/áŖƒÁ`đü<—ú7rÔwĢˈzę č^<¸Ę–o69a„1•LŲ–Bkõä#AĪÉâŖîŽ3šæ Î†sÔ<ĸ.ÛQōŠfØGÃG ƒÁāųy.õtQgcrP;ûXē¨ÚvģĨmN÷FđСŨī÷H˚žˇ‘š2‚ž–ŨG]ˆxZÅÔ*¨nžķQWđQƒÁ`0xf^ÄxÔtĢŨív3֏ڲU’$žį9Ί–æ„2úļ~S M틚:‚~Ē>j~ĩé‡Ķ<ĸ~ÆG]ÃG ƒÁāŲy.5æL„ ÖCĩžŖÎ´5|Ô`0 ^"ĪåŖ† čƒe÷QᚤƒZ >j0 /”įōQ#G A,›Z„ŲŊĄķ„øŗ đQƒÁ`0x‰ŧ5A˒ÕG­›=ēp:MÛ'áŖƒÁ`đ>j‚^.›š˙xĪĩuĻį¨áŖƒÁ`đ>j‚^.ĢzrŽZDÔ>ę>j0 ŋÃG AĐËeņQ‹īũQ>dPŨĀG ƒÁāeō"|ÔÔ#ß÷]Įanj™1žĸÃa†áā*3lV1ļŲž™ĩVõ=ĪS3Pt=ąo jÚxԙ>tŪ3>ęĸ ƒÁīĀsų¨íˆëbŒ)oFOŊ5ŨOeÎy°}ŗ„îæ[Ī#p]Wf­ĨčŸûAĐ ~a<ęĻīŖ.āŖƒÁ`đģđßû¨a}VūgY(TԗZ}ĮlDĘđQīú¨•h¯ˇÛm*†ęۖw„ hŠŦãQŨ䙴S‹˜zt<ęv”ŧqu 5 ƒįæš|Ôŋ‘Ŗž[eFÔZ 5č CuûČQCĐKeķQ uAõŨ#Š |Ô`0 ^&ŋŋ:‚ąņ7,aųfŗĄ=R˙$ĻÅÛíV§IÛ­'“Õ |Ôô =ôQˇg3åûhžīŖîrÔđQƒÁ`0xNžËG=]ÔÆXĮÍŖą>tÉá;hÛĻ ĄåŨ––ģŨNîu¯2Æú€ WÉæŖ–âÁvŽŲ?ēˆ>j0 /15Ũj)ĐĨø™ąūxԖ­’$ņ<ĪqV´TŪėÖÅ­yše9ŨÄ15ŊJvĩŧ ™'6Ļ:ƒ ƒÁ āš|Ԙ3‚>X}Ô*¨.´ ē ƒÁËäš|Ô}°,>ę˛ģ uĶ‘gęáÄæ{>ę& ƒÁīÁsų¨‘Ŗ† –ŨG­Ęģ :W9ęé>ę>j0 ŋ /ÂG AвdņQ…Ķ9jø¨Á`0ŧ†‚ —ËîŖž]…´ :ōQ_->ę>j0 ŋ ÃG AĐËeķQ̞XĢøŅĀG ƒÁāe2|ÔŊ\u).;]ņ]PŨĀG ƒÁāeō"|ÔÔ#ß÷]Įan ÃÁUfŦblŗ}3kŨ1ų`‚žĢ‡>ęÛO{^§ĩ}4đQƒÁ`0x™<—zz6˜1Ļŧ=õâaēŸĒœķXŽZŨÁˇž÷\¯ ˛ČæŖ6rÔ*¨nāŖƒÁ`đ2ųī}Ԏ°>+˙ŗ,”*ęK­žc6"eø¨÷}ÔJ´×Ûí6MSK‚ž“ŨG]Ã'6đQƒÁ`0x™<—ú7rÔwĢˈZ+Ą}a¨~ēWYôĐG-/>ŨÚ6¨nāŖƒÁ`đ2ųũ}ÔAĐŊxp•%Ūl6ō-EL%ŠˇÛ­œō¸ß+DÔô Ų}ԃát5 ƒËsų¨§‹:Ëã¸y4և.ĒFa3mÛt!´ŧÛŌrˇÛÉŊ6…5ŊDvuU–77uWŠīŖŪÃG ƒÁāÅđ"ÆŖÎ˛ŒÂ`ŠŸëGmŲ*IĪķgEKåÍn]ܚ—[ß>jz‰ø¨Ѓ ƒÁ‹åš|Ԙ3‚>XvuĨEÕÚŠđQƒÁ`0xŠ<—‚ –ÅG]U•NßâéBx´Œ5|Ô`0 ^ĪåŖFŽ‚>X|ԃA5|Ô`0 ^,/ÂG AвdņQ›átŲ]yø¨Á`0ŧL†‚ —ËâŖĻ€úTß?ĸØĀG ƒÁāe2|ÔŊ\“|Ôå#5ÆŖƒÁ`đB>j‚^.ø¨Á`0üOņ"|ÔÔ#ß÷]Įanę.;Ũ]ō6¨nĻû¨e!-ŗŠĒÁG ƒÁāyųũ}ÔAĐŊxp•%,ßl6ō-EL%ЎÛm–ŨííķÖķz…='‹Ú| ‘˙—å¨}ÔQ×M"bi9˙‹ĘQÃG ^;âīĩbŒ)gˇÛ™Ū˟p–Ĩ‡ÃaŊ^Sû´<ŨŸĄ˙z{å˛äŽ?üržËG=]ÔƘ>ĮØXē¨…Í…˜3B†ĐōnKKēvÉŊVJ’„*ŒeÂ!úŽė>j}žDŽ.¨nĻø¨]Fõ5-Ži§%-EŽú5xYLá´ä<ËÂ0¤Û\w#~AûžįĨi*9Ë2úNí÷û÷Q•ˇ%opüÁā—ķ"ÆŖĻ ‚/šąūxԖ­(NĻ̊ãŦhŠŧŲ­‹[ķr7÷~lÉŋŧCôáķQwŽ NĢ+*ōAujø¨EŽúšü%‚jūdb5xaÜË֞N'ß÷%Ķmˆb`™ĸÛU–ZUÛRæēžøĶĒŲ>Ũ+Ë ņeŖĻ\Ņ ŧī—â)Ēwé÷*cō'-)Ž"•åĻoĨ,íĪųŧļėcĄe­Õm}7ŌÎ`?Ÿ8Î`đŸņ\>jĖ™AŦI>ęˇAõ4ĩČQĮ×ėzÍã$ŋĻÔPE•áŖ/‹{ŲÚ,M) •ėŦVôEÁņx$ %ąÚvŋßĶFZ~×>}Ũ( O'•ŠVu¨ žéûņÄu Ëgu ŌĒõšÉúԟ8ŽyM1wƒŊ?!īđƒ},´Ŧõ^tCv•įĪvûų&Ÿ<Čsų¨!ú`ų¨UŽZ]‹ōVŲ@ŽzĐGí˛Ēǝ׌Õ ĒË.G 5xAÜ÷S Ũet)°L’D2s]úvˆõcŽÚVĻ iÉlŸZ H•Özž§Ō΅pQĘméĮƒg!zĮnÛ\1ÅäĮÃ!“1y×ūXd‡Í}”^q}ŲļÃÜ\tƒRģ ˇ3ØOËņƒgįš|ÔČQCĐËæŖ.†Î“AucķQםÚĪS¤2¨Ļˆú–Ŗ†ŧîyŒŗŦËQ‹Œą*§ SæģrrÛ:†WYgÚJ>UÄsŋ]}åoTÛ Č{œĒ˜|ŗ‰ãhb,ûXhYë6Ānũ–ÃûeöķM>;0xáŖ† hY˛ú¨ )u_Ņt6æŖŽķęzŸŖŽŠ<ÎDPĮל?™5xilú¨…‰‚KËsëEŽŌŅŽĢļͅ÷Xå¨íīE_.×u$Sũ\|%õ:] Ûg)îŲîŌČcũ™˛…–™§mģhd¸ū`?Áāwfø¨!zš,>ęģĢŋĨĒuFõu›ŖAĩČQđQƒ—Į*[+ŨË@ĻŌđpŸ1Ļ0ûp8ĐR÷-Ķ?iËSkfîˇŋß—ķ9?Tŗ”¤ˇÛmeîO>¨üĮʃ}Ÿo™ÖFQ$ŗÜŦķxõĮžĒüæŖŪīO"m.ÆīōÍúƒũ|“Ī dø¨!zšŦ>ęBĘĖQWUeø¨ËžÚ>jî÷ púšGZŽ>jđ‚ø6ĩ#ÆŖÖ|Čz–¸7ļ†Ú–fWŒõ‘w…zûTx<Ök&ÆČbüŠÃŽ}ŅčíčM׌Q#˛PΊ+ĻĩjŦéú°ôgl{å* MßyŠ“eãįŽzũÁ~N<ļ`đ, 5A/—ÅG­_gÔ("Ļæ9ę˙ŲģVvÕu(øŽÄVV"ą•ČĘÚJ$ģ%Y‹ŦDb+++k‘H$w’Õ.B_tŋn=ëÛ7wNH“´ô1ĻÉĩbû4ßäUĐGMüˇpŸo™˜˜xBü>ęŌūü„ąĒī5įŖÖy¤=ķQŦuŦBnWi“—KĪÄLËt#7Žc.õÂ`|3|ÔzŸqgų0?OįySŖu~įŖö|˙ F}ČCĒ+ÚĻšøĪ`ŽĐˇZ ƒÁøZ û¨MŠ.R=ÆG=ķ ŖŪĨõ.͓C^”g0jú¨‰˙æĘƒÄÄOˆ§ōQ‹b<&ÖëXnįGmÚŦģ]įJˆë˜,nÎ`0ÆÄ€ēžĪŽƒ:īÖ¨ÕGÛUČUŖ>ƒQƒNg6͋Ķ9ŒŒFM51111ņTx*õđânøæzîü¨A}ņ<ÍšŗūvŽYä4™xîk% ãk1ėŖ.-ŸVN­¤zŒzæygۃNg6ÍķōFM51111ņ„ø˙÷Qëœíâ–LyQŅMōŊŠqËGŊzčŖÖĀ^/—Ë,Ë܆äĩhú¨ŒoÆĩšÛ4ét>ŪG}žlĶ\čô6͊ōŧŒčŖ&&&&&žOåŖū úîŖ6ŖvrPad Ս2Čß' }Ô Æ7ŖßG}‘LWŖÎoõy¤tzké´hÔˈ>jbbbbâ)ņķû¨ã8Æŗ¸ķŖZžX,°GúO`ä(–‰4ûļõčŖf0žÃ>ę qPwkÔ7õšéŖ>€Nį’§JŖĻš˜˜˜˜x*<•z|”v5Ō4M¯æúpÅ@›ąíĩĻĐō´E†Ąėĩ*Ģ ę§šÁø~ôų¨ Ŗļ4ģ1užėr9đQ[ēĻĶHsú¨‰‰‰‰‰§Æ/15žĩ ÁÖäܜz`+ä fŗH՛]š¸/÷ՙZæģ–šõ ƗŖĪG‹×üûfö¨ét–}ÂG}1õÎŌé]ĨQ›…˜éŖ&&&&&ž OåŖæš‰ ÆGŸēÖ¨ GŖÎUŖŖëŖ6ęę/7uL51111ņ„x*5ƒÁxãčķQkÔ Ã}ԞĖG}ÜíHˇûcaŪL45}ÔÄÄÄÄÄSáŠ|ÔÔ¨Œ7Žĩŧ7gųRm\cæŖŖŪđˇŨ§Hķĸ Ŗˆ>jbbbbâ ņKø¨ ÆkՏē5ižeĶ’ĻFŨéŖ6õy.ėmšĸe•ôQO‡éŖf0?ŋčŖžy†QƒvƒQÛU„aH51111ņ„˜>jƒņã1čŖ.;§ÎíŖ6Œ:1\:‘m€Q—ôQO‡éŖf0?_›z”z6ŗŒzgé´I Õ¨éŖ&&&&&ž?ŋOĪÍfŗX˜'ébąØnˇ_¨düĸįnI<Ŧį6öûũe0ūl ÎG]ÖKįb§ļœēKŖîöQzoõéŊųßõ…ĄYΉ>jbbbbâŠđT>ęņą\ē¤ ĀņxÁūl%Ģ+ö•‹Ãđl#\.eŅFƒ1&†|Ô6jR}÷ŠâĩįÍ.VŖŪ¤z‡méŖ&&&&&ž?ŋÚ÷}eԍ0\׎Ĩ(ÔWķ7›ĩįyØPĩeUžąŗ(,ųĀQŊšŦ“(Ģ(ƊŠËå2Ë2Ų ĪúeüÖ2o}ÔåíŨÄ\}ã}ÔéŪ0ęŊMËZŖĻš˜˜˜˜x*<•z|ā)šX,’$iķęõzĮ4ŌÍz-™Ûí ƒ…u)Ę3žŪËe€T7OSC­]Ũ[5j<¸ĩ!Ôæū“Á` ĮZíÂąTõCĩhÔûdkIĩIË""ú¨‰‰‰‰‰§Ä˙ŋZ`Uƒ%S^TtS7Đ70^ß÷—ËĨČPžī‰ļŒŸJæ|>oso´•Ļi°X¸ŠúÍą•–Ŧû9kÔđå]f0ūZ û¨å.Ô~9qŒÚ¯5ętŋ•5EôQOŠŸßGíF–eQ%I"˙tI¯beénāĶõz…!žÚnæĖō{z5Øûî ƒņÎņĐG­¤ētHõXõålé´üY:\ŌGMLLLLjcl ø¨Oõ]¨^Ž<חĮø¨EŖ>¤F–ôTkÔôQO…ŸßGŊ^¯ņ ll–ÛmEú‘LχtŨōQc×VĢ•dĒōŒw†‚7ëĩŧĸˆL-v˙jįúú-/3rŽc| û¨5ŋ&ՅjÔŖ|ÔĸQ[:”>jbbbbâÉņķĪGî$ĪÍęÃ˙æsŗŲ¨°Ü7רuß\WK•å5FÔĮą7›Ąr%ĖØûÅ|.}å|Ô ÆWcĀGŨG§;4ęNĩī]DŖNw’ĸ’(ĸš˜˜˜˜xJ<•šk&2oÃ>ęÛ]Č!Õŧ|Ô5>F]kÔôQO„_ÎGÍ`0ž?†|Ô*!ÛOŨ?Fų¨EŖ>$†NÛôt*Kú¨‰‰‰‰‰§ÃSų¨ŠQ3o>ę“ŊíÔŲw¤ú>ęšN#-ëƒ>jbbbbâ)ņķû¨ ÆËՏēĨQ—}uŸúb5ęCr°ĒQĶGMLLLL<~E5õmãÉcĀG}:UœWīEúŠâ8ĩaÔB§mē/UŖĻš˜˜˜˜x"L5ƒÁøņxčŖž íM™Ęö1ÖG}9Ž{pi›&¨>jbbbbâiņ+ú¨ŠQ3OC>ę–F­¤zœZ\†N§6ÅļQŅGMLLLLŽĄ ŽÜm¯vÉK{^=†}ԝtēōQ_ĩ6õÁŌi“b+0ę’>jbbbbâéđ{û¨]÷bå/Ņ?ÁŲ܅ÅÍēäÖˇā–×2hN>Ŋ§/éēÖlJ˛rŨĢõ0H%–¯V•ŦâX~ž>ŌEÍíQ›đKkhÔČ×ūŖŸB}‘ šŊZJ/z øšx6ëP5āĢwegYÉ][œĪũa‡ŒÆ :(pĖ*ŗž­ę=E@ĒÛ{š”Š_=†}Ôį“ ÕĨ3—Ūxõ%5\ēúÃm+Šcú¨‰‰‰‰‰'Ä/ęŖRŨ—j4|ČFŨ˛f`ėĸr9°°GWmÖÂ8>JDļÛmƒjē>g<î]'0*Q<÷ũöŽ(ųÚųĐG Ŧō˛öS|ú°¨ÁāÃ˛/¨MHėė‘<ŪmŨŨD[éė›jüårvãくēsŊą>j˙ FĶã1Í2¤Ĩҍã’>jbbbbâéđßņQƒŲ♆•z 2Š$\Qˇęģ~Ū÷y ļvÍøŪE~GˇewÚdx8Ü1H{_:û&NéÅbnl÷ž~Á‰ÍxĒöQģŦÚyCq¤ÚŒR÷ĮŦú˲Â2jú¨‰‰‰‰‰'ÄĘG=õjĒ"-ėŠrcˇŧ+/ˇ›ĢčÚđQŖÂŌžŠ'•´ˇ!ˇžĒÛjI4:ÆGíöķ+uŨ.ˆ=z̝Rj>H/(PgënÜiÔŗQ5¸´!íPŽŨ9Ô¨_=ú|ԗËų&S—Í?Æø¨=ĢQīŗbĖ%-ĪgS˜>jbbbbâéđōQ_.xRĢF}ĩÔ.MSw^Ž ļ\ t‘įę7Öŗ,s=ÉnŖōŽĄ°qT"ŧ¤]Ä ‹8Â"ĮéļøT^uéŖVŒ ˇÛ-R×G­†ÕīŨvëžą¨ųčĄī{’o$ÁũžsŽ‡>ęvßP-*—9÷´äÂ9ōôQŋA<đQw’ęą>j0ęëūXėŗ2ąiq:GņŠ>jbbbbâ ņ‹ú¨GFc>jyAO?Ń”L^-OVÖ ^'3rč´ō2*DĒL˛­$ã`†–¨ŖšÎJ<Īä€Ļ‚Í “ėžëŖg~ė†ĸ†Ëö\Z`ĀG-íē3Ÿ¸û"{.a°^wĪG­s}¸ÛŪĪõŅė[ēßŖÂššĪ÷ę6>Õwž1>jΟFôÅBĐoú¨‰‰‰‰‰§ÃīíŖŽōūũDÆ×bĩ2/…ũTmœú=bĀG B힟čžĸ8ĘGmėX×Äpéŗ¤ÅÉ0jú¨‰‰‰‰‰'ÄīíŖ<ģÃ0¤úũĐ5$¸fâ{Ä(õék>jĢQįpiIËķU4jú¨‰‰‰‰‰§ÂīíŖî‹ tyƒņãņUõ顏zæFX:-iqē†Ģ }ÔÄÄÄÄÄâ÷öQ3ŒIbČGŨŗŧ ’Oø¨:}1i~ąõϤš˜˜˜˜x:ü—}Ô ã—âkķQ#c„ÚÕ¨/VŖž€QĶGMLLLLjƒņ{ŅįŖŽ5ęŌҍ {ã)¤XúÜöQ FĒ5}ÔÄÄÄÄÄSáŋéŖf0ŋ|Ô=ÆãúíŖ—–Ôø¨cú¨‰‰‰‰‰§ÄīíŖnĖGĮ‘.#ŌWūSõãĐĩWElĪČg—_<ģ9ˇ5 {ÖvqķÛ> î+é..ęâ–Į7E‘7›ųž™ģģorÜÃ.sãc_>{܎Μ՜Üã-cĀGŨ9ˇš˙Ø[ĐHĩŌièëš>čŖ&&&&&ž ŋˇÚĨ‹f9ļíÖ]!q¸üȈãØíđl6sW9ŽØˇËgãáļčUģAčĒ…8dՕĢåĖišöuųrÚxž'dø =×vqžuŽÆČxéöQ×ęt}7**R=ÆG-õîhč´¤9}ÔÄÄÄÄÄSã÷öQˇU_o6kŦ(WéŊZVEQcŊ?Đcŗfâl†TÖ@læ8Ž$n9Q$”5ƒi˲€BA;féŗi= Û+!Ę:ƒîēá՞6WQėØöãð-†î—A_ɊáÃĄ‹$ļû|­gųFĶčŧJÜhH¨;˛čä E‹$ž_ ø¨Û/$š˙Š.ēßG-tzgĶ‚ķQOßÛGíj§ģ›°_P8c%€ÅÆā–×2hNW0•Nˆt]+Ģ ŠB\ÅbĶjqm`eī ”I’(n÷°Ņzš6[ĮVØépcPĐØėém‰đŲŦŧWƒA}ņíkOĐķ>›‡,ܖ_oí…Û´v{Įōsüá.œŖĄuĸBw ÂxƒčķQëäyww¤šTķQûĸQëßMŖĻš˜˜˜˜x"üĸ>j!Õ}ŠFCVŌXŪ¯?,# WéÕÂ.+Ønˇ 36XnƒF"՜­åĀWK;ËzĩnĨ }>ęÎÖ,"m…áŖĩyKyaŅøēepqë‰CqÂ:=ÎÍŊ¨›6Ũé0ŋö %Ü~2Ū#ú|Ô÷tZīH8sš55ęõÎÕ¨O×%}ÔÄÄÄÄēâWôQ;úrÁ3]LWĢĩ* Wۆ[,qfųĖÄÍÕļ AžW#ą 0üŠØ*đQ°¨rƚEģ”õĄF=“VĢÖg5töŧ]L€Qŧ­jŊZ­¤˙ŸÕ¨‡÷ĸķSaŒ ĀÆûüH>Īx•čķQwjÔEEĒ ņv}ÔEŋzkÕiIķōFM51111ņ„øOų¨ą§^ÍÜĀ-…]#UnÜŠôv6weÕår ÂĐĐŊ‘ãžŲIžīŲīƒÖ‡4ę.uŊ­QێÍņ]kĮ4¤WM)Ąo#^ģ”vŋūÔÕ¨q(0–‘.i?ŠQŋwôų¨•NëŊ¨¨"^=ŌGŊ;^”TĢFM51111ņTø}ÔãŖĄQãI­õÕrË4M]ŌÔöĢunˆēČsĩHĪíÄq7'3ęqmŌW;1räíE‰Nũvá´čâMíâΝÖGjÔëõZŪ1Dęú¨ s>÷ĩcŽ’ Z+ųs}uũŨuú¨ÛMƒ„ŖōŗõąkÉ ĘZŊ§úũbČG]vL'¤x¤ZéôÖú¨—ôQOŠ_ÔG=2ķQƒšē¯%Ŋ˙ÜI3°ëāÉĘēA)e† åŸfŽ˙ēĶ;IÖķ\×°+į^{fˇEcC´îŨˇ>ŌGíĖõąlëÉfĒGvë{— åXuš@tŽk—ŌŽs}„Î\čŋį™1•øžŒ°ŗ‹ų\†3œëãũbĐG]JČ=§fĶšL÷ŅШ÷ÖGļ}ԕåãRiÔ}ÔÄÄÄÄÄSâ÷öQGy˙~"cdč|Ô?%įŖ~ĮđQßŨ… ŠV‰Ú”§š˜˜˜˜øņ{û¨BÔÔg ö/vÍÄõOÕÆ5ß2}ÔĨD[Ŗ.ú4ęļ:ģÜiÔôQOŠßÛGŨA, Ōiã—ĸßG}rī3:ˇåÔyųyõŽ>jbbbbâ'ĀīíŖf0“Ä€Zī3î, Ķ"S×¨951111ņķāŋėŖf0ŋũ>ęúTT¤ētH52>īŖžXš>jbbbbâ)ņŸõQ3Œß‹u}Ÿ)u^kÔyŋF}y¨QĶGMLLLL<ū›>jƒņĢŅįŖVē[ĢĶ÷¤ē(Fú¨wõk‰âŖéŖ&&&&&ž?ŋē^ø{>›Í‹…, ōŲhĖŪ<˛$Ž šŋ-ƒÁčķQ—ænͤĶųš>jbbbbâįÅSų¨ĮĮrčZ!Įãûŗ•4VYŌķŧår9~[ƒ!ŅįŖîÔ¨sGŖéŖļuõg5jú¨‰‰‰‰‰§ÄĪīŖö}ŋsņžkĪ }W3ÅņdĘBŪWGy–õ%8Ē7—)‘euEYiQkŖFÍ`|6ú|ÔĨüÛ qPkÜ4ęhŒúL51111ņ3āŠ|ÔãOÉÅb‘$I›W¯×k<ϐnęĨą?>>ļÛ-žž,čzŲĸ3ãņŊ\Huķ45ÔÚÕŊۊ45jãŗ1äŖļ4ģ1užė“>jWŖĻš˜˜˜˜xJü˙û¨EV5X2åEE7u}ãõ}š\Š %áûžhËHņŠdÎįķ6÷F[iš‹…û‘Ēߨ[iÉöļ_ß[ãOÆ Ú5{Ôt:˚õõš­QĶGMLLLL<~~ĩY–EQ”$‰üĶU+KwŸŽ×ë( ņČv3g–ß h×Ų—Ã`0†cČGmī…ŖQįŽFđQßkÔ}ÔÄÄÄÄÄĶãį÷Q7ŊõęD4ęķ5¤š˜˜˜˜xRüü>ęõzĨĐ`¤Ûí6Š"ũH&ĶCēnų¨ąkĢÕJ2UgÆ3; CÁ›õZ^QDĻ– ‚ģīv€5ƒņŲčķQ—–Oߓj•¨3Āņ>jĄĶH ú¨‰‰‰‰‰§ÆĪ?5ē’<ŸĪgŗķšŋŲlTXî›ëėēoŽ2å5FÔĮą7›Ąō4MåSėũb>_.—Zž5ƒņŲč÷QwLšgŲt†äNŖôQ'Ų\zŸ›´Ŧ5jú¨‰‰‰‰‰§Â¯åŖf0/}>jƒĖāŊÛG˙ú¨kF}VR}͍éŖ&&&&&žŋœšÁ`<Œ›:w§Îû”DēúË.VŖŪ”ôQO‡ŸßGÍ`0^.|ÔBŗûHĩëŖîШũjÍDią|XúŽčŖ&&&&&ž?ŋšÁ`ŧ\ ÎG]ÖKįb§ļœēĨQßų¨/ u­Q 5}ÔÄÄÄÄÄSáWôQˇ—€a0O 5î3õ|ÔÕMĻ&ÕÍWÅGō>j€$Ģ,ôQ?ĻšÁ`üx4}Ô§S—F]8u~§QŸNĸQ§4ęKB51111ņāWôQSŖf0ž<>jҍˇÛíídČváØ?”Qo“ZŖî™ēš=O|ÔÆGÍų¨‰‰‰‰‰'ÅīíŖļkŒ˙ŗÉ?ß÷â8’Ä@ų¯´bë—ļ|ߏã¸Ŋhã§BgĀvû/ø;ÕēĘ˛,kdîvģ™ĶN„(Š<ĢÕĒo§pūl6›Ģ]#ģ†áúŠíēķ3^7>jœ^•įK-P_åũ|ÔV–´ā|ÔÄÄÄÄÄSã÷öQģÔNÖ[t—n.?>[ ˇō0t•Æß[\5ë2‘Ah‹8\˛DÎÅFšĻíōČ/í*“ž72ü…U&ĩ]œ’|vsÆŗEÃG}šœA Sđédˇ•°k›â6ę4N9l”VFŖ—nø¨EŖŪYuzWiÔôQOŒßÛG=k1Ro6WœĪį|ä¸jđÕJ¯QɂŒĒš‚Ė/ d"ÕeÛĘļRJ­$Šn•`üî:ŗk5ĒPŒ^‡ƒ”ĸڛFŨ葏JP§4Ą Gj¨äġ Gĸ$°WcíķfcHˆÖ‰>>>t_Ā“wģŨĀąÕ"ëŋ̊.ĮPwŋŨ7Ũ}CšÛ^풗4öŧz´}Ô8€đÍâ ß[æ ~í¤{œŲņhéôÅú¨×ĸQR]4}ԖN_„TĢFM51111ņTøŊ}ÔŽÆ{9ŸÁE˙gĶƝ˛.šõ-¸åĩ š“O¯ÆÆé FēŽ5ۆ’Ŧ\÷j= R‰åĢU%Ģ8–Ÿ§‡tQs{Ô&|ĀŌÚ5ōĩ˙č§P_¤Bn¯–ŌŖŅ–Åŗ!X‡¨_Ŋ+;ËJîÚâ|î;d$0^ĐAcV™ ômUī)úRŨŪSĐ*ĘÔ¯íų¨…T#Îũ!„ú,ĩĄĶŽÚ7õņ F-iŽ5}ÔÄÄÄÄÄáõQ ŠîK5>dđa1c•Ë!€…=ējŗÆņQ" °ŨnTĶõ9ƒžēN`TĸxîûíQō‰ 5ķĄXåeí§ø45ôaQƒÁ‡e_P›ØŲ#yŧÚēģ‰ļŌŲ77:Õxp.÷h0^1>ę/ÜŖŦF}UËĮĄŧžZé4Ōât]ŌGMLLLL<)ū;>j0ÛŨn†•z 2Š$\QˇÔÖŽßģČīčļėN› ‡;iīKgßÄ)ŊXĖííŪĶ/8ąO õîQŅj#>jũs}Ô[ĐéãŲĻFŖŖĻš˜˜˜˜xBü§|ÔØS¯Ļj ŌÂŽ‘*7vËģōr;°šŠŽ 5*,í›zRI{[rp{áĢē­–DŖc|Ôn?ŋĸQ×í‚ØŖˇú*Ĩæƒô&IŌŲēwõl”F .-wöGŽ5ęW†ú ÷¨auĨQ/[GŖĻš˜˜˜˜x*ü‡|ԗ ˆœjÔWKíŌ4uįåjûĮÕą@yŽ~ci=Ë2דė6*ī G%ÂKAÚÕH ēˆ#|ļžnŨŸĘĢŽ#}Ԋąávģ@ęú¨ĩ°úŊÛnŨ75=ô}Oōą/8s}<ôQˇû†jK3ųƒ™sOK.œ#OõD‡ú“ØhÔį‹Z>>jGŖ>[š>jbbbbâ)ņ‹ú¨GFc>jyAO?×E%“WK#Á“•uƒ×Ɍ:­‡ŧL‡ ‘*“l+É8˜Ą%ęhŽŗĪ39 Š`ŗÂ$ģįúč™ģĄč†á˛=ׇđQKģîĖ'îžČ^ KŦ×ŨķQë\îļ÷s}4û–î÷¨pn§æķ=Oízä9×ĮÄ5žeyMĩŗĖĩU§wY­QGôQO‰ßÛG=åũû‰Œ¯Åj—ĩËåûÁų¨ß#ú¨sû+ŧ_įYģŒÎGM51111ņķã÷öQ„Y”- Š…~?tÍÄ Ž™ø1ėŖ.ōÜū"cæHG ܸ˙”#}Ô}ÔÄÄÄÄÄOßÛGŨAčō" ãĮcČG]›õzš Âp™Ļ)RāÍf|ˇüm>ęĸc>ęmũZ"}ÔÄÄÄÄÄĪ€ßÛGÍ`0&‰u˛ÛÅq,6žĪįÜžųkį–Ųšå]šķQ?9ūË>jƒņKŅįŖ[^Y:Ģtē´“ŪČ+ŠČ.-åGø¨ok&ŌGMLLLL<-ūŗ>jƒņ{ŅīŖ.3B§Ģ|!ÕČ­rl:ŌGm4ę3}ÔÄÄÄÄÄãŋéŖf0ŋ>jŒâEvķ‘sžwkč|ԝ>jĨĶ2×GH51111ņ¤øŊ}ԍų¨ã8ŌeDúĘĒ~ēöLjíųėō‹g7įļFaĪzâ#ÃN>vÛGÁ}%ŨåŅ%’$qË㛏ĸțÍ|ĪĖŨŨˇ šNîa—šņą/Ÿ=nWgÎjNîņ–1f>ęaü`>jK§ú¨‰‰‰‰‰ŸŋˇÚĨ‹ā‡ÛíÖ]!q¸üȈãØíđl6sW9ŽØˇËgãáļčUģAčĒ…8dՕĢåĖišöuųrÚxž'dø =×vqžuŽÆČxéx8õC<ėŖļęôY}Ô!}ÔÄÄÄÄēâ÷öQˇU_o6kŦ(Q-6Q+ŊWË*Ŗ(jŦ÷zlÖLœÍĘˆ ÂĮ‘Ä-'Š„rŖf0mYP(h§Â,}6­‡a{%DYgĐ]7ŧÚĶæ*ŠÛ~|˜_Æĩj¸_}%+†‡.’ØîķĩžåMŖķ*qŖ!Ąî8Čĸ“7u.’ø~1<õ<ėŖNēæú š˜˜˜˜x*üŪ>jW;ŊœĪ Â~AáDŒ•ƒ[^Ë 9]ÁTV8!Ōu­Ŧ‚( qˆMĢÅĩ•ŊƒR&Iĸ¸ŨÃFëiÚl[a¤ÃAAcÛŨn€ôļDølVŪĢÁ žøöĩ'čyŸÍà n˯ˇöÂmZģŊŠcų9ūpHÎŅĐ:QĄ;aŧA ÍG=đQW25}ÔÄÄÄÄēãõQ ŠîK5 °’Æō~ũq`Y¸J¯vY1Āvģm˜ąÁr4Šæl-žZÚYÖĢu+íķQwļ>`il+ 5¨Í[Ę ‹Ā×-ƒ‹[OŠ;ÖéqnîE}Ü´éN‡ųĩg(áö“ņņC>ęų¨ĪôQ?~Eõø¸Ķ¨/đ[1]\­ÖĒ$\mny°Ä™eä37WØ2xy^8pĂĀđs¤bĢĀGÁĸĘ)kíRևõLZ­ZŸ ÔĐŲķv1FņļĒĩŦ¯áŠQīEį§88€÷ø‘|žņ*1āŖÆÃRđf'ŋØn?ī–ī÷QßkÔŲ™>jbbbbâÉņŸōQcOŊšš[ ģFĒܸSéí lîĘĒËå2I’†î÷EČNō|Ī~´>¤QwŠëmÚvlŽīZ;ĻųvŅē¤o5ômÄk—Ōîןē5Æ2Ō%í'5ę÷Ž>5Ž5ųåčÃđį*ŖTų Ÿjų0^=đQgĩF}ž†ôQOŠ_ŅG=>u’Ü4ęĢå–išē¤7¨íWëÜuaWI–LYëíZMws2Ŗ×&}ĩĶ!GŪ^”čÔoN‹.ŪÔ.îÜi}¤FŊ^¯åC¤ŽZ2įs_;æ*É ĩ’?0ׇņQ×֚NuģiđŌN8ėžAYĢ÷ôQŋ_ôų¨‘n6kųũ×rpUĘo1¸âÜōĒQ§y‡:1tZŒį‚>jbbbbâŠņ‹ú¨GFc>jPSWøĩ¤÷Ÿ;iv–m=ŲLuâÂn=`ī˛ĄĢNˆÎõqíRÚuŽĐ™ëũ÷<ŗ#fĸߗQvv1ŸËp†s}ŧ_ ø¨/įķ*Ž}YHĖĩéŦĸX>ōQ'Ų\zŸ›´Ŧ5jú¨‰‰‰‰‰§ÂīíŖŽōūũDÆČĐų¨$JÎGũŽ1<5zøŌ—&üˇļCŨF™>uͨĪJĒo5}ÔÄÄÄÄÄá÷öQ„¨Š”FŋvÍÄõOÕÆ5ß2ĖGm]@øęÁĢ‘Ū%wĘ ĪG "]ũeĢQo°‰üÖvšTĩ=‰jALLLLü0žeVÛ96ōß[Ŗ‚`ąXN3ŋįŖ>Ų›OšĻ7:}_FįŖˇãŖļŒ:ģXËĮÅjÔ×pe4ę,ËöûũétB'kū11111ņ¯byîā„'QÃGmķpûÎ5ƒÁøŊĀŨ#Úf~)ŗ¤ē"Đ­2VŖžöų¨kÚҍOfģüx8$I’įšŪߘ2eʔ)ĶßNņÜÁĶĮX>ōϚtˊÅĪDŖT¤SĻL™ŽLqßX|万|áW3ÅFŖîņQ_뛉wĩq}˜æpO;XRėnALLLLLüĢXč´;ŦĢŗ<ŪåķM6ßäķĩM‰‰‰‰aÜ7*:ũw=šs}´|ÔJĒÕG]ĢŨîiĮã1Cؔ˜˜˜˜˜øW1ž;xø8âĐôžnbbbâŌņQ›ų¨[>j3҇: Ō¨§~˛˜˜˜˜˜˜˜˜˜øŠp4čŖļSį ŠžČš‰Ī0 &&&&&&&&&~|7ׇãŖēZ‚ŧŌ¨/ęŖ~ž111111111ņ´xØG­tiQ¯™XN= &&&&&&&&&~|įŖVÚø¨}ŖQĪāŌ’æĸQ?Á(€˜˜˜˜˜˜˜˜˜øypĶG]Üų¨wŲÅŌi“§ë’>jbbbbbbbbbâ{<ėŖŪ@-i~ē,ëcúQ111111111ņķ`×G Ŗv5ęãEIĩhÔvÃ;fžßīƒ ˜ÍfžįEQ”g2ŗ, ÃP2d6ūŽŗÜäBÃ.™f~ūĒdđ<# bbÁŲņˆûn/˛†žã5w-pˇĐōķH Ēsˇ‘F=UNO[¸-ĮqŒúqۙR ŲuÕ RŌ6Æņp˜üX?3ƈĮ¨›/gNû\ ‡á2ŲíėŠ-žėōėpˀ`p!âĒDĪQ¸]6ŠežāE’`&Q}Íjy÷zÄe‡ō¨°ŗo¸äĮ­ŗŸh™æ.‘eˇĢÕŠG.X¤ČLĶ}ŖNÁ¨sa;JPváãccJŪīãĖtūļŋUsOpŽũoû[" Y†ī÷ĪĪnƒĖũŪ‘_ ũõ`s{*~ũÚüņķÖŨŖ<Xy áéƒ2í}ųņëëŊņueų¸TuôČGk6;U]!ųø6=;ōBZT¨JļëA&NBŒū䋖L|YV0§.2e„ˆXGgĢUŒ­|ߓŗBFĩĸø…ËĨÜŌũ§‡ ĩD,íPna•„ÅbŽJēŠ&<ېŽČlÉš-i4“Ūã@ü˛DwƒÎ2 ĩĄt„čļ"ŨVÜm1œÔĶĪ\)ãVbŽO+¯nVR':ųSûKüŽg XĮņxĐ|ÜEA ô4sīz/Å?å×ßķpģŒ[ĢôU÷UšËVÚžŨA°@áRoōĨ9inVä8ÃöYžn>ļ*ę_jp™ˆfÛ(ƒ} íĢS¯Ų†2ī^_mÁC ūL?‹úÚŧSĢÂp_˙¨Ôˇīč<î6}e´Îšh5Ÿŗuž%Æ)Øū9æ|Ģ1ÆZųBŧqNĘã^ŋ‹NŽņøüė<æ]ßģ‹QíA¤O^›˙Īy‹ƒ#›œœĮĐo__ī?áŖ.ø¨¸9XAΌã>j•jQųđo?>ōzÄg†9­zđáÄffd\ĨŒĐņOÁBxJۖåf+Ô&ę7†QąlŽĮÛíGiĩD3žĩõ›ŸÎĶ´¨ÕŠĶŗĪŽō–iJÆfķ­)i6ƒ§Ã­dÜRˆßãd°bHGW):ŦĪáļ"=Ŧb\f¨BĨNähTԏöUƒ|WŖ.ė?ķzÛį9nÄΆqfĮŖĢ&‰Đ§įĒŊCn\ÅŸŽ×+ÃFv;<%cûc1q5ušģŖmËTeīĪŽŌ…;°Ū{‡û âaE­ģ|äԜ§(n"Ė]tLÕËvũzÍē×Ŗ¨Ęz}šåå7ŖOõû0žeŖ<.j"PÁ…ŌšV†øY靺-íŗûëiu(ŸoŒqŠgÂvÛøŽqJ 'ÄG8=vöÜĢÉa§šüŽ€Ø|­õšÚæÃ}pš›_Ÿ ŨÛZo^ˇ‚ũđÚüÎ[üĪģSū§ëëíņ÷}Ô:BÁ!×_~)_Í܎ËzDY•´Ŧ ŗÉ×᧌ЏĮŖ`Ü Ā8ėN €'ÛjnßLÉ\JšŗBōÍīāŪĩ MĘënüēNËaǁ0@õķGĪq ~ulî*ÕŌ,Ķp3ŠmŖs„^>ōQ§‘Å‘üdŒģ:j=5ŽĄÍļ|âø¨764GžÔĪp܈Ÿ Ëh ææ‰GĒđ=WqU?ŪŲ;déleķ Ŋœ§­Üm&nŅž§m‡“}ŖE|(ųxˆƒ9<ės!Ô¨ĨčáÂ^e*§ˇˇ­~ÁšũŗēBËúšmäKZQŲV6›õÖĒ7#û9ŗá; Ō9ūâ¯F`/ÚžŲ˛žÛhžöPûÜȝŸžū|áÔ2n^ßĮW ŋ˜€ƒŧãÜÆA–39•įž'ŽšÚæí6¸æŸīÜ6*oŪŽÍß>oõ˜ÜJ;×ø]™ŊžŪôQīú}Ԋs;čS¯‘ŧ; ™2R —ËĢQ#ŋsÄWŪt:GčŗÚ:âŪđg]žÕúËũWŸ&?ŗW%žî`ąHS9ÁĖønĩŠÁa‹…ØŪ¤Îj[û'uڒĢ[ɧ˙ކí]eÜķ°ąíg}Ôĸ`āDŠh„,36Ŧ¯šŧžj–MÕ+ˇB.Ÿ†ŗúŽņŗa9“d'*1ŲōôwîĢ3šŋUwČÖyۉ[4ģyū/—čÎûŗCũĖķ ONtYíp||lĖO9­27 ­ëzÔköŪį™l6úžÂm[ũč3ũD>ČĖ˛åŖŽUU‹m…°´ƒšŧõ‹XģĪ 5îmOė_ÁƔžb÷-´ÚŦaŅØBä ŠØoQČ;æâķėTSGžSārÉ7Ū§ŠØwo[ü_ŋ6ũŧ- GŖļžđʧņ{××ĀŖ|ԟ˜z´Ú€ģõ˜ģP-hĪkŲÄõ¯zĩōœ; ‰hԍūĻ(zúYé~|›YÕiíEßūîÍŸadDüS7ˇžˇž;ßÚ–Ā M~F‘üãá \wL쏗 ņĐĢĻ”ĢĻåC‹ãXf ¨ŽU˜¸tn°ˇáätßĘā[¯Qc[÷זN|ûí/ŋũJ螇f†kx^i¯‚'+ÅxMũŸ‚•Ÿ7ō­ĪŗŠÎ}͏šZ×ßúŠĪ87锑°Ŋ­vŪÍo÷šáŖ.ūžē…  }CĐ0íí6°NŒ“Ŋ-ãđŠ‹ĪUSÛcDģˇcn¯Ļ…¤;ËËŲōåkķ˙9oUc?=đQ˙Ėõõ°Ņ¨Oߚ:4ī_$ōęĢ*Ī8ąņEČ´ø:šs}EcħuZßZDėęõĢû:2ͯęã1Rĩ= ;¤kŋ>ŝY2ũZņ0™lZŲĐōdSŌíæ8ĩÎfɧ˙Æ}÷jy›'lTûÁÜķ°ąíÎŧ–%/Ąs]p{qĩg$†ijÎÃåŪûËxČÚürįĒŅmíīé•g¯ŠQ?Áq#~6ŦĒ‘¸yHuŸ_=+Ģ;dõūHŸ‚§¸yn)TĨUĖļvá~ÚĮ‚ļĻXn×e!†Õyũ û]™Bį"°CZq}?đŖšëkĶV•ņđ:tũōØŲĪ(˛ATæfŲúEIî YvDq}´÷Q´ëÍzŲš> ĶųmģΎ_ĮlĄJŖ~‚sėÃĢ8Öi+˛ĘĻ[ņ ąŦ7pi} ”☚@ë<û†‚dŠëãdo}vHõāÂzW<ëOęi=lI;+ˆ}ŲäyFFÄ?ˆ‡TÎ1ߜŌwķQlkn_öÜ@꾝o‘'ɧg§—Q/4<ĶŗÖ|Ôč’zöčŖ&~ˆU5˛fĄÛ/nš‡Ĩ{‡,OpįlK …JfÖMœŲ:چ÷R¯/-|ģß.8įûöˇČužÜ¸,BsĨT×E§ĪSŽëâūúžÁĸŗūÎ~ ]Š^Š0SüÚyŨVú/ŗq–=ßWîĖG-ž;+…€Î78Úũ|cl8íz­ŗvËĢ…Ræx<ę›V‚ŗúįBË |PGu8trŒÎķŗ“Û4ÎũŽÜī=xôëĖÃkķ÷ÎÛŌŲŲ˛žīũÁ|Ô?q}ũėø¨AǝÃ>ępĐGũ#x¤¯‰˜˜˜˜ø91ˆMõ{úsô‡˜X19ņīá>ę–FũÛ ŸžPbbbâ×ÅV4 øë ņsbr âßÃcæŖŪŨ4ęĄų¨?œ!˜˜˜˜ø9ņbą¨VĶxŽū709ņīaWŖļ>ęëŊúƨ‡įŖ&&&&&&&&&&ū›8žĶ¨›>ęŨ˙îŖ&&&&&&&&&&~-­ĖGí¸>Žģ¤z¯ÃxŊŒÖSâhŊ4xD+ü-Â8WøCĻMĨ¤ų‹VüŒŽ[éíSSŌ–ˇ[™mWQŦøūĶ kē‘VîÛúpĶx"ė¤u랇Õ1Ŧū‚0v˙‰ŨëÔ9L™2]™Ë'Ō×KFTōWr•ĩ>CšžÚųŌxÚTūÂ*géäßÕëķúž˙ˇĢģSīŖ”īŨĮ‡ũŦrš=iÜc›õˇ÷ą§•Û1\=:ž­ãĶ_gß§ãSīË&rĶÕ¤ø]Ķī{ÖõāĪÖķŊīåΞŸ? ëÛíĶâ|mjÔsëãdé´IÁēķō’Ÿ.Åé Pœj|jāŸJģęŋĩ{11čÕ9+äī”Y,™…-†ņūN’^œôÜĿΔˇ[™mĨ†syKĢO̚O>_ëļēZüYŦŊ}€/å-įÖ7wīęŖw’Tū4'/OEšŋĶšŠR9bĨ{ô˜ŪŌōÉđ‹Ĩ÷įØ.īđ/ĨˇŽÜ^2Ā­üVjļ=!-lj7qđ]ZViŅJĩlĢG žę›Įđ>-õčõs[g}O8ĩj¨ķËf™ĒæķEîąÕߊNŨúģŽLq—ŪzŌĩÍ>ÜÛ} õ÷ÕĶįģģį_Ú÷Ū'Ā—úšÖŋ”úŨ}_ž§§QųwûužœēĶëhüéwÚú>÷ÖųđûŲsāÛįa/ūbZķĢŦŧ:o&^-Ŗ^€Qī’}šË\FŖNzĶËÎ4į{¸ģūsŨCōˇ‡ū>Ōë$ßė͟üs{(QfŸŸíęęfåuøOŽ@j69īŗ“ũ+‘&Į˛ú§äWŸĸ3‚Īûü‚ ™?ąŖ›Ú¤N§ū˛‰;RÛĪQ¸jĨw¤÷{ˇÃ!:”)ŽŽ[öŖ—døÛ"ĶĻøÛŠŨ!O$=ū×Ūšrš qü+\Y[‰Ŧܯ°˛˛ļreem%˛‹D"ąH$˛‰Dö&™¨îūĩDū7Ä/×}rČrļr0¨wG4oĻũĖ)Ī?ŋm~K6˙Ę$†jƒĶĖîŖŠcŊÜqëĐ0čė!°ŗĶŲiĀi;;]÷ƚAã4x”UÆŊ8ūėLõˇ–S|t÷ęÁv •ŖnpŽú–;9ę@vz¤œūmđ›Đ˙3;Ŋ ¤ÃuB+•uĮíNŖC܌ƸįV<Íą U=´ŋÄûsur_#; ŲGâîl: œ)8/ĸũŅed2ęéEhízS¤8&–yī§{ĮÉKČēš?jĄ1?ęŖŖnû'Cčäđq:Žˇô|ūúŗÛą’ļãõ'Ąšö¨ŸûĄēi ÕđāG'ü°rÔ <õ‘AŽîga¨ļ¯­<8­29r UhŨy3ÕĒÕ)ŗŅ÷đ_†ä!ÖGƒ_, 5ÂiœŖ6´'GíÍNĪQ˙VĻzĶīSvZcŌHÃķ ™ę2čgúnĻڃ¸vŽēIK“ŖV|Ôpß'5ĐKqÎ uĩņe’}UJ]PŸYåœ ôŌ?ōJåĢ (jô%7^UĻ_RŅúVûzlčŪE›%Ę Ē^:ŗęb'j`ÛAîÖā4­īš˛Îå›˜ë‡ũĖJĨŪŦķŖi“y6Â#Ķ5ôA‘8x&Иq ĸrH×ÅãunÖS¨n7¨žŌŪ‹¸ŅĮž%{ü’ēcxĻš€ũŦ<;ÍāyˇÛĮëõšŪķË5=]öÉ!9˛,žü׀ŧ›jëzв:(ĩ§SblÁįÂ=ž‹yĸ7č¸aíe$q<Ų?ņŪ1?ŦOĮW6ą˙]K¯ųsĮ­_aü캆ˎ<ęsÆĮ!ÜfĖ9ōŦ]ŽS?¤Ŗmēí/KÆOÜ8YįŸoæ˙ŽŸ÷÷ôįœĮQcŽk]˙ßk[ž ›“ÃÃéËårģŨŌŦ`˙TŸÎ_ŸĮãęlđØnŸė@‘ąÆšĸj ¨ø­€A55áŊB÷ČOp›‰ûaO_ũū¤Ōc„(YŅ“1L¤ĘlēéĻ~…9Bf ԃ[NZHB*gĢëã:öģ> endobj 35 0 obj <> stream xœĩWÛnÛ0 }°āãö2[w(tínčAS`ÅÜDkƒ&Vá8Øú÷;26[•dK6 0-™įˆ¤(RQŠrR–œ#åHhKĒ )%Š’dYÆW—“¤1Ŗ%™“Š Z“Õ†œ°¨+Ąé¨°I*…$]Č dIBähHcÉhLFAZĖcm%č+z [TNÆ@–ŽĀ( @io™G˙‹ ™ŊHN˜ÄîE2dˇÉÜYŠŊX>2IšÉĸjÆoúü7‰ŧC&/Õŗ”›'Œč1ƒg€˜˜"ÖÆN8‹˛(Ž,$ ÆĄ8v°`Ũą$íßh‹HŸ’͘ô‰ØŒIŖzĖéģ0~Hâ’9o–k~NŒ^ c˙t3ŨsˇÉÍĸņŠe˛nŗÍ;Ã{oŠĩönqÔŽ–[#›<Â[´ųēí“ō9fCd“ųiÅ6ī,Ÿģ>ļ8*ˇž§Ķė {Ú6€s ßŽŪîŋ?=2]ß6cŌÕl#Ļ_FĘ4fũ6ŲÍĀtIØ#w5Đí°˜ÚŖw˜$FēsF‹™¯Û¤‰}gāæĄ9Ë 7ÎđxQí÷ ËĮ˛ĻcMĮšŽÉgŽã6ã §lo׊ —÷!´ŲE˜úŗę>^YŖÅƒĒĩņkŧŧv‡ūĒ@tôņë9ZeÛüŽ:´>;÷õøiģôuø‘ ũ¨Í>ųjė~˜åûįz:ŠũđļŠƉà U; u?nÚɡ /ŨčKhîŽC¸{Šoœ™ßzßF#ÛėŦ5ae|t‹įĘøxRMÃÍĘÄp:û]^j7M5ësŗ÷õ|1›_Å˙Gd¯ķĢa>¯f~~ÅÃ˙yaX“9 čģ×üĮŠúK:qįz,ŗŅØ>Ã~K­DN=&Ą\‚”kąž endstream endobj 52 0 obj <> endobj 76 0 obj [ 220 0 0 0 0 0 0 0 0 0 0 0 0 0 205 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 563 0 0 0 611 0 0 0 629 0 815 0 0 568 0 0 496 0 0 0 0 0 0 0 0 0 0 0 0 0 488 0 441 555 488 0 0 552 278 0 0 0 0 558 531 0 0 414 430 338 552 0 774 0 504] endobj 77 0 obj <> stream xœė} xTÕĩđÚįœ™ĨY@g ŧĻžaCũI# tÃUŖG*|paY›(×n…#6;Â:ÖÃĄĸđ˙đAãÛ`ĨĐ +ųßA{¤NX>Îķ/n#\ķ… X9âã˜ĪwÂåÜ [X 3ŠN[ éĖ6’ūá“ø;ÂXķũWaÉđåüiĖ…Į`•` į‚0Ë@;×ĸ–¯ s͏ M8Aá&ŦwĢuĢ ß— đˇÃtĒĶîWs!’˙ķúŋČ\˙U€; ŗ˙.,áįÂîSXŠãgB%yæ^܆ŸsųüķvÜÍ0—Û š4(āëÃk/܍wĩĩ@î?~˙|āĻÃOÃŌPO˙:ũ°æ3aāī>°(D! Qø˙ølčũg! ˙ZĀ΃āåŊ•„Ÿ„Ŗž§.õĖô,ûG1 QˆBĸ…(D! QˆBĸ…(D! QˆBĸ…(D! QˆBĸ…(D! QˆBĸ…(D! QˆBĸ…(D! Qø^Å4¤DåņČ?ŪB^"JF”`@5L‡z˜ 0Z  VÂ:ØWÁ&ØGЏwøqŊ¸ex˜ųNĀļˇh…vX WŒŅ‚ ˙€üpø'į°ĮQ?íÃw Ŋq<í¸EũN’ü˙“™’øI@ŋ›ˆWÕĨŒĒ'lÄa¤_G"ĒHŖã #ũæ Cô8;†äCäÕ*j§ĢH#Y¯b"âi<T´ ÎW‘~ģ@‹Š7!ļ"ļ!ŌŋÂĻÍOŋą"„¸qâÄ+鷙܁x•Š÷"nRq/â>q.¤(Œô¯ŧšwÂH~‰U1a$Xׇ‘ėĸßÄFr€o}O׆+ÖwŽ[ģĻcõĒ•í+–ˇ[—-Ŋ|Ée‹ų›4͟×pÉÜÆ9ŗëg͜1ŊŽĻēĒŌWQ>Í;uЧlō¤ŌwqQĄĢ`bž‰lŸ×į#Ûį/j>ŒĪ‰âöĻæƒáĒUūž,Ŧk>ŒOĨ>Ļ娖*iA¤ęŠ ąĖŪqØĐÍjĻ`åÖ~LŅhíįÂ:c¸Ŗ֑„[û…p/b- .6Ŧë[įŠÖąXc¤5O>ēĢ CĐûâ5žX_œOĪ%pIĒ:ˆš'Ņ6ŽĀ!=I Ž>ôŲČÔũ¤ģ/Îį8Ė<5Ē–ŨhIuŨ#:95åû O|Áų,XÔ|HčŸQ´¨ĸ€w°Ŋ¨YÂ-T| Í×ųÛC?MUHo_<¤rP8ŠGŦÕ+ņR[•ĸ“Ǎž‚ę+Âz-ÕĮHUxxāŅ"ԃ2đđÄëĢÄOēŲ¸ląxīŸAŧˇœŠ6û2DŧžâdŋˆgĀ,´›N1€ęéJwk Ũä<Ŋ gļú•؇h2S‰CqĒ´¨cm詍Z1ÅZ$&ĸ–nŋâ—i§Í+ŠQħÉŌE›öŠÉĄšü!ŗTĖ.cmļŸŊ˛8ŊF˜ÆEėĖRŒGŪ*aUk@ įČ|ÜĘBũÄ;š6|&rÚÆ;ÔJŸ?ē„x%Ž€Ūô1LÖ CüÄøũáÁŗŌ6Õû6*:QΨPĒ 0:X5“Ž?Ûp¨ÔôYęf^?4JWã FÍ<Å`ĩ’=ŗëp{j¤˛HcôËTÔĮ‘°6†Î\Ī^šú‡•69GølC7€ŸĀ}ā]ŦPãĩ{ą6ŠCĄØ„ą„ã›0‡ošV<Ų—ĶÛN¤W-æˆx?cPĩ93[v”™KčĶÅJĖhiV7Wfœ0š…× 龜ˇ?Ę)ũÔJĸG==ÔūŦeD™ķqj¤DÔRxyCƊ ‹í#Å:Šôģ@}~rØEãTV9”ĖՈ ]#|91JSčĨ4…5žN1€Ë6˛QpC`ŌmÔŨ*6/ÃôG‡xĢՅ°ąĩE ¤Ú“˛VžĀ%î‚鄎čt”î1āxķ‘yôE÷'rqy‹â“ZčĐžžcĐYˇ„hŌŊ^J ŪSË[Ú$öäLΤpôéu##’B ÛÉuhŒîsč˛Q†ŸNYjiÃÕĨũ‰-mę G‡zsÔJ¸ģÛčĖT6/Ãe”´˛‡ø%#a ™Cĸ'„‡ōŧO„œÖ…ŧŗčÕ$˛ĨnqHôĨĪ8“–üč(l—M ۂŽfÜˇ$&ûŧ†}ÖÉaãXæUd/Ŋ ļè°_öđ éäI#{äg'OĢgbx}˜Uė•YT¸Ļȋk?“6uD,Ü 5ėVQw\_6ŲŪ0úļēLIŽo\ėĀĀN„à ’ĄĮãėd–ØOžŠg#Â#—ááLD8>§"ÂɈđIDømD8>ŒD„÷#Âņˆp,"ŧۈ¯G„W#Â+a0"û"­awDE„ía[DØG„EÁš#BSDhˆŗ#B}D˜&E„ˆāŠ#B~Dˆ‹1AãfŌŒ~ÎčgŒžaô4Ŗ§=ÉčĮŒž`ôCF?`ô8Ŗŋaô]FßfôMF=ĘčˌžÄ苌žĀčFŸct€Ņ3ú4Ŗ‡ícôŖ3ēŸŅ}ŒîftŖ;ŨÁhˆŅ[íaôfFoBę+Ÿ%vŗŌF¯gt3ŖËĮhŖ3­b4‘RCeĢP ™ˆ.Ä ÄK—"ŽC܂x+âCˆŒø*b,å?ƧÂnū ؃¸QA@| ņâÄXôęF¯nôęF¯nôęF¯nôęF¯nôęF¯nˆĮ1” u Z— u Z— u Z—@ ö*Á{ˆ§y0 ÍDŦ@\Šø ų$͙÷‰rnā7pîĩsĮΝ9'„?0üÚđąá3ÃBgeŧÃ@úâ1Ä3BļO/{æĖ3#†J“āDĮNú­Õ\3ZCä°ÛxZb'†b¨t1ŦŦEē…ŗ1Ûû!Ņ…Xx âRD-ŧ‡ô4â0wŋo>˙Ūąd[Ú΁äÚë’×^—ōú(_y’5H:Ö!YŊ6Ųązí–+Rģ6&YĶVŦB˛|%’ļö$G[{ĪúԔ É×T§87!ĻTqˇÁŊˆ¤!ͧw/wˇôÜ.n7w+ōˇƒÛ zpp÷ÂDœŌ‡„ø+D{m…î!lû-ä÷cÛ!aø#n÷Á$És…û¨P™ĘŨČmÆ%–š¸ë@ƒüzî|Œ“šÍ*ŋ†ģ”é¯âV0ž‚ģô FÆë§ķ Cô<Í]õÔn-ęĒŋôP‘ÛWYÉ­‡ÄĮ°žŸŲŦÄŌģ(}„Čs7s›0ĸ2ž]lbíˇ §ã¸V囸…Ŧūjn9>#Éܕȩ~ŖĘ7¨|šjׅ˜>Ė×q ÆČã+°L`+ĨÜîrn)†p×ČÍG>—ģ„kĀP긚ˆķ ž[SQöŖ|%âF,īÅōŋƒ<ž[‰-Vc@[ŅSōzZ†|%xšVÄâÄyˆsk8/‹Z5g…’ņũ*\.Į2õ4΄QĢĢ´ĸž@Ō9n*ÖĮ`Ŋ9ŨdÕŪ‰ö14ĘdOe2įR+ T>9í _-Ë*Ÿ€ 5ōôĘ*,Đ }˜ i*į†zÄ –ē¨-WÅYוȩ§ ätčST}™Ę'ŠŧTåĸĘKÔvE*/TõãUžĮq ĄĘĩX&Šô0WŒSļqv.EĮá›#ōX.Ž‹g‹‹¨ÃāÛp´ą¸8:\.Ž 'Į†‹‹õļČÆÅHGO™ČSŅSr "1҆¨CŒ/™OæĐ™‘š*_H.Ŗą" T~)rĒ—üĪ6™ŧ­ōä9ŽōcäÆO#§ö'É'k>/Œ‹ĮÍ6@„ƒEEĒ€›ĻxāņŸfŠ´āæį{ž$<ÁPĖ'Ļ⥁Œ )ĸLO(ĶŌF”GD™”ĒJŨ:‹*ųââQâ9äk؁Ą:”*ã }aŋ2ЊrlXĀF‡$‰ŽžHĪđø>r8Ø0—•íYØOb}ōëˇ5ōÔˇęßâ|Š.Áķė€FFßä‡,ī~WĄįūûˆŧ÷>|_¯ į^Až÷6^ö=Ÿ_äš­——ˇ÷ŪĶËÅĩÚ[Úʋ­ t~æņé™ŲžŸõ“x_šįN"O~Üu''ÛīΙāąŨMŒwVø<īÜIž"“H>Ū2)<8(Čøpqđ(eōČōŠō)2›Ėb6ŗnŅȇÉbŌ„ûĘP™BšpēMĀ‘­d;[œmČéâŪĸōíäVÖp7rZžõPFŽ¨Ô“}@Č+ä(Ģ|9nCō:9zPKW6æ`qą‡˛< ÃĄßd°eõ™ūÞęyée^~ųEAöŊčGĩ‡^´ÚŖÉxr*ŗ–~<ąČĶ0ã4ã}§õáXø`ÂĪāQĖ ŖU5Ėūhn.åOĩĨzžû˜āŦãžË:öš?ÎÎöŧ÷1ņq¤{õiä>\ßĀ´iž‚üæ|`3×īš“=Ī?MÄŨĸ›P—;&•1×;re6”âč{į.ŧ+$ȡ„4rãøÅi^ūü´FūŦ›“ĪėäĶßÉâī$öF›ī›×æĩĶÃŧĖËÜéöáÂŋˇėÖTæ?Õ˙ŧãsÃ"_ŖÚŒ]œB|{ ŲŌ“šŊ‡ČÛoÆ^nBßãé™ŲÃ/ī!u=dRÉé!ŽÉVû$ĢĩÔj.ąÜV}ą5ŽČĒ-´ō.+XĪ~eĪžårrķr äÄ|Ų0NJĖ’ ™‰bĻ4F į–¨ķvyīõōŖI¯Ķkcbõŧ ŅãĄ×ōÁĖÎ Ä0č õ<)ĻB ßÅ~eĐę@Įë SajœŸ_w%ŋöÆŨkxô‡‰Žč} ’ž`IM°m f!)ÁuvŨŲ‡Îî;ûęŲ×Îj+ÎúÎ8̜=vVũDwĐuÖõ$ŅAŅų …?yĪzŋôūŪ›īāÍķæxŗŧãŧĸ7ÃëđÚŊV¯ŲkđÆyĩ^Ū ^žĄÁŨDs=Ô7U)‚|~•â–ëûyąQ)–땸†ÅÍ}„ėöŖVáļãŽnR„íøēؤ˜Ģ-nî')´ēĮq”ú@Ī.ŋ,§W)ÁúųÍųîîô*ŋRĖä={P†zĨxžâĒäą`CׯßĐĨĒđ‡A_^N­2ĄļEɯ ÔČ-˛!l¯ļáŖ}Žô3fīŖĢ(#L‚.ęŦ‹jēē.0Ŗj˙gJĖㆠÛ@dÂĒI×ßÖæcîŠØ)vĨ—đbƒ>BײĄąŠūŋŠz%ØX¯d4,(ŠRUŊō"–&5,VĨ*ôŊ! ]ôŗq]UĮū{lG‰ÉâÅ͕­d‚ä+ÄŗˆDüņˆgO#~Šx ņ$â'ˆŋE<ø!âˆī#G<†ø&∯#žŠø â âQÄ}ˆˇ"îF !nG܆¸q1â"D?b3bbâlÄzÄYˆ“ ]ˆķãc5ž•Á/‚Ÿ? ž žž ž ~<ü0øAđxđ7Áwƒoß _ž|1øBđHđšā@đĮÁ§ƒ‡‚}ÁÁ‡ƒûƒû‚ģƒģ‚;ƒ;‚Ąā-ÁžāÍÁ›‚ŨÁ-Á냛ƒË‚ķ‚ ÁÁĒ`bpˌųģƒ˙ĻÍ.úÛ*š`9ü 1‚ũžJø—gž ËøšõĨũPãy[÷A? ¨Î:|†;Æá}Ŗ-ž üąH/ą* ´P›UƒÖŪÉøÂŋä žû‹ĩcÃ+đüˆũž ĀSpž§ęŋ?€ôø\ÍĘ~|0ēö!mBÍ"˜ ārö;9ëa?<ŦļZ(ú6åҐĒ}>‚’¯Ņnī7úŋ{šúą§Ŋ0 ũ•C/Îöø.<õ°õ‚ī‹Ņc\ Ŧ‚ đ((Ø6¨ūKsā˜—áØę0Jëa-öžĀãĐ}p/ꟂFxPû Är]tĨ†˙‹›2ü_°ÛŪ…oI7pģųnč‚ëāAø āĢ?Ü:ôÜ_^ŊŋöĀ=8‹›a7Žé"~ßĀFÖö¯Á¯g16WãĒ<‚ëņ ė!ŲplƒÍDĀS¤øŋũéOĀNô}!ücÜÆõŨۀëōī8ú†‹›’<yŗ ‘Dø –ū7G26tb.\w#ösÎŧ–cvmDŪŽ8ōīXáŖp9lĮU˙6ž‰ĸž އĩĉ'å °Øá´ĩwĀ“¤m7Āã$Îĸ˙Å8ËožFõ<ē/I2îܛüW´Ė9"”dÁ‹ŖĪ"‘ˎ'ā1ė˙ß`/q~Įaˆ¸HŽÜxxņŒÛ“đ,Æī$ZØá—„\<’oŽ[ėĐ´ jí7Į‚Ųžë‚ŗéÜ)÷ãūڌ9ô8îõgá6ø!ōXڇ;čnø>æĀ#˜KŨ8Öķũ.7Ō”˛$bfĀHŋT?üúđ ëw0Ōjh÷ˆü ÜÍŋÂũÜū7Ĩĸ…˙)ābžū@ķ7ScАáO„Įb„ĄÅä÷Xņ0îøÛ‘^‹?+Æn˟ã?ŌūTķäP•ƤÉZ?tŪeŋ„wāUx>€71ŗ_†ßņ…üķüqūs! h5ƒšƒpÜuą?a­Đ.Ėö ‹„M.–ĶđŽj„KņŽ ā}ššūĢfOL‘p›fĄ&ČÎĨš›uāšˇĪĻÛņ$ŖÛî$[5MųP%PīËļēJōÆįC~ēŽ¨ $_WP Ë/J'ÁxšĐmļXíö‚"*‹]øŠøõ[ƒÅ&3ąy\ÆAã Ém,6ūú…ĸBRZRÎM.įKKr¤q‰\ŒT:i’ģ8ƒŗ&a!‘ˇZmVŠ”˜œ&ŠÜdmō„,[ŽÃPY.fĨÄŧˇT×ĩ–§˛ŧųbŽ5Æŧ‡|}Nˡ|]F~—œœ=Ą47ÅåöHõIYÅ7f¤ģëÆį”OĢ›čĖĪÍKĶŽũÖˇ†>îûĶrá˯ž‡W Ÿ˚6áiĨGnö“Œ‰ÖIķRķ:3oļA˙đ_!ĒpĨ¸÷'Äņ\†-‘OėõŲ,Z^ۛ!d­–‹'–ũœŲ<.ūúT×qŗĮå>ŽAđā*NÉ`¯HĨÔxdéåK<˛ņ’ĸBĮØũĨŅ_oí­ˇ#C@Wr…<ÚQa‘Ÿ˜¤N§Å@š“Ũœ&Mv›´Zi˜JĖYîâdÁÜjŸĩŦvŎ.›}GGcûtąõōį¯úzŅūpņ}š˛ĄO.]‘ËĐŠŸúd—kyëĐû))¤™,x‹ā+ާ{0Ná§ÔŧėœėGj ‡Äņ99z.]“Χ÷úˆÆb 330€ąÁ0žfÄõz+oíÕ VŊ>Ö¸ßdâ&nEWĘæXĒãD“Įä‡J[…CfˇŲcwĄ‚Š4dĻŧlė–vĒIīíˆôhŖĮŪ=‹Öˆ?šØäq-1š1`Ų1šŖãcģ(~Ĩ˜ 9šĨN+æ¤0iîĖĘŨ—†DÛˇä{+[ŗV\˛f˙â9÷Ŧģō KádrGV–%Fc›—C“…ŋ UߡĻZÔ&ī:ũė‘Ąw­Xí]ԐŸXT¤Ī,¨.Įxnĸ˙J&Ū´ĩp‹gLā'ø’sxʋ 7ŖĐ‘áæ ‹ ŠIwÚųĸD“ŸV>í‘C\_Î×ęLÉgvŒŧe˛ë¸­*lnŒŽÍmwŠÅÔA–ciĒcæōŧģ°+ŒÍm˛š]š:H͉ĨûÁ@°8p1„dp6Ģw,Õņ/ɔV•sĨĻ’N ų÷ŒœŠš )ß /Č#‹å•C é˜Rå74={œ-å?Įįäg—ÆYĮ -.ŸoË) įįÚtÜI{ÕĸՅžÜtG^ļPT#MĢÚzîtSuĻύˆON6ģŗČâō<ËВęō,ģž¨Č˜;cßR[!YŠŠtÉÎĒZē¯ņyMKߏ&Á^ߊ%)ž8ķŒ”wļ.1—ĪĘÎz¤&!ŠÜEÔ”Úŧ›Oą—Ø dNIIĻöT˛­”7›ËRn>ŗ[Ļ!Æ$ĩšiĒĘnˇnŒv8Ø&š 2Ë͔‹ēĄ]„Ũ‡ãėvŅ(›˜f Ö!4O–đNJ6[ŋYBœÚ#ļĨž|cœÖ4ÔĩahIĮtˇGBŲF–’IŖ&‹Åãë‡ÔP%fÕÍ#[î&Ĩž¯üéiCßēmŒ(~íæéßf`Ž eà |S]Íĸ˜&ōb¯/- ŗ=6&ö‘š‹ų‘Ëx~|¯…ˇ]Ÿ•51ļč]Įé6;N÷2ÍC*"ÜÍl˙ωŊĒ ËøŪ ė2…ÃBˇæ7Ļ^œLīi\n)ÍËŌēUcČwEC3#“>ķņ‰7?ŪēnZ×úÛįĖëŦj->7ĩnŧMwáė=ŅūČĒaūÔ[Ö/ÜP—.Đlé>Ã߉§[!gķ4¸čŠėščĸ­ĸ’éãEM˛'ģhīļ ‰ŧŨžĻËĘŌ& ŧĐë›hą¤ņiŊÁž˙ąŲōÄSCaŪŠ˜˜bØ_>åĖė”s-9~ūF0™=õ„3{dĘi Æ…4ö(pŊtNõ‚01ŋšÅčZ28zžŅ㭀+-Ą×īČé§ĩ&%'Û¡1FÛôķ¤šŽ7—\šiãöę•?ģivīšV[Ũåõ•+ŧîŽUŨģ.ŠÚøí–ÉäæåE×lŦ_žČ;eŨs:^dLúŧyYnaKõôeM%žĩģ._Ųģx|)1ĶŧjĀh+˜b9PLX^2üŅã#™×ßGžx*q$]Čë>æĢ‰3ĖČĶérųÜģ|ēnĸĐ  3ęt{tût=¯ĶĨ’Ą€/xĐ'$gg=Z“m-…–}Å""á,ôŋ|įææĪ,IXJI°Ļ['Zų8ŪjL1ōÆ{RŒ–¤¤ØŒĸËá‹i§qx—Lč‰ÅŌ!,ÄēdļũÃ§Ā ]AĖkŒ.ŪVëå%ë+" îČrŽe‹ eŲq¨ļ€°•ąåŪÕą'sLwF'ę|8ŗŨk: ›ŒPđ`ÎdŦY¤Ž5‹ã=)FēđÅØ›:",°ûnŊė–×Ķ}ÅnŧœœŌ’Ŧlēū¸āøüUŽéœLĪ7Îļ͸o.ŊwáėŽr|ģuæ-œ1wŠũĻ@wwUëÕ^.>)7čSũĪ^.¨+ŦYW~ĢāŸ5uuŨm%Tļ_]ÖØxcąËQqãMCûgM+ÉLÖ‘šö•žĒ”ĒåÅtŋ­Ä hÄ pĒO5"ž*s !$ŗÛ2]ŗŅ,šų¸L31˜I oö匟a6Ø ŧánŸ=9ēîôeÆxŗhąH!RüMļˇqqRSé @ø‰K}îz‹.×/IM9•j7.YOwXҍ~͖écwl7ÜŨA{Ŋŗ#ĶŪ[/¨ÎäTę‰ä֑ Äļ×d|@ÄPĢۋŸĩÅT\UT×QaŸ˜^{•ĪãžëŋÔũô‹/ßܸƒøõĘúŒEũ= vË&ÕT•MoųĶŠ;N\gÁŊ‚‘dÍŨ0o˛;YŦ L†GkŒVw’Ëîâ]4$ãÆgfâ)LŖŖ}´&bI|9i/Ņ”ĄÉ,Ieų4FjCEŨh$síTkn˛S;FšœKĪŽœ‘“Ģ10˜TîârĄ”ø[ŽûķŠÚ$yŌI¯ŪPądĨw˙ÁŽį¯­ęđ$:ËĒ]Ũ[×væO)›âH*ŗK3Ėq%ņēfnu^ŠŽ$~ŋP]wö“ũ':­öĄžš—ûō“^48§âcÆĢ3ëĖŦĖ­#,^Uĸ%9ÃiâMwųœN]2LßK9ÂAŠ1EL9“‚Š)ÉB2Ÿ|7ē >ãNŸklfpš7ĨĻJæ›cXÔ>3{Ū2y0ˇ")I,ļĶSNEŽ8O āÄpĮwuĐnS`zĮ7ú’īŪŲĄ‹$uÆNkJÖUõ]Ēęœķ×9§Îš-ô{_ĶŪ —~zÎĄĮđ:䑇÷!tĩˆØjŖY*‚>°†8‰F& ú¨ÉI —˛ŗš& ŗ@3dö[­mĀŋ?ƒ‹Œ,\A†‡KÕ#Č‚%l ‘-䍤[ŌĨÔupõ‡ú°…€…Ŗ$Œ- ļNn$!ŋ„C…!˛uŦDHøœ€A¸]˛pÍę|͊ލQn-î û,Įĸžŋ,.7FãÅ]‰IEøN]uzė ļ-)Ūĩ˛ĶĪĸč\īé)@Ųū{g;ûįĮ‹Ÿ(´„m(|`Ė>ž—î-Ąãl)ß9iĐi÷ŲÅ,›d˛`3•Ö§ŽÚ°“†:úw4ĨŖ×Ŗ_4 ī  û)•čRTĸÄTéxüģ§ØS™ęķ ė,ívܨ[ÜĨŽĻ6ŒÎčO‰‚zÁĶ Ä;˙ĐIö5Č5ЂH(aYÉÁ[m {-‰5›<×Î&ķś¤…—^úđ•q2ÍöĖÄ BčõœAļ8#8:d~J8ņ–P ‘Q…A ō ’j$i ƒ#˜ ‡g/ ‰”ôĪíjíÜ5õËģb&…1”.cöøœ¨‹Ģ›?7Sŧ›*;]|_‘ĖãũļúŖK‹wõv  NŋŠ,’8Á0‘d@ÖčĖr;m? gÍŦ.0ߥō0 *ˇÅyĪĄ3Ú;h t’FK*ŽmŠx%rÄā`w`÷s`T.Z$ iŲúšŒ2$–—>eüé¯'~xÛĢŋŲņãâ§î¸šg]ĢŊkg˙žO°īŋ}čôØĨs‡Oß?üĮ×ēv<¸čá—ÖüLĀDķ‹8p›mVÛdÁjÂqVčHP§ƒ P° ĘH+¤ˇ18HÕŗæ~–1RVŠPDÃPŋß-ęߍJPu2âØ#ürų|į0_%ԛРĻ“žŸG]3Čú`œŒÛË‹ā'Zļ˜üeqā˜ļiŨöŪ–ôÖÆt ¯ÛfÎį‹ÛC^+#hž6Ķ= b˛3ŲØâŲ™â_¯éõ(§ĢšŋŊ¸iU…¤’…j"sÚŪi_d§˙ہĩ{fí!~΃īכûĶ(ĀŌŗ‡ų$C™†ĸÃ2<ŗ˜š‘*ĨŒ+eBvõb<%ĨĨRŦũÍËÁCąŋßįË+öĮĘˆS\iŨT\8Äúa+1 ÄûãX”˙Á˞˙™™Ą‰ÅĶ:0šbK[‹Ęh•Á°ü…(hÚ."„6mæ2ĢöԕĖm훝-Ū4ÖŲ8t×âe_š7äØ:čmôD–m]ŗŖsõ“7vß2@jëuÎëJĩÕĮs‡ ‹F n›ņåĩKuūļdžoH†ú‡z–ėã5lˇ‚WŪĻ^’<‹VÛAĸ™Ŧ…ĨJ÷xF×o`ZĖ”J'ƒ -“ķ*0Yp¨Ū’ë FĨŌ-—Ē÷Ûͧš1D_ÅA~.Jmîœz5GYz~cU—öāhu§šę­QšŽ˜){éé8zچ“+†ē)oōšŧ\)X$qyįæ:üFõ}°§ø-61+™íKöŨZøÄĮĄ?ŖˆtŽ_ķ¯ÃŞÁ‰.§ˇŽąˇãą¯PqÄī!éAÜMøõšŽ8ŽÉ‚ĶäQÉũrZ~÷› ˜˜,ĄÎŧßãfJ†IQû#$Bäā‚ˆŠ`ģ,¤9Ā„4Į5‚C ‚ú÷ËŽâÎQĮˆ×<—/G…‘9aœŗˇŠģėČÃs Kčn Í^”i ū0tɌąÆ?´ËtKÎ_ŗ§Eė _,ŌģoįÍÍÂŊÍ-~ŗ^•Zž  F„×/ëŋ˙vxķ˛Î ‹õīēōo˛’G\2ēZųX>°ø@ļ˜.Ô0zЁÃ-öl:|`ņä`9ĨÎ7˛øČ‘#yŽ]gCg=øČ Gc†& ×ŠnZސO$ĢĶ'U肪Ņe  ĮĐŽWár…i$X=ږįņGZ•Ô]gžŗäõ„ˇh€ø-áéŖ”F(ĨîȓxõA?-äāČ&Îb€yƒĄąœĀŖåčēģøáŨo[ꐠīn‰2Ė!ŧ÷WOkõÉF8€PãĩwõĮ)–:ĶßęR!ģ;ZÃEŠ–]ūž õēc)ÉuŽv~ʃĨm,Ε¨´SāëDÚj+gî˙ƒú}5%įQ ČYšGŽ"šš—¨iõ×x‰ĶXŗ)Ŗ­+ ,:Å!šÜĖ'jăßyĐY–KžDÚhžĀYā˛ü9ækŖUƒ 6ąœ>íb'ØOŠ)%Á:5A1IŲU Ž’SŠĻ-[ŅÔģš øÜé|oŧøĻà Ãé^Ŗ}懜!Ũ÷„c1גŋúÜBÍ÷ŖËŦ]ĢŠwæ´¸‘ķP›ŧvGK¸¨†¯Gz\ņ Ū>^đ­đũ=`-DzzjNLŒz˛Āč4Ŧnŋ´G§E×ø<ē_ÂĄrš8ŊĻĢŖŊÁĄ^ö#ĸ<ęJEys%aā~°‹īö)Ĩ~ŠWĨõkí6ŋ×į§ĨZ‡´Ž.hÕŊ*Zu‘×yëŧiī­^´}ņzũz› ëhNĪM€E+uJõ ]4ÚĀāā)/­iœIcØ[°ŠŲģžķü“%i>$č°W. –$ūš`ž$›Ą—ĻžÍ Zš¸Ö`ÖĻ[‹ûũŲdnģ†-uŅč”ny"ËŧĮŦh_ŽšŲ#ųʂ}KčÜåŖŨí^U>Ž˙đАuxđ:¡á­>ĨŖę(J (–ĸĐfŸŌSZŽė\+;Ũ*‹Ö-ĶâÚNˇę6<Ë;īÚ'üûŪŖqųRŲĸ䊑Æ|¯ČfĒfą.cÂöâÄÔĀëĖĢøÕˇ÷vū՚ÂęNįēÁ'oxí=§ž0˙cô‡æ¯nųÁ=ŋ:]ĩüŨ: d âĐŸdņd+Ō\ˇÄ, :x¯ÅI;/Zx œ,XÔ~ÚQÍĢU“ĩ’;_W˛] Ņč$+éõč÷ÖbÉoA1äshŽ1đĪ(Đģߤ0)´:9ö=†íÉCr!‘=ô[áÅHg{.Zykâ/Ģ\ôķ~ßdÁ¯T<ĄTĘdēāĄÎü„É”€ē Ž´°įōėĩÅÍ*‘6ú…”ĉ3?Šik§ŊUR!Z„Ÿūr8áÔŦ\ŠvF_ū0Ķ :Öq—ŋ[įØX#ŨēΚlЧŋWŧ͘Ę˙‹k4žŪĨÅŗ% u†4Ų÷z ‹9Âblæ8ÎŽ†ÂĄÉB8ĖPnŠ›v#1ŠÍ.2*†5™8•ôáĘYō‚Ī—ļŊ[*œ‘`pZ1_U6+įŪŗ¯Pđ*Ī2#cU)xQ&úk=Ŋmc‹_˙Ũžß~ņÁëũĢų•wö{ [įčb đæX@Ëļ{\"õöˇŦōoúņƒ›˙æ–B˙’p@kPččt/Ų‡tŸ×ņА†Ö<ʇꌡG­o"Mģëܓ…dR"×Ķ&Ŗi˛ 3‚ aĩē!¤Ŋ`”Š‘tŲ”Ģ[d,Å÷HQ^ŽęC…tĸōAĸfšâx}œ-ĻšDž8&¨>ØeáËēxŧ¯1ĒûmdĐüC| ´Ī`WĢ]éj~ë­–Ž n‡ųfKx‰ŦÆ$oĩ-1ÔljW.ČŦ6Ų…H&.…MNɞ{gΗp/M›šęUãOÂķå;ŽFæÁß"t„×Ķ>´ĻĀcÆbĪĐÉTr˛`sé”ZÛLˇĩļĄ9)XēUŠ—xŊ.NĄÕZ]†nĢĩD.Ô ĩÁãôåÕEH˛z‚Ŗ”Éf”Ãh?îŋļZā§ŲæÖ[g™ÂIøÕĀ’Ūâë°>Ģ‡ęę¸ČW5“ŅeÎÔUāâˇÎåôúĨČÆįz‹ŋ-Žeâu($ĄŦfsŧáwœ™UH‹§¯­2l‰†^‡¤•kųŧ đČö`Uk‚Z…íeXí4[ÍHyHpVe ŊŽ–ĨŊ‰<%{‡k[yÁ‹}d%0ũ$§>“u+J.]hōCęŽâ?Š%f÷I‘ŨÕĐÛ` ‚#†ėnYšēøâQęØŦál‘ŋŠÁŠõÔã‹S‡ĒĐûZapjâzã•K’1Äu t큍ZĄTL”q%āL—Û5YpëŪ)Ž–H2FSœļŊ¨”vīO>ŲšûܓĮ.ŒĘŧų§ūÅ៭ũ É9ãˇk‘tņ_á›Ë‡mØûō6[PāA“¤“ŅŽšîŨH$ãs\ĨKYŌ*?5­R:Í4“•#B6i6anRŲŲí1ī~–ÍÆöËåyp([Š_3‡ƒĸI$ œQÉfĒ+ĩ´Ų’ÛR,ČeĘH†åۖÎæmØáYũÉĄáNGĮč×Í{pÕ §cQĮš­ūåģún\ûÁ÷W>y”Î]Zą4Õ͡ÄēįÎM.ë÷jõgúįy—õį{Û˛îÖŲĢÚûŦ•ûÄk3~ aĀ –ō ŽaäĀŠ/YŽ•Ķō‹ŧVÉâ´FÖÍŌh\– 2—ģäį°ROĄÆģ§Ę5€*€cĪG˛r8‘û‘ķB/bB`!žˇ+vå;2ÖuôÖY˛ų 6Ígļäėę'ūå‹ĄŊxfáŠĀTÃü9†dĢû5˜OėzžwĘ<´į"/3ëcI!¨ˆ•ŲˇCĄŧí‚ļ$÷RDA–4âr]J2Ũģi1Q Œ~cœ5đČÆžņ•­!‹Âė””ÎĻå|ĶÚv;Žn‘ÄÁšŸØ27fJt,Ũš~Ŋ•Ѝ¸¸0´¨ŅZ|ãZxT Dŋ„ŗ_Bķ‚u|>ČŅv_Žöú*%ōIz'Úņû€\ŠÄ9+É)č58Ģ`Õø­nwXCË+ÛĢJnáĒ Î/@č‡.I°´øæŖÅ,ž@>ŒŗOCķ¯ŊĖ“pŖ%8~€îē|œîęos)KY‚ŠŋŖ¯ŋ|ˆęđΊĢd .sgÆßr ņ˙ž1(7›ĩ0&ÄHŒËCë´:䂔zģ_ĒUšeģGB˜Ō+&ē ‹'Ē1…ŒdßĀ•’ū“ °Ę1UÃô\Č´ũkāíRZz†7‡°#Ū5ŽKZžĻ¨:ÅIŗPŸ}U¨ÍžwZĖUvzčŲ‰O*ҏ@_UĨ6PۇtMķV-Y‘Y™NĨ\_ŋ˙žEŽëÚ&ũ뇛īÜûņ›ĸ‰|*ë~ōķC—čĻîgLĢdßF~?Fž—J& Ō#ōcæĩÉÅąIrügyVœËĻÃgxVœIwDŖa& šKŠGįķ ą“6ÂŠĖ“,kESe'Éž÷Ŧ!bŽJŠžrŊŊ\p7UÜé*#s6?–:žš•ËsŨ=Ác­Kî_“é ¨Í¤wÅĀ‚ëŠŧ‡‘žíĪÖ)RĘŠH:ėPĻå›čLĖ4÷ÁMŖįjtŋH6œĖ{?~ŗÖô™đ_¸Åúz éËÜ |š>īQŖ• Tg­ŗĻ­ÅV>‘îˇZU(4Ŗgy DĨBÛ^…bŦģdņ sŗē“VĢ—=!“y=\ÅOräŦÛ*Ąˆ­†¯š‡B2Ŋē`ŽõŪwÔc o›ũĐŖķī][ęĶÅŲë×įÖ6Ž’wuô,SP4ũėœ–C_zti¤w×鞱DūŸ†×\_ŧYą.ē– }į>9'°ŅˆÎŌ|…B™#Ųã4bÆ`t!ÚđO´¤aƒĄԝˆ•Ã=Ņdą§‘6E˜ a5Qß´0ZˆífîõÃ:~(ßŊ:¤˛Į‹¯íÆqÛŌívÆ.ž˛32įŦŋËHudķ×/(žŸö•ɤRLü´Ŗ/ÂqЄи~Ö¯G'ÆP’Ōôåęi%FõBēî‘=4l<Ļ z˛ ;ĸ=¯ˇ˜)Ÿ,X.Á“wBĄ°Y‰Ļ~ށ‹<+ōõÛķdÍ5ĐU“.Ŗ÷öm™a’ĖWöüŨw_>Ēԇ0“ ەiEŅCf‡¤ŽæCkÎęÁC8Gú<lđHĪEceÚ‰34s–c$ŲŦ”ŊžļX-ČĩX/eŨ‘Pˇá„;ũÎë9nē„ĩeɋ Ëņlđˆ•ôĢŪ†û3gG­¤Wå6Ō+ęQĖ$Įã–\œŧüR­'úZz’[ĒõJkˆ˛4Ū°ËšČēÂŊ Š#Y…ŦmS}×ĒˆÂž¤Û‰˛^Īõ…tŦßįâŠÚĒ’‘| Ũ‘ĪŽ^\|?å3Š„I'¤X´= ×āŊĐĀ+\´ë ĀĻSM§į’æ¤ÍæãNJ$>/W‰Dß;}žœ„eƒ‚́3‹ČŪˇöĶķ—ŨŋzųîšŪE¯ŦYœėŒč“ķ2‹WStĻ÷Ņ­įgWėîŲvp\xëŊ‘Ū5š[fŨUė‚FŠUhžAp¯QˆŒh†—"aŋ`ÅYžĀ?›U,6_RœVÂ`ˇū„ ëņÕiXÊ| ŲKŋ`FØt•ã‘eēúâ‘đJu0hH4iY_ øĢ’¤sé|Ōĸø˛ßgΝ—r!N6]ęxŦ@ļī툃$ÚņM ‰ĒÕ6‰Yƒ3ŧYŒ¯ŽÅĐ>āĖˆŲP]ę’Ūf˛9Í1 ‘Čáī‘­“ŸMēNgÆü{Ķ´×Cņ‹đ•”Ū{'›÷ŨōÛĸˆË˛+ęŦ˛rcævÁ@͚¯īîhėp4õĖ^œžëļí{ęįtļö:fõ…z<ą9ƒ ‰5… RKļ>ĩŊgļ'=0X÷pKc.éyŨŦ?ŲčĪĨvęQÄ{ ņî”-äŠWđJŠĖ€â–ŗŧ™,™LėĘ32Ŧ=Ŗķ’DĸtØ ŊÁā–)O ũ’‹įPōÍiö$ LŪÃUV!(IŸd‘Ģ6āüĢ)oō_U …ŠhÚŽ;uJ§Újķ6ŽĪ ŦŨ ߒš:WyNņ§ŗēŊzģ1›Š m;Ļ~÷h]Ȥ_Q4_FåpŌ ‚Vœå=dʐÂčdƒdĘVl:lGŦ— 'ėŨNĩ:NÁÔëtm§s"öD큿›6 "ÆŽöÍä|ß+JÖéu6lcœáW¤ŽÆ'ŗ2y=ßéīZU8âŌ¯īIwxmĖō,”!{­HŌ;ņÄŲ¨)ģrT¤|FōöQšn‘DųPü¸ė(kđŅdö…Ė椕 %bĮhŅ#WŽü—ŧ>sÄĩ-aõŋ¸G DĐyڒ+ų%KŒöĶĄPuøŒw U´?H-xfÛ!}(=ū€Jžõé‘7™ũAÜnģî‡[á RŽ…§ëãōd’ļuKņā™ĸ æg9LN:™”Zâĸ—F܄Šm@Ü$Đzē™wčČ)h=­PãŠ!ŋX„•PJZ‚ƒ+ŊŲŒø$3RTˆuēR3æŅė‚ēîä%Åīå:î„Ø} ‰Ë•Jb.-yöTWNįr>ß#īíÄũ>yCV•É v%ūûB‘J`Ū‹,Z×ĪõËŨ&g$ü–Æė̃‰°_ŋīn…RæBÖYį6ŨEۗmĖ0÷ØŦÖžŲđuoȊd!5›ŠâˇĐŽËâT[’dRĸ&Š„ĶHn‡ˇ’wĘ“j õš/QR‰āĮČÛbä ?ķj˜øVŧõ~å Yāâ~ÉíWÚÉHēįP—ôŌįaúT6CŖ˙wå>éŠ+í˛¯ û‚ŧŪĀS;ŠÛ(ZGA\8[D­§$zŧ2áiŲWČĶ`;:47ŅĶQŠ~ú6 >UŁĪ˙Y|šdôņëŽxĸM?~ķÍ71ÎŋƒzxXēõ =Â÷Ž(ųĶŧ IP0Ō^ĄģĨOɐ˟vˆīžÂ锴}ęqŠTŧW<ƒÅ€`mĀ#<Ü5˜ũŅĨË/ĪŖŸûˇ‡Ņ`xū.uXzŒáM2Yü|øo“=EKž’Ë•Šr¤ƒcšŧ°Nŧč.ãGė”ŠsĢMz°˜„¯āœwĨ“Ôû˛{ŅŽ†AĮq¨ģ:å7aEVR˜:ę (ā8ž5ętĖ1˜z†3~ˆĸd‘A¤‘s\>}ũ_Ųž”ö/BąõSī/4-]ÔˇŖsרԕî…]ŸYũÜąõĮoŸ}Ŗlô…žŲ‹ŸøÛãíĢŋÍ~ūķ;ŋŋC'ˌ^Šfëx}‡ũ~ģŽá8ã7a 0ųãg eG“â5Ō}>—%ČąlĮ`âY—+nųâB‘>‡ÄpŽ9ל>IRVø}™üé4b€ŧ„~Ŗy e§Ęö¤z6­č¤m˧†;Ž>?xüļū[z™—¯ŋk÷øÂ…Œ7Ŗ‡ŠngS6Ķ˙ųŲí?ØÎ柟SøæäSßhj§S6ŲbIŒš-~^ö1Đ ná͜€[“JCĢë7Xm3?Üp æywėR4ę hrUSŊFn 8ôVŊËĨ˛ÔĮ,M1îØ•ˇŸá@ā0ŌésšÜŦ|.Îs¸Ø›'~9—&˙ LŸ9oÉåĶįķ–ŪhŸlĒ"tĨ*"7ˆU‘†úĒėåÕ5‘Ne káfŸÃģ=ŋ˙ OŲ‹/ĻŨ_ąÛ\‡Æ+ū8æĐ0ÎlŒē´R“§sƒ^Ī"c* Õ˙ĻøŖLÔJ%“”… ŋėŗ0˙š TɤŠ d(EڏÛJ} #JI˛I)öņLÆ›˛Ųäa[*…eS§ö”Ėa‹M}IíķĢ،ŲgqŠĖä|˛(HKđK„eë#i2éz"’>—OįsBĸáU^Ā×ŦĮߝúæķX@9ötúäytņ<Î˙ÁéûœpđC?+ W‹Įķ’%Q—†A2øYĖÉȤĘâ[+Š Š IŖø'Q*š?BÉ%ŲbĨ۟ú ´åņg_˜ŒLŖ–´}ĢīUAU2™ú!5‹ėåÛi%’ˆ „ÁâoL=īvËŲÃá ZG9‰ü0‡eŖ”]’˛RĀĘĐĻ>ųœĪ•šF‰ĄCūFIš`"‡>WN/ÎäGüB“ø&’h‘ÃNΛ,ūŊČËĐá'ō7ėį[\Ž7o[?Ō’Ą• ”ĶæžsŧcĶl'œŗäúeKņ?9†Ŋy­ę$Xú‚Ęī§­IŗÍ˙ƒÕęLŌ Ãq؀ö¨ŠįbZ-–0V¤R&KkA"Q§%v†# š|ŊåtŋؐF¸&ēÄaNU€I]3] EŸ1Ŧ^žhYpҊŪõíųãšá勴™î|ßŌēÂō•›fũ¯/ÜøDą¨Ģ—6įîöÕ­m̚*ķ҆6Kgc Ÿ† k:öŪx­yO… J¤gĸ†¨!ZBh&Iæ˙W’f}_ļNö>&ųC(‚-ҤRNč!ÕzÕ/ÕëÕë3ĸįū‡čOIԌ"zEÕ>Ŗ}FÖ}N÷KŨ/ŲˆÎqKšĪâ7xô÷$†ë Į ĮQD_øĄgū2djEôˆŲ`ž‡ĐK_™~cÍzĐĻŗmąŊl{Ų> Ņ‹zĮūŽCę\ƒzEēĨF5ú”ÕčŋHī ä\'Ō;Îw\7z “ģŋF5ĒQjTŖÕ¨F5ĒQjTŖÕ¨F5ĒQjTŖÕ¨F5ĒQjTŖÕ¨F5ĒQjTŖÕ¨F5ĒQjTŖÕ¨F5ĒŅ/‘V8Mĩˆ˙°†ūTųւQō‰&Ÿ6Ōī‰m´’Ub›ZõØĻA@ŊSlKĒî‘Ģúą-Ģ:/{Ô#ļ &ĄÄļĖf|b[%W•īWƒĖąÍ€ķmą]š3]žsé_>Ī1gÄ6rUlS@Î~QlĶ@Ī>!ļ%U÷HÃ>/ļeUį堕}Il+€‰9/ļ•ĀĪN‰mŊŋ|ŋÄ9Øf€‘›'ļ5đģÜzą­ü÷ĪĄD)ĘYh rڂœ…ļ gĄ-ŠēGŗĐ–Uä,´9 mAÎB[ŗĐä,´9 mˆÜäŧŒ€ `Œ ôŗ ėÔÚ v"E׆ÁFtfēļ]KĄöjÔŪ~oƒ`úŊŨ˙mæ]` ēĸęn|n q˙Ãčî]č˜@Ÿ7’ûp˙¸|fŨĮŲ‰îŸŨ ö2,~$}īDŸĮŅØ#čÜ.qvCdx|<QÂ~Ē4/á üôjÍ<ŗŠĖCĸüšÔ×ĩ¤ŗ“|ۈžŲ€>'ˆŧp{Äqåqfr€9ķ€ŊDNĐņÚ2Û+rŠīŪ€¸ŲÆÂ|^Köø™QԊ ûŖč÷0ē6$ĘåZŊ sø¯ĘļŌûFŌĶftëvŨ1N¸Ú…ŽcDöWsPũęyĩVas"𲋌ˇ“Hsô/đēŲK8Cį?ŠS{ƒĶP5Lô2&Ž„önôi'9zČlKÚ,õƒīEwüGÅęB­}3z/­QĘ?xžCDŌ‚nˇ™ī- h/ĄŅÅtüĨȘÛŅ=ģĐ8X6›‰tvĸöĄŗ%ū'Pķą ]ۍfŽŸ$Ģî&đU4‹Ȁ,húŒ÷•$_ŌŠĀß("‘Áf2ë‰2„U đ̓žÃXÁ÷ ų Ãč&(ØHžÁŊ덞2ã"Ē“čš`7„ģ RA] -‚v0ÆĀ6ÔÚLZÅu&<[­˙äYÁĘáU$pƒįq3™æqš^šņÂ×>‚ū=bXŽƒh~3g#čV[e%ā> D›É™A2féĄ˙]D Â<ō:7Jú&ŗ(Ũ-HyÉJ8;N5NĐ)hjiī#÷î"ķÁsL”-Ī(yb ™#æZĀË (‡kõ^-ŠŌ(ęĸ„•UūG°QģČĘŨ^~Ëi§hC'ĘvNđũ#D U’“ā‘FˆŽĮÄČEčĪ~ī4 4HVSiŊn‘4RöP#d…xD<W)°dšŪ¯öļ-W­Įĸí(IĻõԌčÚĢvŒ=Nŧã0Ņũ8Ņ۟d7“ĪģIT#Ü-Ä; į#ĸŨX@f1VÅßŅŽĖô~ˈĨ#-á^Ám#kųŋ'žÁöĸã\ģ×Ęuąˇ¯zr™l“gÁȆņą‰ąMģ<=cã;ĮÆwŒíHyēFG=ã#›ˇėšđŒO īۘęÜ>4>2čŲ28áŪáųwöž=<ĒōÚû}÷Ū3™L2ˇ}™Ų3™ËžKf&cR1'ƘbĀ”Æˆ1*ŌÜI0™Œ“!q#Ĩ(RJ)FŠˆH1RDDÄp1\Œ)"RH‘r89H)æããāY=-|ĪĶ?Î÷œŲŋŊßÛú­ŊŪĩÖ^{LeUcíÔpUĨRŨUÂßiŦˆ’îhUYemxĒRŽTb J]CÃŖĘԆ†JeF ŒFĸĩáŦ)‹)õe@ĶX;ĢĒ1CŖ‚›Ēĸ3•Ē&˜Ø)̏"&mŨˆj03¯ļljC¸ŦŽŽĀüXm4jĘjŖuĩáĒFÚ *×VÃe´ ÔŠƒ›jĒĒ›Š4Æĸ áŠé Hm]•RĶ­ÕŽÁâkĻ*Ed=oĄĒ>ēžTÂŖU ôƒj`›˛XMUT‰Õ”ž1˛¨az šUõUuMäļ&ÕÔ6Ō{ލ'4ęcJ¸´Ž*+']a˛@Š=j+‰‘@ ŌS×0Ŗ*ZQÖXĨTԔEË*bUŅ!§—WN¯" éL*–W‹Â˛Ú(\Ø˛ĒŽĒž* [ØP­ĖhˆV~§ļžl*Qę˛WļTšŪ8´‰ejdē;d_”00xŠis¤SŊ¨aĸßšĒÔ՝jŦi˜^WITiŦ#žVUN¯NÕoš^Ŗ†Šr Đ ˆ)ĶĻÃđ Í¯,˜ŪH6´QŠl¨˜Nīd,]­š:ŊŽ,Ēˍ",_ûcUķĐâĩąĨL9SA—Ē1@}é#ŽQQ[Ž€ū™õå uCšÜ žû(žkf´ļvâ[Ü|:Õ54’=ˆ@TÔ6‚ĩˆtØj•0đ¨XUY=¨j†yąFâs JYm}u(ĸRmc |xo¸jÆ •EéžÖƒ‘jI@ÕF`WgFŽØ*ŖhđŪ¯†íØ+ûX ŪA”“qÛm×lm™‹–UV՗E%ęũû:ä§FĻGHwEC}¤,\ žņÃpMÂ7|åJøMĒ6(“ ŧčŅÆšX,2vĈ3fdÔ_•FĀē†ŠŅ˛HÍĖąjˆŦk§Ō6™ö5=Ø0RWK7;ËPl˜ģ4“x#č ē“nb-H+eąĒtĨ˛ļ1RW63}(%€ÁAqâ0ÔÛÁÎõĩ1’ZĘgÆDX˜Č‚pä蕋jūMk@’g''ųÖĻ“5W`O kTÔ\ŖŲ …¸Ž›.÷ĩö apû`m$r˛ŲW§ƒ„ŋ§-Nķ&„H ŧb0¯ ω!YˇS k%š ˛6øėL“á熞ĘáÖ+JšQr; 4_A’Š@žĒŦ"ˇIæÔTÕE†[á™CĶɆĐ,ÖPS[^ :g܈ Đ QYU]žœQÖižō+úęIx}ۇúfá œcôÎŋ÷QŗŸ%'c˜ƒ—ßč|ŽĖgŒ7:ß` ķįÜč|Ŗ‘θŅų&™ĪÖŨč|A€ųjv "ßÔrt>˙YˆÕéؐjve!3Ô%OBíôT sĄÚzŪPæAëgč¨u …č´mBĪĄíh ęAŋDŸ _Ąč×čoøfņ)ŦeĮ` ¨åΉŊ×pęá°g8īÎį#ĀRœĀųSāœœË€s pū8ˇį^ā<œŸįāüOāLN8ęÃ8™{Žá4§8ŋœã€ķ~āŦÎpļį΀헀́ķuāÜœīį'Āųgų+p~…~yātg8ŋœw įdį} į÷ŗ8 œaāœœss pŽÎ Āš8ßÎO€ķĪ0ōp~…~…“€Ķ œßÎŅĀyp įä]ÃiNđvā,ÎjālÎ'ą8_ÎõĀų´>ÎŖĀųāŧ€–`ũķĀéÎŅĀy/p–'ų{? įTß} § 8ŗķālÎ_Ûī€ķ-āÜ œ§€ķú–P; …8-†{x?œ5ĀŲ œĪįjā|8{€ķp~Nâ[ŖFš„Ŧŧ8|ō˛4 CsšūŧüÂH~^ŋ&i´ŸÅ+`cī'ŽõYœŽČËËkÎÉé͍@Vs[ŧÔ¨i;ĢV!ĩē´ŨiPsH­Š´ĮãíĨ‰j”˜ k4ÍmmmKëčœHÛ@ŧ-Bērr kōķķĪhX¤as´AŦZÔ ûi˙ĐøD¨Đũ:aP~ŽŌŪ§á†ËÉ9 Ũ… ]xÖågŠ‘:qo|ŧŗk{|{ŧ †q] ŅW'_u“/Ÿ[āĄJ˛hYŧ´ë‹>ëŒ}Y‘ĖHf | –,Ŋ[ž[΅#GîËĸܗélrT Ų¨ôęqé°.1ĢzGWW$3Y­žÛ͜¨Á‰‰-Ũjuvuwī ’k5Y•]äS™I…%@ULŽ;@čÎĢĮTt{œä{ÖŨ}šĢ̜2kû†˜Âô‹ļ¯/ã:ëTĨĨõ•~ÎŌD˙7&…´YÕ;ɧzđ™ŌŊ˛„¨×Ժ̝Ų>ŋg I^Z:08X3’ ü|˜8ĒĻ%ūj¯ĐT´}¯}ôūŅûåRrč´(QûWHēečV4 ŠR;d›Ûáčƒ' f¸Ž.jw ~Č6wuq*ŦK8|øp;­x¯ÔÃä}€ŠŦ Oē67^g“ë;Ŗõátōæ^—NŪäMW ĘbáoīĨĩxyb âĘ`˛UĀ 4v*Ė@udÆZQŖ¯žBI(÷  4"ö‡ča¨õ~ŠÚ FÂ÷å*(“ÎIe•ˇ€R@c Zģ M„‡Údp°úŊÛOP+zfh6F Pa‰` ÕdäƒAu÷ũFš|ûØ×ŧĢZˆP7i ú”Ĩ‚13Ņŋ @^~M÷Ĩ =Žæ@hFėøÂÂ|4ŽčG?TДIE÷(`l"CK„3Cë‡J}Xnš€îE%PĮ’īļC3Ā™Ÿ‚JĖO„jĐoˇ ī‚gŪŠĐĪiŋ´ŌBŠ”ųŠt:nC9ŅîA÷ĄÁ1Ģá-#ŠšŅP+.bVydEn”õú÷ ķGhzˆ~cZOŋyC]ų,j¯ÕXÁ–SœF1JqÅVŠķ*Ęębė"ŠË)ŽĻØIq Å]äk3ļ—âAŠG(öQŠû)ĸx”âņ†heXuŠb?ÅķŌ‘ QTSLĻČS”):)úaīÔ!Š#)ŽĄ˜E1—b~cm¸Z=‘â$ŠQ,ĨXMąŽb´ąž"ĸnĻØJq>Å%;(ŽmlyĢz#Å­wQėĄxâQĀQęOSÅE—R\AqÅĩ;)n"Y"Ņvg–ūĩ.ß˙͆ŦxcH~įKE~' 2]"d’y˙yŊškz¯íģ~†Ŧ{3ČA^7AŽūI× ä˙ß9cČË7‡ ]Í ũ$žúÄÅĀzshšItÍ7‰@ņ‘…gˇž7sEū⯋Ú/Oļ?cxūū#d@˙ô›8ßøbxrß(J7ˇA•3-FĢĐF´ D'ĐyėÁŖp..Âå8Š[ņ"ŧoĀ;đ|Ÿc†g<Ė(&—)bʙ(ĶĘ,bV2˜æ"d3Ų|ļ„­a›Ų6v)ģ†ŨĖîaŗ§ØNÃÉ\ËäōšˆV­ô\öĖuí Ãۜæī´ĄÖâ8”€¯´ÕŠÆ oĢ×ĖW“_šĨmŸƒUíÕ^<'m<'GĪēĩÃWë\͆(4ĖŽÍØÅÃÛw ×>]×^qmõˇ´;‡ķåô _§ūēvöuíŽáíÜÜëڝÃÛw9‡ķ×ˇĮøÅ×ĩ{‡¯ŸpŨnŪëŧŽ­\×ö o1´Í@Žį-P„†Îmßļ¯÷Ĩž'ŲĪ÷'ž‹šo›]Ü?x~`âĐyũāšÄ<üŽKš†īBÉÖáZ>¸ô´÷\×Ūw]ģį›m|mģ÷ēöŅëæŪ~˜ģŽ­˙mí?h_ˇ‹̇īú#uÃĮé"ŋ3–›ŠNÁģŌjÅjôš ä-‰GjË,ë¤7šõÜĢÄĻx=î„Ŧŗ1eDĘHÚ.ƒyŋ‡{Ņā~—Hfv!ōTŅĐ7Ŗ\x;)†wĢjDŪ5x'~īÅĩ–W 퀺ėseVŧąĩ‚&ÍđĶo\ŧû,A+Đj˜ģ ­Ep^yq œ7ŖmY´íAŊ–×cYgŲø;Ë€ëĨ‹÷JoJ‡éų-é8ŋí#ôü–´1ĐÚø–ôb¸O¤Ë­‡žũôü–ô!œ_…öz~KúÃĐøÁĄņ†Æ ŋM%žC%n§wŌžwiĪnŌcy•ęŲIõ|čiŲ@{~O{6’žĄ‚D“Bö ‘ŋ‚­‚ˇŌnū=q‰ô"´ĻÁ^lěņVŧ}ĐĻøCüū÷1s™66ƒ{‰{™[ËŊ­ã~GŦÍmärq‡¸šÃÜ'Üî(×Į}ĘįNrŸs§¸įÎsã.p_rÜ˙á.r˙—ģÄũ'w™ûJ…øéü ~&˙ūqū ū§ü“üSÂíÂÂ}ÂũB‰đ0Y˜"” åBĨ0î, Ŋ#m—vJīJģĨŊŌ>é=ŠGz_ú@Ú/}(ū ”>’IK‡ĨO¤#ļmļ.ÛÛ.[ˇmõǝīÛFī;ƒúî|ąņ˙ã;Ļ÷Ë6ĄÁzqÜ/ņúV¸žĮô'b$ÚčO˙ĀČzô*ŧãoƒãÔ Į<đ˙=āīÃ1}€öŖŸŖŋ ĶčYĖaZˆŸĮŋEŋĀkđ+h)~ ŋ†–ã×ņëč7øMü&ZßÆoŖįĄ–؁:p7îF/ā}xZ‰{!Ž Æ´ ‡Ћø>‚V3v& ŊÄd3w =ĖĖhsszų3õ0ã™ņ¨—)` ĐĖũĖũh?ķķúy–؁0ģ˜]XÍ|Ė|Œ˜?3Ææ æ œČœgÎc-ķ%ķ%Nb÷ŗûq˛ Ģȡp ĒlR%Ē1¯JR%aAĨSą¨’T–UwŠîÂVž‰oÂ6ž™oÆ)ü,~ļķ-| vđŗųŲØÉĮų8vņ­|+Vø9üėæįōsą‡oã_Į^ČužW°V\$8…ąø~!KČÂQ!GČÁBŽP„cÂ$a~J(Šņ\áAáAü´đ°đ0nÁĪ?~Œį eBū™P!TāųB•P….Ô5x0MôāgEŸčcōEŋ`Ƌ!1šG!Ž`&ŠŖÄQĖÄLq,S(N'2÷‰?K™Ib­XË‹ub퀨(62%bŗ8“yPü‰8›yXÜ/îg~,ūA<ȔŠ—ÅËLš%#S!1ÃTJđaĒ$Y’™jé×Ō¯™ŠŌo¤į™iĨô[æQéEéEĻ^Z#­aÂŌzéUĻAúŖtœyL:%bfJæ(3ËŗŧĖüÍōŽĖ°ˇČÉr2Û Ûd‘}˛}Lū9›Ęŋ’—ą1yšŧ‚m’_”_dgĘ/É/ąŗäõōĢėOä×ä ėãōëōëėōy+—ˇÉÛØ'åōNvŽÜ-÷°OÉŊōė|ųCų#vüĨü%û ų’|‰]lÍŗŽcŸŗY‹ØĨÖëCė¯Ŧ“­°Ë­Ö v…ĩÖZË>o [Ãl‡5b°/Ø:m›Ø•ļwlī°/ÛļÛļŗkm;m;ŲWlīÚŪe×ŲvÛvŗŋŗíĩíe×ÛŪˇũ‘}5Ĩ Ĩ€Ũ‘vĪ—MŽË$×f‘Bųīō<ÛķÁHČ)ƒų€FŪ4’:TŦŠEI| H| C$>ĐĮ:tXePĐ'$JĐ%čÄßŅQęīZęīIā›.Ŧ'„wÂīÂŨăđnâAxņxr‚_āŠ_ŋ`Z‰Õ™nbQæ}bQæ(Џ~î?¸/¸sCY|ķНüOŊĒ=}u0zōņ\ČÃķ ×·ģ€fâm÷@æ%y˛.Éšq;đ đŽö[Čē/ãĩø’/![ŪE˛$äČȍ@6ü˛āũžd÷CÆcTœJ¯2 NQŨĸōä ȡˆé1FAϏM ™˛äˆ:Č  +´ˆ‹ŗ!/@N€˜˙´BzžÄ;DûåŌ)ˆcÄ/Ä.‰\ˆÛßČ+ n_"ņJĸbu‹ü–ŧât'ÄįnyŧWŪ'ŋ'÷8…(= ˙A>(‘z "ôÖqĄ%Ö­‘脨Ŧ…˜Œ|ĶĀz_ûĀ˙Úīæė7<Üú?$†ā€{û€h¸†ûP§Ö%ëxŦsę|ēn¤nŒ.[—§› +Ôë&ëĘu5ē°.Ļ›Ĩ‹ëæęæëé–ęVčVéÖę:u›t[u;t{tŊēƒē#ē>ŨIŨŨ9Ũ€î˛žĶkõFŊYo×{ôA}†~´~Ŧ>G?N_ /Ō—č§č+õĶô}“žEßĒoĶ/Đ/Ö/ĶwčWë×é7č7ëˇéwé÷é÷ëéęëOéûõįõ Č 6$xƒlp|†tÃ(CĻ!ېg˜`(4&Ę 5†°!f˜eˆææ–VVÖ: › [ ; { Ŋ†ƒ†#†>ÃIÃÃ9Ã€á˛‘3jFŖŲh7zŒAc†q´qŦ1Į8ÎX`,2–§+ĶŒc“ąÅØjl3.0.6.3vW×77ˇw÷÷OûįMȤ1éMĸÉfRL~Sēi”)Ķ”mĘ3M0šŠM“MåĻSØ3Í2ÅMsMķM‹LKM+LĢLkMĻMĻ­ĻĻ=Ļ^ĶAĶSŸé¤éŒéœiĀt™įx-oäÍŧ÷đA>ƒ͏åsøq|_ėđSøJ~ZĢjŠ6~ŋ˜_ÆwđĢųuü~3ŋßÅīã÷퇸ŖüqūßΟį/ HP É/ČP[ų„0RõUސ/L„Ēę!xV uBThf s„y†NĄ]X",V kāžō„õÂFa‹Đ%t =Âá°pL8!œÎ Âe‘ĩ"/Ęĸĸ<$ŽĮˆYbž8A,‹ÅÉbšX#†ÅDvĢØ&.‹ËÄqĩ¸NÜ nˇ‰ģÄ}ī‡ÄŖâqņ”Ø/ž/‚×k$Ŋ$J6É#Ĩ i´4VʑÆIŌ$é!ŠTǖ植Ô,͖æHķĨEŌRČ̤ĩR§´IÚ*íöHŊPë‘ú¤“Ōéœ4 ]6sf­™7Ëf§Ųg™G™3ÍŲæ<ķsĄšØ<Ų\ižfŽ˜›Ė-æVs›yyąyšyĨyyŊyŖy‹šËÜmî1063Ÿ0Ÿ6Ÿ5_0_˛0Eo1[œŋ%Ã2ƒmÉŗL°ZŠ-“-å–Kâs–%n™k™oYdYjYaYoڝ–M–­–]–ËËKŸå¤åŒåœeĀrYæd­l”Ͳō[ē'ȗ­jk˛•ˇĘVÅ´Ž´ŽąfYs­ų։ÖIëĘ­Ķ jk˛ļX[­mÖÖÅÖeÖëjë:ëëfë6kˇĩ×zČzĖzŌÚo=oŊhC6ĩ-ŲÆCųé´ųl!ÛHÛ[–-זo›h›d{ČVjĢļÕŲĸļYļV[›­ŨļÄļÜļŌļÆļŪļŅļęĒn[¯íí¨í¸í”­ßvŪv1Ĩ¨S’Sø9EIņ§¤§ŒJÉLÉIšĢ(Ĩ$eJJeĘ´”HJSĘė”š) R–¤,OY™˛&e}ĘĻ”­);Rö¤ėO9œr,åDĘ锺)R.ŲģÖÎÛeģĶîŗ‡ė#ícėYö\{ž}ĸ}’ũ!{ŠŊÚ^gÚ›íŗísėķėíöĨöû{§}ŗŊËŪmīą°ļŗŸ°ŸļŸĩ_°_r0Cī6‡âđ;ŌŖ™ŽlĮ8ĮDĮ$ĮdGšŖÆvÄŗqĮ\Į|Į"ĮRĮ ĮjĮzĮFĮG—ŖÛŅã8ā8ė8æ8á8í8įp\vrN­“wĘN§Ķį 9G:Į8ŗœšÎ|gĄŗÄYęŦq†1į,gÜŲæ\ā\ė\æ\é\ëėtnrnuîpîqö:::O8O;Ī:/8/š—ÆĨw‰.›Kqų]éŽQŽLWļ+ßE"Û5ÁUč*vMv•ģj\aWĖ5ËwÍuÍw-r-uu¸Ö¸:]›]]Ž=ŽũŽÃŽcŽŽĶŽŗŽ ŽK ŖhŊ"*6EQüJē2JÉT˛•­Īč3ûė>/čËđöõåøÆų |Ežß_Ĩoš/âkōĩøZ}mžžÅžežßjß:ßßfß6ß.ß>ß~ß!ßQßqß)_ŋī‚īrĒ:UŸjNuĻúS3RĮ¤f§ŽK-H-J-I’Z™:-5’Ú”Ú’ÚšÚ–ē uqę˛Ô•ŠkS7¤nIŨ‘ē/õ@ę‘ÔžÔ“ŠgRĪĨ¤^ös~­ßč7ûí~?čĪđöõįøĮų üEū˙Ĩš?âoōˇø[ũķü‹üËü+ũkũü[ü;ü{üŊūƒū#ū>˙I˙˙9˙€˙r€ hƀ9`xÁ@F`L +ČL L <( TęŅ@s`v`N`^ =°$°<°2°&°>°1°%ĐčôŽNNÎ..™ &¨ŠA[P úƒÁ1Áėā¸āÄ`qpJ°:XŒ›ƒŗƒs‚ķ‚íŽEÁ%ÁÁÕÁõÁMÁmÁî`ođPđhđxđT°?x>x1 )ą4uš>͜æLķ§e¤IËN—VV”V’6%­2mZZÄ}0­)mvÚÜ´iKŌV¤­N[Ÿļ)mkڎ´=iŊiĶŽ¤õĨL;“v.m írˆ iCƐ9dyBÁPFhthl('4.T* •„Ļ„*CĶB‘PS¨%Ôj --- u„V‡Ö…6„6‡ļ…v…ö…ö‡…ކއN…úCįCoAˇ¨oIž…‡ŠíYúm;ĸŋS’H˙­›žūnŒEŲȂrĐŨȎ& äCá {ŅdDSāJQúũ÷zˇĶ‹—ĻŖ'`ÅoЋ¨­AëĐèŊķŪDoŖôzÕŖŊhŠĄ8šP/3Їč jF‡Đŋĸô'8æ OŅIô:ĮĪĐ8æŖ~tŪ0ƒ–`Ņ ø<­Åˇâ[ŅĢøģx,ęÄY8mÆšxÂåx*:„kņŖč(ŽĮĶҟđ üStŠÉd2Ņ_™ÛĄž>Ī<ČT  L 3cf)ŗk™W™Wqŗ‘y'3o0o`=ķ&ŗ˜mĖ6lbz˜Ė3Ÿ1Ÿa9Åü‹Ėŋ1˙†ÍĖŋ3ũØÂœcÎa+‹YŒmŦĖĘ8…u°NlgVÁNÖÃzą‹ °ė†ęjöĘ ßÁ¯įāqüAū~”?* Aƒ)h^!,–ãÕ áyüšđ‚°˙^xQxŋ.ŧ,ŧŽ7 ooāÂ[Â[x—đļ°ŋ+| Įû„ĸosTLã̘/Žgėâ=â§ø#q2㧈S˜PE•3#ÅJą†šU|B|‚#ūT|’ųžø”ØÎŒ‰ŋ`Æ‰Ī‰˜|qŖøS-ą’Ži• ’‘ųšÄKķŦd–ĖĖB¨¨üĖ"¨Š‚ĖķRē”Ît@u5ŠyAʓ`VKJĶ™-Ō Šų*ŠMĖԟ¤>æœôŌYæ<ŧs$3ĖzķH6 ę§Z6Ãü¨y[mî°`v™…ĩøŲK– e§Xę,-\…eļeŗ´[:¸§,+-/sK Ū9­°|j9Î=o9a9ÁŊÕĪ)nĨå´å ÷ĸĨßōWnåo–ŋqëe‡ėā^•Se?×)å ˇAÉéÜīå y$ˇ jŖīsoʡɡqī@]TÎuÉĩō4nģ\'×q;å°ávÉQ9Æí†zé'Ü>ųqųqn?D‡_Ŗø:Å7)î ØMqÅ^ŠāŊÎ RTá›ßĻx„üŠčĩ†ĘÖĐ9:G3ÔßMqÅ^Šd•–ÎŅŌíPΟ“h2•–LĨ%õė ØMqÅ^Šd­ŽÎŅS ēĘ@¯MôÚD51Q &ÚĪSų<åéZžŽōT>OåķT>>BgJCø6E"ĮL{ĖT‚™ö›iŋ…^[čĩLšd:SĻ3eĘ%S.™rɔKĢ$Œ6ēĘFWŲč*o§ũvÚo§ũvÚī =Ęë 6ywRÜHq3ÅíßĨ¸—âû?„Ũ¤s_ĸøÔnϏ•â'€OSŠOĶҧéčĶtôi*õi*õi*õi:˙:įÚķĖPOā<Ēûn*m7•ļ›ÎÜMuÜMĨíĻŌv“ĩIÉtôįÔĸ čŊ. ×ítm;ÕĄŽm§ũ Šä…tt!]ģŽ.¤’RÉ ŠV ņG€ĮčĖÅC¸•"‘ķíyŽJxŽö˙Wy_fEqĩ]˘¸ŨÕŨˇš†ëČ&˛ {ؐePGTtÆ .;Č ›(Fđû’¸åWƒ‘ Ŧ3 c"F1jPPAQqá;õv‚äûķü_ō|ųŸ<ũđÖšÕUᜎĒnĶ=ˇ›¨_ Ŧ„•J´ŠD›JXŠ„•JXŠ„•JcÚÖjôZ^ĢŅk5Ú¯EũZÔ¯EũZÔ¯CÍ:X_§Į×Đ- ŸnnîîîŌÜjDÛĻĀdƒ›[€Zk äTčNE›T´I5õģ€{€û€ēf†p0¨Ąąá¨Ī„ļLhË45ۀģ€{€û€ēo-´Š uĐ ßXnAļ≠ęmčˇqÖF_gm跡߆~[=Š–ŽÁ-@­ĮC ę=ÔG G û°åŖĨ–>lų°åÖ[žžmBm1Š^QôŠĸWí륞ę륞ęëŖĻ>ėÖ×c"˛õ7\´æˆŲ„]€šĀ<`~€ZÉs PS ę Q_„šb` °X Zކ\ j+e Âeúû'–띈P{õ °5Ģqö!´|Q6'Ü­‰j͗Pk{ō>Ôī×m$CËotŦ¯ÛHĻÛHš4}–…Âs€ã€ã“€Sŗž5­ĻgggãüË8ŸlPëJÆ~œ ÉИ ÉИl4ĻŖm:ä°ÁqĀņ‰ĀI@Ũ/ô oÔãAø¤FŨƒäíĩĪ Ž-EËGMÍvČēMÔā8ėųÚ㙨™ ,N&€“ąĢo6­Ļ§gīÅų}8?ĮāXėÜÛ —'@­qŽŅX…ļ /58XœLuŋĨAŋđõŒ>ĄQ÷ yd­ŖŌ Ž?€–Đō€ŠŲYˇYkp,öIė~ē†p,°8˜NÆN¸Ų´š œœ ŧį1<ÕāXėsÛ —'@­1ÕhĖDÛLČļÁąĀrā`¨ûŲA?‹k–„OhÔ=HŪYëđ ęú´LAËSŗ ˛nSĪāXė$zCČԁa G(uæAYGP>mĘŗõOâ;œņ7‘4ĻBCĻFëA]cÍCMĒÉą[Zq`ļūö@N†œ9rr˛‚Ŧ {=ČiĐLöņ= ŧĄ ÍäeAmā[4ČZCĪ&!īIÂēH U6‡o5ƒ<õ5Q_Ņģfh'žß{ÁZ—Č^ŠVc1܂ŧ,Åä§{á™–Ķ + ™WZHĮčtj/u~ ™ĸU&,Ö"YRVēuĩ‚:XĒļĩĄˇ6Îց\'Ņ˛<Õ#đ´)wŖ <Ī-ƒēˇ V áģ‚.gœ!uųģ „UmÜ@F/žzĄ­Ā*āNŦ™íf íÅhD°3EĐ͇Ŧ`Vr]“Ãj9† 0†ŗ1ؘ‰ § ¸XI¨ŸxŪEą5(Ÿ2åŲú'°‡íĄˆ”:Ã|y×SÖirÍ ö |ZdßXĩŗ0ŗ0ÂĢŲ¨™šŲXŠŗ1֔]ƒ¯.‘cNæčŲ°G ˜ĢGVž‚ņķĄk>Æ}>VęĖ^ÖkFT’^9‹Đvė.ÆúXlVÎâ ö*ĐŖ#]K / d´\5÷§Lš åfL˙—ÔŊ—Ģ„ŧ #Ŧu­Ā™8C8Ƒ>ņיÎÂõš•°ŧ­WÂĮJŦĶJ0­„/•Æ—JŦÁVa‡\…žĢĄe5ä5×˜|\Ë됉¯ÃŲu°1?°„6̑םÎ }BxTžUO#E“-Člˇ ˙܂œVŽÎõHč\R >Ÿ­Q(8_#X/”ˇīA^Ŋš1ĄĒŖkė͍I7š1Ž ôz$|TĮ'ČŠ3!gBļ!ې]Č.d˛9škčŅÖš4ŧąƒĩLePøV/¸ÚĐk™×D–c§å-á[Jp}úÔ§ ŖNŅsŖ¯)Ā:5XäņN ͞ēųtššŽØĪ´œ]Ș3B¸–Đ+Z_W@GíŅĒ6,ęũTjÔk‹× ę`)ŒļačEfGcŠe+ŅŌ‚§v°ŠPîBų„™§ā›‚&…Ūn€°ęō=ЅŊ”Ž,ôgŧ`Eë:´ˆā\$Ņ:}Ŋĸ Ģ€;ąV_ü`EķēČRęĸgZ1ō䘚æxWújŖ>ÎևŒĀÚDqíRX+ēJˇ-pUŋnžÜÜ Ü Ü Ŧū¸EŖŪK_Ö:¤FŌ”[Mų‚)ˇ›rŗ)wšrˇ)Iģ“ĄŊ!Ü |¸¸¸¸¨Ŋɂ÷Yđ> ŪgÁī,øŋŗāq<ŽŖ}íãhÛ8zÅŅ+Ž^qčŖo<č †qÃ0nÆ Ã¸a7 ã†aÜ0Œ†­Ā°ļÃV`Ø [a+0l˛áq6<ΆĮŲđ8gÃãlxœmÚ˙¸י{z~šBOSči =MĄĄ)44…†ĻčÛ}›ál ƒ;¸j…•hŲ-[ĀJ XɁ•XɁ•x›=9Г=9Г=9ГƒņÍ1ã›cÆ7ĮŒoŽß3ž9f|sĖøæ˜ņ-Åø–b|K1žĨßRŒo)Æˇã[ ē„͍_MøäÇ|øōáC>|ȇųđ!_˙vžp/đEāŗĀįĐ :1âųņč/€ūč/€æh.€æh(€†h?m !ĸo!ú¡Âā,4BC!|+„o…đ­žBs!4¡BøVߊ ŋú‹ ŋú‹ ŋú‹ ŋú‹ ­ڊ ­ķ_dÖS‘YOEf=™õTdÖS‘YOEf=™õTdÖS‘YOEf=ÃŋbøW ˙Šá_1ü+†Åđ¯ūÃŋbøW ˙ŠÁļl‹ĄģØøZl|-6ž_‹¯ÅÆ×bãk1|ŽĐ>šBûDø„ŗy?á&ČŋîÁęânvĩ–Ŋ&‘‹‹Ŋæ.÷ –˙< |¸g÷ų5Â7!ŋ üúOˆšī`åöā,đŒšOž¨eüũG6*`ÎĻ™ŦČZ-BÎjŗ$æŗAl1Ģæú‰qŲŠfŒEyœŽ9[ķN<—÷åų`~3/åŖųD>ƒĪįËøū˙˙5˙­ž'Į_åoōƒü0?Æŋ ž/đjū ˙3—j>å§Hm˛¨-<‘%‹–ĸƒč.z‹ĢD‘&JÄ]bŧ˜&f‹…b‹Š!âq‡'ĻPÍbąRŦ›ÄcâąUė/‹ũâmqH_ŌØÔ™Ō‘õdļl*[RĪoeHĻK[FŠĻ™l#;Ë<ŲO’7ĘbY&ĮČIr–\(—Ëuōaų+ų”Ü"ˇËjų2‹Ę5ō!ų ųkų[ĒŲ+_•oʃō¨&2:ĶŪ[ĚđųQū ?Íŋáßķ3BŠ$QS¤Š ‘)ꈰPÂķeY"o“ĨrílˇË;äō.yˇ-īĄ}nŦ'Ëåx9AN”‰¤,ë˛Đ„âGøĮü˙šËŋB„D ‘"ŌDē¨E;ŗ%l1Oļ§Ũōy­ŧNÉëå`yí7É!r¨&‡Ë›åÚIGĘ[ä­Iõ­MxÆzŽd7ažšn|ÁįŸ×ΐxxŽ$x†¤5žĪfhą[ūŪ´ĐO™ÄM ZáūŖĩ5ōa<}{ļ-gŠL?ޟÚ˛Jîa!ŠdÁÛĢkȚËM”E0Ú튤ÜˆĪëuIŸ×ËGĖįG‚ĪÔ+›Õ’Ģäjš€âÄ"šXVČ%rŠ\&īŖ¨ąBŽ”•Ô&¤ĮŒüҜE_˛tų¤|’fS°ž,.ģÉ+dŲSö–}epąŅ쨄åÃøpĘũFđb>’ßÂoå%ü6ĘGņ2~;ŋƒßÉīâwS^xÃĮōqŧœį(KLđI|2ŸÂ§ōi|:åŒ3ų,ū(eƒŸņ/øIQ,FRĻw+厉R1J”‰Û)ī쓞ÁģÅhq#ÆRXNšá1Q$Ä$1™rŠ”)N3ÄL1K,§Ä×â´øF|+žߋ3ÖšRR˜DŲaM™,SdĒLŖŦ0ƒrÅZ˛ļŦ#ÃŌĸQQæčJOF¤/ëRÆŖ<˛žĖ’ ôo_)ŧT6”dcŲ„rËËÔ1uB}Š>SŸĢ“ęˆZ~?üAøPøÃđáđ‘đŅđGáÃŸ„…‡? >ū<üEødøËđWáSá¯Ã§Ã߄ŋ ū>|Æbˇ„%­•dÕ°jZÉVŠ•jĨYéV†•iÕ˛j[u,eųöû-û/öÛö;öģö_íƒö{öûöö!ûCû°}Ä>jdlbŗÛŸÚŸŲ'ėĪí/ė“ö—öWö)ûkû´ũũ­ũgė3Š–”’*¤’T US%Ģ•ĒŌTēĘP™úˇąĒŽūmŦ˛ép”KGDųĒŽŠĒ˜Ē§ęĢ,Õ@ÅÕ%*[]ĒĒFĒąjĸšĒËT3ušjŽZ¨–Ē•j­rTÕVĩSíUÕQuR?SUÕUuSyǧęĨōUoÕGõUWĒ~Ē@õWÔUęj5PĒAęu­ēNŠëÕ`uƒēQŨ¤†¨Ąj˜ŽnV#TąŠnQˇĒu›*UŖT™ē]ŨĄîTwŠģÕhuŖÆĒqĒ\WÔD•P“Ôd5EMUĶÔt5CÍTŗÔŊjæĒyjžZ –Ēeę>ĩ\­P+UĨZĨÖ¨îę •Ģz¨…j‘ZŦ*ÔuT}¤>VŸč_üĒ/ÔWÎĮÎ'Î1į¸ķŠķ™sÂųÜųÂ9é|åœržvN;ß8ß:ß9ß;g\ær7ÍMw3ÜLˇ–[Û­ã†]ËĩiH×u=7âún]7ęÖsëģYn7î^âfģ—ē Ũ–n+ˇĩ›ãļqÛēíÜöîĪÜÎnWˇ›ÛŨŊÂÍu{¸ynOˇ—›īöqûģÜĢÜĢŨnĄ{{­{[ä^īvopotor‡¸CŨaîÍîˇØéŪâŪꖸˇšĨ^ž×ÛëãõõŽôúy^o€w•wĩ7Đ+ôy×x×z×yEŪõŪ`īīFī&oˆ7Ôæ ÷nöFxÅŪHīīV¯D˙ļØå•yˇ{wxwzwyw{ŖŊ{ŧ1ŪXoœWî÷&xŊ„7ɛėMņĻzĶŧéŪ oĻ7ËģכíÍņæzķŧųūGūĮū'ū1˙¸˙Š˙™Â˙Ü˙Â?éåŸōŋŽÛ?ē,vyŦyŦEŦeŦUŦuŦ[ŦwŦOŦoŒō%Ö xΔ˙Š˙ŠMĨëËãl?Á?g3đäé,1_Ėgņüé&<ú&ž?=€įOßÂķ§Áķ§oãųĶwđüéģxūô¯xūô ž?­mŅtņ:xū4ŦßmĀĢí=ö>ūž6ũƒūå7Ãqœ–ü˜ĶÉšM¤ā™ĶŽū>˙u1ŲÃ? æâ™ĶŅĨŅĨba,k$ÅēÆēŠûbŨc=Ärä65(ˇŅoélÃ:°<!‡¸ČÍD ĄßWŖß2Ԙø6e-Y;֙>u ĢķNŦ;ë…wĶpÖĪųīm‰;0Ąö¸”gĒŊn'ÂŨ.¨īMōoÜž@CĩÕíGø‚[@¸Ũ„6Ã5žķĒ1kN^ufšČێđ üažQ?ëŒŋUzøûkŽv´LĨūTĮ|Ēķ˙ ŠéÛįM"iŠØō“Hųŋ'˙—ĸä˙OŅ‘ŦŒ$Į‹{~ˆ’äíu4ÖM”üscĨũŊbJ(Ĩ<Õ1ķŠ–‡tŗ?ĸˆÔōEGƒ¨xæīŒ‡Î˙%ū4 ļĄŊĢ5ÅĀĸßŲØō¯ˆuK)vˇ; WĢ5”{ŧŦCg:ß8jQ˂|C-§lãsû¤ę¨s ÕÉ>m"%EIwŒ;Öį–ģãŨ îD7áNrgģsÜšîā>xŅØúå˙ ēÆūŽøÚÁíčvB”írŅ8ۛ"m_÷JˇŸ[p^Äô7cîđPÔ=?æ˙GD]Uí÷b-Ô}˙}üĨ¨ûCÄ텷T3ļífÉŦšíe=ØKėOTŋŸfØG<‰ŨŒx÷ũhFųŸgGëüŖõÜūÜ|ūsėĩ4øŗĻŊŗņûįō Æ~ ú—ŞfŦ5ëĀ7R~đ0ŽÄÁÔOđŸãɀ:_ā.ä˙‰gô ‹Í¨Ŋrnt ŧ‰ÍúŅ;Ķ~Įļ˛įŲ lÛÎv°l۟Ļß§đÛGk[ŋGí ;ēÂ{Ã/ž{Zcŧ­ŖŨŲîcØ×Ø×Ú×ÛCėÛ.xÚ4{ē=Ã^ĒŸíˇ—Û+ė•”­ļרkíuöũözûûq{ySÚFõxš~ķŲŧņėÜ[Î"y‘"ŧŲLŋõ7ÍeG)?\lŋŦÖ̇ÔcŪcŪ“úÍA¸[]EOĘaõį…ĪŅhS>G2åssŋĻīT]ũÖ#Šû7Ķ7črēū™Dņt6}ƒēŅu΃ŦwYŽåcFĻņĻÆŗęžĢšĐfĐ"bEÔG˙>ŖĘŠŠgėJĩé‚7īĸ}Zē=æÜ›iå=Wō'ųĶüYū;ũŒßË_æ¯ōũüMëRŲÂĒoeY Ŧ¸u‰•m5ļš[-Ŧ–V+Ģĩ•cĩąÚZíŦöVĢŖÕÉęeå[Ŋ­>V_ëJ̟U`õˇXWYW[ÃãÂãÃÓÂSÂĶÂ3ÂŗÂŗ­ÎVwku­uŊuƒu“5Ôn°FZ%ūķū6‡ŋ˯ō÷X ­F˙ÂīšÄģ§­\̇•gõ<īÍw4C™C؏Ū#GæÃæ/—$ŸųĶy“Ŧ˙(Č0õ§ôō;~Ŗī}î÷5Õ÷û(~”ˆRQ&F‹r‘H’JŸŋčA+đŧƒ´œÄzĐz=ī Ģ?š^pč{‘į9?=č{pŪA\ūÆAWōįÄųüŖôb}ˇÎ;h”Î?Ļáøáķč Ž1t”˙#qąƒžĮįc.8f_pŦ:˙ø—Œâœufu)ļ^xũäō“ãâqFĐzĖ&Œs4˛z,]žbÕŗŲöīÕęįęqīqī×^•÷û˙vĮe˙o, endstream endobj 78 0 obj <> stream xœ}TˎŖ0ŧķ>ÎF`;Ø E–fH"å°mfOĢ=p˛HƒrČ߯ŨÅ<ÂJA VáĒŽîļ;iĩ]m]7˛ôģī›ŲĄs­ˇįūâËööØšDpÖvÍ8!z7§zHŌ Ū]ĪŖ=mŨĄO–K–ū›įŅ_ŲÃsÛīíc’~ķ­õ;˛‡ŸÕ.āŨeūړu#ËcXk!ЗzøZŸ,KIö´mÃ~7^Ÿ‚æƒņz,„9’iú֞‡ēąžvG›,ŗđļ܄Į$ÖĩŗũIĩ?|Đe ‡eaد°ŠŒā‚›ßQūF”oēæOíŖŒ đ¤!T- ¤ BæįĀ&ZŽeeîækĸå0TĐædČ3$Ŗø]Cžm"s”¨ÄĄžrū ,8ėUNH ^ĨåS@ 4é  PIHm€Ēû‰jj ×čŖēíL1OTO˛(JB:Ē€Ą‰j „D5 |Á1hXq ”Ti ōsÚbžļˉ&ķWI"žX‡Ģ?¯ą[ BĖ›ĸøŦ(Š$hëģ’A5úŽåsP1ZâØÛbēxōÆ"ŽLœė÷yl.Ū‡Q¤ņ§ŒĶ×9ûū1ôCTÅß?ZN1ˆ endstream endobj 79 0 obj <> stream xœė}|TÅÚū;įlKŲdS Ų„Ũ°$„N@$ )ô’Å$´„ŠAjč`l¨Q,×ŪąP7`/Øģbŋ*ÖĢW°]õzAČ÷Ėyw Á(xŋûûûŨ˙oßä9Ī3īĖŧgfΘYM "rābĸŠ‚Qų%CÖĨŧCâĢ ĸØī FMČûøŨûŸ&ņé<"ËĪ“‹ŗû_÷øā}DâÔǍZXšxÂ[Qם|)‘ųōĒËŨ;ŋ=ˆčæ|¤Ŧ]maåĒÅ}ėƒ‘ö‘{aÍōĘĢOßŧ‚ÄĄ¤Ī<ĨraÍõûœMĸy7Qße‹-[ŪꤍčĪY~ņԚÅqsģv&ÚPŠÛ}Er,,Ã>JŠ}áĢŲŅäÎ6’öĀWë^üÖØ•“ė?Øļ׆{RiĆz:DbWøæû÷oÛkDjcˇJŗ'ŨHZE:j:(›ÎÆ(Æ}5äęĻ,q™ÉfžĘ<!ģ0ë¯ĐFl¤E›5M3éšéSęĶú(u[k´6ąØí&/ҎÁzŊ–á&Ņ*ķôæ(ŲSŠ7EixûFōĐŋaĻ|ĒėĐŋ—ļļMë_´O˙–éwĶVs$M˙wÚō{fęūû÷ˇŧ…ûö<ž6ķ^];ú7ēC˙įŨ6mîJwũ'ڂ¸]ŽYæĖž?h&Ũ¨?O ;ĖĢÁŧnŋĄ}ú7Û1…n4NuŋЎęH}ą÷÷c!?æWqŸå:ڞŽëZ,¸īÅį™îĸÚcĩģŨŊžä8Ļ&ĒÕ÷5“il‡uĘ(ĨŨ=7Ņ 䞖4¤Ã¸gûšęoЌ?r/iρt•>‡Ę;Ęŗ.ĸrËû€ā|”­hwŋ4ķxîĄ-ĄtËՔn{ƒŌM[ ¯ ęá”~<õ-+ޝ\ģ:a¸GŪ¯ī!c™öņYģSēž‹]˙čž}W)-Ū¤ŗŽÕ”šĒ#ŋ~sģ8–ąTĶUmī÷ĢļätüĖ~ŗ|0–ė—ö\û¸zuTĮ|O{ŋvĨĩ‹ų9Ĩ™ęÛû:ŧ7ʘã(Í:žŌ,īģŧ,ƒv^zŦríÚÚōëg(Mŋēū‘8ÚVĘ×>Ŗ:m’Ác´-ŖnÚÔSû’ęDŋ#‘Žŗ¨Î4 e?7P`ÔCņ¸/ŸGÖ1Úņ õ2ęm$×iĪĢa^“xéĪnEČB˛ąi׈đßĖĢ }úģŅš™Žč(gŊs€,ÅA_ÖŋŨžex‡Į]~!Ŧų•ßKgkŽUîxLDį™ëņ~†ŗŌ?˙˙93]JŗĩįÉŖīĨ9@‘É×ņģŸ‡Į÷Kš€ģÃry4WÛDõĶh†žŒĘô­”ŅQ9œ!Ļ›€`0ė¸Ú×ũ¸ÛסÃûĻá,ņ3Î~¯ŨM#ĩ÷Ž}RPúŋŠĢ]G9âGę§MĨáÚXꭍŖøŽĘoš?Ãô_¨æĪnCČBöÅLũą˙öŅ‘i?ōÛĐ-TŽ§ ´;€ķé=ŋũgQm]`Üs!ōęi“á{›}†n Bí$2éņtRģzįĩ˙o'! YČB˛…,d! YČB˛˙M}ÆTĻ>gú8>gÎ ~ŪėčsfČB˛…,d! YČB˛…,d! YČūߚøÍŸrYČB˛…,d! YČB˛…,d! YČūĶS,0H2€cūÛ,øĩ4˜LĸĪņ×nŊã?Ũž…,d! YČB˛…,d! YČB˛…,d! YČB˛…,d! YČØZī˙ŗ[˛ũÉĻ‘ÂIJ|€”ž›Lâq8JČKfã_RļSWĘ ԇŌ0A#i4§I4…|TFķŠžVŌjÚLwS3íũR{ĨöK:$uXęˆÔ‘î0ˇÃŨÅŊØŊÜ}Ļû‚ô˜Zŋ…¨nDíNŊŠo0jĸNDÔbD­¤“G Ŗö5ĸET¯5ňēŪŊ Q QEëh˙R}ŽęŠ&këXëßĩū“Ŧ¸Ÿņ÷>Ä81ŠĩJ{R/ÔGĐ ¯Înûĩ/ķ“9DŸlüd#Žg}x"QđolMis•6.Č€I4ë˜#=ĩÍUūe]§_v–R,%aü3(QĒižĐD´pˆdŅEtSDš˜)ęÄ"Q/Vˆõâ\qž8_\$ŽÛõYúlŊʤ›LbÉl˛˜Ŧ&›)Ld‰>b´ČãÉ"~2îöĶŅ% i-ø7Å4ú}ãšF[ƒæJ`ƒ~j0%ÛOú^}Ÿūĩūū푊zđ_92ņŋ oŽô“m8oü ķ\Öč}0ī¨ū×c_‡Ŗ˙cĀĶĮčןoúQI<͎ŠÉ§,Quė ĸ6´n˙ë×-yË7žĩ|ŲŌ%‹˛°îäķįÍ­­Šž3{ÖĖĶËËJ}%ÅS‹ĻLž4qÂøqcĮŒ.,ČĪ5Ō›;âÄá' š3dđ ė>Ŋ{uĪHīæéęJŠqDÛ#ÂÃlV‹Ų¤k‚zx +ÜūŒ ŋ)Ã3fLo™öTÂQŲÆQáwÃUØžŒß]asˇ/éEÉÚŖJzš¤÷pIáp§áŊ{š BBë^0ŦI#›]ŪÖ¯§TVû§•ä;ĶŌĘ åąü–<ŋՈåž/ÛLįš›z=Úx~‹ƒæTdEV{Ē+g”úõJTjÔ ĪöĮdų{xōũ=Ö|š„.×ø{yō üY?õđ „ßœîđ¸$4Ūŗoo{OeĐcIwüHRĘ.&ä+MhZˆūĨĨÉļœ×âĨ9HøŠJ9íĻ9ÎyŗŗĘüZ…ĖyTå$ødNƒĘ9\Ŋ“&UAEđ{Åŧ$Ãwī^}ã;ßČwûõŒŠ9Uķ$WÖ4zōķyÜJJũŪ|oe°¯M}ŗQž˛˜/‡Ą¨ÔŸíYė÷Œâp¸å3˜_\jT VķĮįųŠĸ*X˟]/Ûå.hŦČįĘXžĸŌ4 uOĶ@ˇŗyļą2ŲbJFAciu­ßUáŦÆüŦu—:ĶüŪ2 _™§´ĻL>%Ãßcn—fÜҍ…žUZ–=ˇĻÛÜĨšS/“O w!.žQÑáĀã2’ō‰Žî.NRÅp—` ŠÚÅABOĪ#ŗtY5oŒ3­,íwšä ļɜîˇĩ‰å€ãp›ø>ŋŲ4.-ÔÃ]P“ßĻí‚šƒ F븝š‹āQÃ&į•Ĩ§cå§!Œá’O1Éí§)îRO§Ėƒ9äR*û&ĮÚxžã‹=ã‹ĘK§œ%%íRœŸÃ)?Ĩ![%´<ĖÁÂ,§zŦFz´‘>œsTöX•í‘íjlŦn"=]Neg“0„9īŧ2˙äŦ2N–'Mļŗw¯&EĻ•Täa­bģķVzđō*lŦlim˜ĶØäõ6..¨˜7 ëĸŅ3ļēŅS\:Üi4~jézįyīX/ƗŒB(F5yÄ9EM^qNqyéN‘ûœ’Ō€&´ŧŠQeMŨWēĶ€áÕ¤W:eÂ-2ŌT$lFyįN/Qƒ‘k2FēĒEáŗ)Ÿ Ē}žQ†q#/NU-&ÎņĒŌ&ølėkāŌŨƒĨmČqȜû /22ؚH°7ÜėĩyÃŧ‘š]ÐJWžûQ6LPs¤° gbN5Ü-ĸĄ)ĖëÜiDš,Ų€’Ō×p؇–Ëbmá~ÜqߑøĘK›# ņ+JŒ’†Y˜4sī“wĩœëĘæ5V”ÉŨƒ1Wņ-üÂ3‚üšgZl‰ô‡{jFų#<Ŗ¤?WúsŲo‘~+fžHxØrĶmŦđ`#Ɗ)%§āĩĻːî–ÖÖ’Ō´ûĘŌ°–fåĨū°,ŧÜĖéãPn´DÜŖũ U•˛ä+•u­écĢʰ.U@ëC„°`”(4ęČõ†JU˜k•C­ŖĄĖ_–%oZ:ŋĖX¯?ņ ķ[28Ļ9CŪ(ģŦ1ÖĶߨ|°ÖÃĶĪ–†ļQq){œHâfeiîę2Y MžbėeŋYH´)$_ĶFđFĮ *%‚)~˜ūší“ķ' %pLīÃgtEîĩ˜+ œū:ĖLUD>wŖÛáæ‘Ŗōh‰ <¤ÃËĶŗN.š†*wéLv,Ŧh,l”GÔĒĘā°īä?%Ģ]HŦ Éƒ@˛;ū†)îŠ2wŽĻĸ¨4-͉Õv×âœęŠ”¯‚)ܟ)åÆQĨ˛QNqÂIĨĖéˇâÅT[YãIÃÄ/w }ŲFSpؐŗąŅĶč7Öm! #|–ŨXIø^œåŠŦ‘GčZy‚Ž1ęĸšÆčČhÎÖr ÜÆXbā°õ͑—ĒFy@ŸY‘…‘ˆiŒmtmÄ<oSFÕ´ ŧĒäÉm<ęJ'R„ą2U†@\0,]ä% [ŗ0ĢiĻ5ũˆĮø^”Å…mFT´ljŠŠ*bŦ')–dųĩN9ȔSËKÕ>ĨËėą^/f•SÖvûĩ’Ōāã1ꏕUęq5xŒwHp}~Û¨÷Đ 'Æô7ũx9č#‹ĩg´§(‡\ÚĶA~Ÿr´wɧŊ~ üvßŋŪ ~üøUđ#ā‡ÁÄc“ö Jũ°Ēnvf:‘E ž xíqĘǁåĀĨ€eFŪ-ˆ(Č­š-,IŒÃ=C‰Ķ•8M‰%NUbƒë•X§ÄZ%Ö(ąZ‰UJŦTb…õJ,Wb™K”XŦÄ"%NQbĄuJœŦÄ%æ+1O‰šJÔ*QŖDĩUJĖQĸR‰ %f+1K‰™JĖPbēåJ”)QĒÄIJLS§D‰ÅJLUĸH‰)JLVb’•˜ Äx%Æ)1V‰1JŒVĸP‰%ō•ČSb”#•đ*‘ĢÄ%NTb¸'(1L‰ĄJä(1D‰ÁJ Rb ”č¯D?%ú*‘­D%z+ŅK‰,%z*ŅC‰îJd*‘ĄDēŨ”đ(ŅU‰4%ÜJ¸”čĸDĒ)J8•HVĸŗIJtR"Q‰%╈S"V‰%JD+Ĩ„]‰H%"”W"L ›V%,J˜•0)Ą+Ą)!”  ­JRâ ŋ(q@‰ũJüK‰Ÿ•ø§?)ņŖ?(ņ%žWâ;%žUâ%žVbŸ{•øJ‰ŋ+ņĨ_(ņ7%>Wâ3%>Uâ%>Vâ#%ö(ņĄ(ņžUâ=%ŪUâ%ŪVâ-%ŪTâ %v+ņē¯)ņǝ(ņ˛/)ņĸ/(ņŧĪ)ņŦĪ(ņ´O)ņ¤ģ”xB‰Į•xL‰G•xD‰‡•xH‰•x@‰û•ØŠD‹;”¸O‰íJlSĸY‰€MJø•¸W‰{”¸[‰­JlQâ.%îTâ%nWâ6%nUâ%nVâ&%nTbŗ7(qŊ×)q­×(qĩW)qĨW(qš—)qŠ—(ņ%.Vâ"%.Tâ%6)qžį)Ņ¨ÄšJœŖÄŲJlTâ,%ÔąG¨cPĮĄŽ=B{„:öuėęØ#ÔąG¨cPĮĄŽ=B{„:öuėęØ#ÔąG¨cXĒ„:˙uūęü#ÔųG¨ķPįĄÎ?B„:˙uūęü#ÔųG¨ķPįĄÎ?B„:˙uūęü#ÔųG¨ķPįĄÎ?B„:˙uūęü#ÔųG¨ķPįĄÎ?B„:öuėęØ#ÔiG¨ĶŽP§ĄN;Bv„:íuÚę´#ÔiGä5KŅĸč2…3s KčtNč2 ÔĀŠS™6ēD‚ÖsjĶZĻ5LĢŠ#AĢŠy •L+˜ę9o9§–1-eį’@ę(ĐbĻEL§p‘…LuL'R @ ˜æ3ÍcšËTHÉÕpǚЊiS%SĶlĻY\o&§f0Mg*g*c*e:‰i“Š„Š˜i*SĶĻÉL“˜&2M`Ī4.ā Ë4&āÍTpŽœ@ųLyLŖ8o$×ķ2årŊL'2 į’'0 ãęC™r˜†0 fÄÁ2 ā(ũ™ú1õå`ŲL}¸^oĻ^LYL=™z0ugĘäĐLéŗ“‡Š+‡Ncrs=SĻTĻ&'Sr y¨3SR y2¨S";˜âŲĮËÃyĻhvF1Ų™"9/‚)œ)ŒķlLV&K ķ9ĐšdbŌŲŠqJ0‘Aĸ•éQDäÔ/L˜ösŪŋ8õ3Ķ?™~bú1Tú!T ú§žgúŽé[Îû†S_3ícÚËy_1ũ_2}Áô7ĻĪšČgœú”SŸpęcĻ˜öpŪ‡L°ķ}Ļŋ2ŊĮô.y‡So3ŊčtčÍ@§i 7˜vŗķuĻט^ez…‹ŧĖô;_dzéyĻį¸ČŗLΰķiϧ˜ždÚÅô—|œS1=Ęôį=Ėô;dz€é~ĻL-\r§îcÚδŠ9˜ §ƒš˜üL÷2ŨÃt7ĶVĻ-Lwą_‹;9ĘLˇsŪmLˇ2ŨÂt3ĶML72mf灃]ĪQŽcē–ķŽaēšé*Ļ+šÂœēœé2ĻK9īŽōĻ‹9ī"Ļ ™.`ÚÄt>—<SLį2Ãt6ĶÆ@B%čŦ@ÂЙLgjA§3Hđ ،Ŋ„Á  Lëšú:ގ–iM Ą´šĢ¯bZÉ´‚Šži9Ķ2Ŋ”Ģ/aZH¨-â`§pɅLuL'3-`šĪõæ1Íå–ÕrõĻj.YÅ4‡Š’Š‚i6Ķ,îôLnŲ ĻéÜér]Æ7*e:‰›;oäã(%LÅLS™Šņ^Д@ŧŧÃä@ŧœŪ“ņg€&â{ƒ&p‘ņLãņ8ˆąœÃ4š…ø  ‚@üŲ ü@üŠ ŧ@|hT ļ4’É˔Ë4"‹÷ģ8‘SÃ1e ˜†bäÔʔˆ ˆ) ĔƒqŪ@ρ˜^ ū\˛_ FvŦo FŽÍlĻ>\Ŋ7ߥSëÉԃƒugĘdĘ`JÄČQęÆäá˜]9fssSŽ—Ę”ÂädJfępĖ%ŗ@ŽŲ DĻĻxĻ8ĻXŽÃėŒfŠb˛3ErÉ.ÎÎ0&“•ÉÂ%Í\ŌÄNIcLämžã’8]å:]íúú°ø|?Ã÷Oā'āGāø˙|ŧīūøøØ˙^ā+äũé//€ŋŸGÍu}5Īõ)đ đ1đ|{Āī#ũWđ{ĀģĀ;ĀÛö“]oŲûšŪŋa¯síļg¸^^ƒ~՞åzxx ų/Â÷‚}Ąëyč᠟…~ÆžĀõ´}žë)û<דöšŽ]¨ûâ=<x[ÅõāaāĄČ%Ž#—ēˆ\æē?ršk'Đė€˙>`;ōļ!¯žĐø{#Vģî‰Xãē;bkkÄzזˆ Žģ€;;€ÛÛ€[#zģnß Ü„:7‚7Gœėēúzčë€kĄ¯AŦĢë*Äēž+€ËË€KK€ŋ ŪňwQø$ׅá“]„Īum ŋÕu~øíŽŗôtיzŽë ‘ã:Ũ×ā;mKƒīTßz߆-ë}ëEÄzįúņë׎߲ūŊõŪXKø:ßßÚ-k|Ģ}+}ĢļŦôŨ¯m¤Zí,īpߊ-õ>S}|ũōzũ‡ząĨ^ä׋žõBŖzGŊģ^\î[ę[ļeŠ–NYÚ°ÔŋÔt‚éžĨ-á-­6/uv){×-ĩ; —øųoYä;ĨvĄo8?gŽoŪ–šžÚœj_͖j_UÎ_eN…ovÎLßŦ-3}3rĘ}͎”ûĘrJ}'Ąü´œŸoK‰¯8§Č7uK‘orÎ$ß$ø'æŒ÷MØ2Ū7.gŒoė–1žŅ9…žtžR)îŨ!0)-!§Õ×éuîq~ë4‘Ķī|ÔŠĮF'ģ’ĩŅEŪäÎbQįS;_ØYNz9Iķ&õčUŨéåNvúĻ“)ÎÛŠGŸBJt$ēõ؎ĉ%…įæ3÷dôՕčÉ(ŒNŅ Ž­ā›ą‘tá‚„¤ÛPf›Hpę ų~fâ"*ÉßbŖŠãũļ)Ķũâząŧz‹Ęũ–süä+Ÿ^Ú$ÄeÆĪ$øãå•éŗ6mĸÔQãũŠÅĨ}ķæÔQeãũ R{Ŋ†n•šP¤,kÖ˛úeYĨŪ)fOˎ1zÂ#Ž—Zt´ˆŽnÖŧŅh|t”+J“—Ö(ŨÕoHa´Ũe×äĨÕŽ'zíđČūeFN))ŒŽpEhžÜˆÉš7"7¯ĐŅģoá¯úŲ,ûÉwÎZ> —Y˖gßH•‰z™Ė’^ųŊl9ŌōĢŪHSÖīÍ^[ޜËŋÖ˙uvūû’gdĢv&Ukg§§ ĀŠĀ`=°X ŦVĢ€•Ā  X,–‹EĀ)ĀB 8XĖæsZ ¨Ē€9@%PĖf3Āt (J“€i€(ЁŠ@0˜ L&€ņĀ8`,0 @>ŒF^ œ N†C`0 ũ~@_ čôzY@O ĐČ2€t āēi€p]€T pÉ@g č$ @<Ä1€ˆĸ; Dá@`Ŧ€0Ļ‘­¸ę€€¨ZĀ'_€Ā~ā_ĀĪĀ?Ÿ€€ßßßß_û€ŊĀWĀ߁//€ŋŸŸŸŸ{€€÷ŋīīīooooģ××€WW€——€€įį€gg€§§€']ĀĀãĀcĀŖĀ#ĀÃĀCƒĀĀũĀN ØÜlļÍ@hüĀŊĀ=ĀŨĀV` pp'pp;pp+p p3pp#°¸¸¸¸¸¸¸ ¸¸¸¸ ¸¸ø p1pp!p° 88hÎÎÎ6gQõȁõ/°ūÖŋĀúX˙ë_`ũ Ŧõ/°ūÖŋĀúX˙ë_`ũ Ŧõ/°ūÅR{€Ā °ė{€Ā °ė{€Ā °ė{€Ā °ė{€Ā °ė{€Ā °ė{€Ā °ė{€Ā °ėë_`ũ Ŧĩ/°öÖžĀÚXûk_`í Ŧ}ĩ/°ö˙ė}øŋÜĘūėü—-[Öæ`&-iļüUëõD‡.i÷û)Sh-Ŗ|m¤Mt =BīŅ:ę*ÚLˇŅä§ĮčYzë˜ŋéōėĐjķBŠÔw…âˆZ÷ˇî;tĐbŽj㚊8“ûˆ§ÕŅúõQž¯]Ōę8Ôb‰ĨpŖŽ]{ Ūˆƒ­ûņĘEēu°LkgCG5žŗ^čŪCˇ5ETNĶiͤ ĒD˙åo’ÍĮȜLu´N1R§ o.ŽĩHÍF)l/†>Rj-–ŌrǧøZ Ŋ,˜’yKŒ´üŠ•´ŠVĶZKëh}đēŌđŦCÎ#Ŋ Ø@§âɜF§J1{Π3é,<ĩŗé:÷wSįVtį|]ø›zSģÔEøē˜ū‚ųp)]F—Ķ•˜×ĐĩGy¯0üWĶõtæŒĖģ ž %s¤§h;ŨC÷Ō}ÆXVaÔxDÔ¸Ôc¸c°=<ŖM‹yüV­ čģė[c°§Ģā?ŊMÁq”%Ī@IŽÂĪAFYÔH\„>°>Ō#N]fô˙ˆˇí¨üžWĮĩmFæ#%ÕŅŪßŌ—ĶuX7â*GUĒ› YŨ`čļūë—Ũl¤oĻ[čV<‹Û Ĩ˜=ˇAßNw`mßE[h+žŽčļŠųēÛxr~jĸ5Ķ6<ÉûhĩūßËëČßô{vŌũôfČÃô(všĮņĨ<Á÷HĐģËđqúqziYŠSOŅĶØĄžŖįéz™žDę%ãú R¯Đkô:Ŋ%ėP¯Ō—¸¤W˟RÄĮ˙û1Î×ŌŦãø=ž˙…™“)6ˇūÜē˛õg} Պ ˇâ)mŖķņ‰ũ”#%…‹ÂMSøČņŨK1Cŗ|´ûŖ~}ŪødûŽ:TäŲQ7HˇlĒĶcre}oX]ŽWŗnĒC¤ÜŦ䗲^ĘÎz) a˛úö+1i1âŖ4Ģ5ŪâéÚG”™1xĀ€ū#´A3<]Ŗ4Ã7pđú€ū]4=^yFh2-ô×~)×'´h<šĶ˜ģ$GĮÛ-f-%)ļ÷đtGņôôá}R­ēÕĸ›mÖîCFu_WĐõ]kLjBbjŦÍ›š˜c=øž9j˙÷æ¨yĻē—ę–fäv͝ ˇi&‹ĨĨKRįž'¤į0EÄ9bmÖØ˜Čîų3nLH‘1R8ÖÁ‰NOë~ĶsŧņÛ°•ãž“ēĩ~ą-Ō!&xZ‚"ŖĨõÛmJ„Cx“ĨJwČĢŨ¸FWow‘.ŗ{Eˆ‰Ũ<é?DFD&uMõ„ÛEĸ)’"‘ÚŊžG7§Đ YŅf‡˜ūļ9ÚāŊÍvƒŋnŽ4ø‹f [ÖÃø EI"›Ō(Cô Ä›=iõ}šÂĻa‚īŪ'!˛?2†Æņæ.LëĻ´¤‘Ũ\——Ņ"zmĢ‹+dj=›ë…õ•˙cŖ51ĢweIČ!‰˛´™–„āl•ķ8!ž‹&§ĩS¤fļÅ{g¯ģáų '_ūęŠ9 Ę 6ŗn˛EØĸúO^2yÚĻę!ƒĒ.š>qYŅĀhk¸EßáHŠŠī‘é,šåģënüåŪ îžÎ¨¸äØø”¸°ĖėĖ‚­[ûĐŠ#3˛3,1]äoÜo%2]ˆ} –\tĨ1ojnšˆKÂxÅ90XqņЏX S\Æ(î­?ļ–dŅäāˆl7ø'9ĸÉÁM~ųÃ0ĸ‘¨"g‹Čh2—PîžÜÃ#¸›Š_ߙÎĻ( cäļē¨"ŗ,¨CQ [ŽąČ!Jëš1(fāāië@Œ—'F•éÂiˇ~{ÛĄ¯;õčŅI¤ßņÅuEÛ.ēkãŊMëîZ:TģúގNuešNĪttķWÍß~æ¸_bF4<†™‚žëëĐķ^tėwSrfpžd{•ėUf°W™Á^eļh1Ū°°8wœKn6¯Ŋ!C<š!^É–Îōđ؋2AMî/K–ĸÛŲÆ*qpˇûËŲ“aˆ¨ÃŒKÔQÛŪŲ{‘EÔY‚ÀŗgÍ N öŖaĖ ´˜Ŗ¤žÎnˇŧDŒVkŗÛĖf\YD†ĩc ƒž¤ ›=Ü4:ÖkãA˛Å:ãc1ļC Â)qąÉëĄ~ļ§üũõ­­ûõŒW&aŒ—5.8^qÁņŠ ŽW\pŧâ‚ã‡ņÚnOĨ.ŠVô¨9.ŽŗĨEtoîZÔYn@ÁˇEöޘĄmF%NŨ^‡˛]eámuFil3‡ß ŋęŗÚôÕ¨č%čŋõŒ}4´×īNNęoÈŪ]q)čėĢÙጠ;ø™Õn5›q1Ũ##ũžŪúĩi•ŲMšô>¯””č$š>’äúHr ĮIá‘RĄ¯IrnØé‘LáÎôfVdę™ŅÁQŠŽRtp÷‰î>ŅÁQŠ–?ģž=P Ä2ßÖĩëĐėˆpŧŠÃEĀĐâxė-MŲĶälÂÃ63¸ŠfÎÜÅ n9z]eŒûędķČæ:ķĐđŅc[ŨĐâl)P—=§ÕŽŦ˜ļ#Ún<$FN3šOãŒ!6ĩŲšLĻU&[¤5ōXųø6ĘkĪyiF‘4zŋß/Û˛-YōK~I~Jļe;y8NâG2! BB„P(-PŊPļ-KˇŊŨn“8‰ ]āū6ŧZčöw7mZz›î^ ´ĸĐ{iH”ũžoF˛ė8áuŖü4ãŅĖ}įüĪ9˙sžoÔ¸áÎĩÛŋs˛{˙÷fZn­ËŸÕh(ˆŌ_WĩrmĶúÍĶ5üų;Ģ'ž—{ īŽ™nĢœÚ sč¤ÁęāāŊĪė<đÜá.‡ßįõHĨœ]›×Yƒ¯™øÁ{GûđG›Ŧžr̎€Āķ kÆąCČš¨ō(ŒK¸į‘‹ē–‹ē–‹ˆ”‹ˆ”‹ē–C+ą†ĐˆGÎŲF¸…L˜,&Â0L|ŠŌsÄ,—,ø Hf!ŧwBÜ6hÄüfĐĶ ŽäyFīĩų*LŪ/Õ{,€RŊ×lņčĨøOiÎäąZŨ:FŠÍâ?×0včĒ4''îž´¯ˆÆ×ĸÁ+ŊôODRÆ2”PZM—._zĖĒtC‡wļ`ŋBēá6ļíj#”ҍ)‘W›ÍÖųO˛Ąjœū–•CŒË!Æåãrˆq9Ôĩü)ÕąËĪĨ,āĖ_?Ŧ0›”sM5í*v­*‹¤p8ĐčŲ°¨S@.Š{šDk$‡LcÃee˜„5õėÃ!‘”÷-rzÄ)đ8Ô>R?–ę]“G'%ōqRapč NŊ‚ȧq,f ōJÛVwÔo–á{%ø…Õ´ėPÛtŦĩ ljËGG9CR ˇŌ÷hņø“~ÖZfģ8F>éŦ°(d:‡A°`uŦ{qZ­ՎļjqĢDÛ÷ ÚõĸÚõHíNyuu Ē=fVÃ7pbŒcá8%Oá0gãˆŧZĸ,0"Âô‚tÕ|…–#qČēUK.0‹Wt*¨@Ųg4–Q¨“4ŃÁxS•̞Áōų ų­îv;ARËlviĨ•ÖGČåĐāMŽúXųEį˛ŨZiZh°Â ŋO|Ą9ķHßÅ+‚ûûe^šŠÜuéĨÚŠ‘Ą"ž,¤(– xũ‹ŲvŦû&Ę<~ZÔ*-‚™ÁL‹`ĻE­ŌPe&ĒÔ‘ėāX%žu¸Ág¸ Ķ`x¤iÖ7+N†Ų’”$(´4WĶđė“<8ŨΟãŅKŗ’oi*ĸJč ųbjīģå!™Îcū_aÅ ÛvdËO6MT~ķëƒ[züäC›ŋž%_]„PcJŽß76t]­ęŌ…˛ô@[úrŽœ’x°^ŧL¨ ÚAá Ĩ@ģ¨ ´åÄ-‹ļH+íķDe*Kéôx6–Ō€z!æą63ŧÖ]ŨÆqđ \bƒĀŗ=EÔ@?aî§‚-âV/lOŠ5xc̟ÆCXHWÁ”BãnĀR Ījā<ŗî5h4ƘÖÛm’ōQ#ČGĮ$(Ą˜šĶ@^Op9ĒĒŧŌĐĸh;ÖP W˙đĐ‚§y$ĩŠ=Í#š(°G!ŋĢÃĸč…G CĄ‚ŦϝBÅirĒsīˇ'ÚwŽ5› ÕIUņģû':ũą‘m×o‰7o{pexl EGSI+E¤kĸŠ~E­56zŨõ׍Æņíëž23ēŊæ€ ”’ŒˇĖįlXolމˇ­Ü=4|Ûę*ĩÅĨShĖ:-`čvŸÃíÔļÄâ­ŖģaÎSô˙ ß+äŧĶæä¨÷9°‡}bW€A[sųš“ā3 ­…Ė!ĸ=†GÂī#õ>æÎ@§ZÄē|Į(Wܞ@lë׈Q-$8°'2Nō0⛈i}ô"Ļ7K5vN¨‰Á8ŋ9Gíš=ŒXÖÆ*Ü ũÖ ũÖ aé†Č  ŸZKi°”( ĨƒoŘQT…QT…QT…QT…QT…ņ)‚ÃäŋÄžKɀyp„ą-`ÔčΆā7a; O„%ĘȒ%ėŠ„ˆĩ\IäÜ×}hūĻí?:Ø%đP´rôĻŪū›†ÃHk ˙ŨÍ?>ÔŅļīÔ^ŌWĐÔÅŋŽ=˛ĻĒrüŽ1ŌT8‘áDqQU`ģ˙Į„ácâđ1qø˜8|L>‘Ā‚á›đ„Ä60bŖĩ#4„6Q2x€†ĶÅĪĖčCԓ)dŽúz”)tFŖ)^_ß ƒôÔũ Ņ+Đƒ4˙U…Dō8F…ä„%f%L5–9RĄķZũåœD˙=_.ūņē ”üāįųûęö4'v7ā7ËU€ō¨ŦF80î˙G1?V&Œü¤ŲbƒĘyOÉLA78Ž†× ’e0ā¨gY­cFģU˛U`yHŸqKÄ|ö ڄ•{CØŲ’W°ĄķüÂ5ë‹@žŧ¨Ąō0‹jh¯3$ôxz)9–OPrßîđŠ)žbÍ!§ÅgÖ*¤äˆ˙Žoi1‚‘’4+ËũIÆJI‰Ęn ŸW¨'Á“ĘËáoŗ}ŧ}‡rcNāØ,ʁÁøĶ‰)0aāŅMđŠ9ži6 ¯9ÎGˇléÖÕ žŌĢ šjš‚k‘īJĨŽÉîČŽžwSmÃÔ=Ã՛B.ØŸ4ē9gÅĘÕåˇŊ|_īĐ/ßÚyÃĒŊœŧOg㤎€Ŗåē‡×l~dKŅ€;ų I‡+?Ĩw0ZĢN‘Ŋī…ũˇũü!ƒËĨs‰ļÔ%ˆE°ĩČļ>>Ödō+āÃķ˜ŠbÆ„ėŠ‚ ޘ`á3Ŧ_ˆ#WĻÅĩĢg™v§¸KūZjÖuë™ŧÕ¨Ø)đ˜-nŊ˙ŖwŖdj*jã…|SaŸükįSø*ė‹#ÄûÁ ˜_ä `drnØũeN.ūŽ…o…÷ž‹ ~đ]Šß€ü7ZØŖá¯fjĀÖKî"‚ØAXĸ‰ Ļ á/ƒû7b„ûûˆsUUÆÆø3D+āĩ B191•RbÆ˛¯BcŸŅ5-TļÜûąs˜ŋKOēĸ'‹Ųkõd@ÃAMK~YĒķYė~“R’?x…ŽwŌjŖÛlõęd€ÖËōßÅ÷ŌRš43Ā IĀQÍĨŋH¯PAž˙)8JÂŖ´BĨQäoĖˤ*Ĩ\ŒŽÄk@ fŦVЂâ‹QÎįQŖPHŅYC9CÁŖĮyJhl´øŠ¨īÄkœ:īŌûž7dÖŨ~āûä+…¯uņvFcė ™QŦģ~ƒšJCUČ<_NÉŧʈŧĒĘ[+‡i0oŨt•QA:‚ĶŽ­œhˆb!ĻM´Â&6@>āL(ŧ-=ŊСYÚĩyĶĩē6Fƒd†ŅšMˇ–!ō_ĸ|eģVFæ%­Ûbqi™ ™wUzĖ2ŧœÂcŦÅSnŸĩøđ¸÷âa–%iM¸xOņč‹^7l×\Ē%^rVXno—ī‹4cYäų-|°ÍNEįņŸ(Úëg&¯É-ĨP}>Ĩ(ũ| i˜Z…ŽŠ^čš.-"ĘÕä‚1É÷ŧÖ]A.˙vŲPĮ œŅ؍fíM¯‘æÃĢĘ üŖĩv“ŲĄĄ;ŧn—‡Pô?–õöõ÷y/=S:VŠÚĖåũÃß)[ĩjuū`’,_!‹˜Ŋü.Õō a8îg1=Ņ ÜÕ Ūå˜WWĪúæqõ1ÉļETâ˜ÚŽÎņęY ü?Û퉒 WD'JČÕÕ~ÛOöī?}kKĮĄŸėŋéäÔqOß-ããûú}î~°ŨŸõÎ;ū׃ƒ]w˙ôČÁWė:ōÂũãņ-Š ¯{dGsĮއ!ģ Øxđ Ā‚é§AôЀ/ßLĻ } ‘°ŋĻŲ­ĨmœB–„@꒰†ĀßxtĘĮ5nŒ&'ÉÔCÁ`]W;u˙ĖŅB¸šqĨ¯ËŨ´.å=ŅŅfˆŋúæŪ ņ¯ŖwŦ‹ä,5 ͰņÁ™žĖfD’ßájčląžzØ"€%°(;Ëܚ |ÎŗEáoãhÜ26 •mœ­ƒŠ69#df!+k‘΁ķŒadã, O=ÁÃsÍÉ3BB#”čE˙ŖKg…€đh4âKö,­RhöM yË ė?Ŋ¯h;m Ū[wKģJ•˙ßE+ö‚í­YīzƒĶPŨšô™ü]_üŲ‘ƒ?–ŧûÅݡn_ë¯n7Đĸüč`Õ¯ŽX˙5žĨc×CĸUVŽ2-ÄF9a˜ĢášZøču°Ņ-ĩ=ŦyŗšŲ”øô9!*j›s1XŨ$~…Ōƒ6ÜŦy“gēãÅsĄ+›bžU“W6ë uŽ ¨¤¤Ö!—v›Į 'WĢũŅöÚ-"fŨx×獪.[cĢ x¸5ræĪ†hę᯴ Æ,:#RĻRüĩĸ+bÍQņ3#ØŗĨVAœÂM•ũŅj!~įk [ō?´Dā/eö^~—¸đŅôŌAhOkƒĩ*|ŌSPĨLÉm’đ,ߚSn]TGč@\W"FeP šĩFJŠ $z,ŽUō‰pÛ]ރןāuxqÁŊĪ-´3%^NŌîqąyö+ŖņÉlĮH°REUĪĻ–Ēlƒ+ÜŗvbmēĸvũLÅHg }.cdå­#ņPĒŌ\™^ģamēõíĒÔÚėœ‚3pz‡^æđ9ŒåÍÁōÖH "ŪŊŠ=ĩ­¯œ3ZÔ ™ĶĘÉę°qG¸­:TëÚãŧāĢ āˍ5Ą˜QN'ŒjŠ9ö„mZžUl‚y˙yØûĸlđƒ9}˛Đûĸ¯ŪújSĢōįdZÅęŌKķį Eņ6´5ų›€įâEĢ”j@aĶ0Œ0ĢúMÄ΃ ĸĨ„šÎMč@46ú“ōā 7c[ÅÉB(> ?€-¨…(üI[Päģ­×cķäã;›|ÍVNęëžL$6ty¤:ˇŲáŌ1øc{žļ­1>ķđmÄŽ…¸ôøĻ™./¨ŅĮ‰E‡c —?¤đŨ[°}¨g'‹ČYŦ%ecķø{)y k2+>ë'Œ)™m˜Š˜‰úd)•4!)ôb´ Kva˜°¯MhE‚ąôē Jˇâ\é# ŗB ЏN\i"îÁĻõ&Ĩ´–9Ŋaŗ‚ü=y–bÍe.wØĘRų˙ÃāÚ ÛéŅ1äŋ&ĨZÃîÕ2äyü˙’R×áđ¨Ú),7áXâÃKV ÷U,ųŧŅĻ„ĖP~ņûä¨B *e˙QاT6Đė[ŨˆúViÁ›M  S˛Vø ~3cœŒu͘ií ŊĩІ{?ņËsÜĐ觋}Lna¨„EĻķš,ĀČų9–QŊ΀AF]$ū’é}vo@%QāየÄCB? PŪ(ū Š‚Ļ(ĩöÜÖ_ΑIō§(F˙ ŲÚ­îpuD:H…ĖT˲ø@-ėĐÖÂæl-'jįņŋ§TX(¤Æpƒ=\ŦIœ‡hįĀšÄ†$Üĸ‰‹ĻyBšŌkLĪcĩ\-Ņü\-ŽÕâĩĩÕíķ8€Á/ŧ¸×K9ŪŠîkũ-;@a‘ÂĘ 4>ą{ÃDa]É™đ†‰„¸Ę"0´a–R*Lx­éyĘķ"FķâF ČŦvŧÃW÷ą­ŋåĄ\s¤dŊšŸx ģ}uu% 4^'&Mņ…œŒ" ŧd’ŗÛŦ.UķƒÃé‡ĢÚöü×mŒ5ƒ‰ÖMŊ5Ŧ”•QŒ­cõlíĻ/Ž >ņåŽéךí;[Í,KĶ,ģ6Ųč™mĪîę ôÔŽ¨ŗčJ}ĩÅaõ9t•ĢŽa7\­†Ŋ÷0‡–Kō” 3Čëë<”¤ ÉŠ`Ÿ­‡Ë&Āî1É MĀĻDqrAķļĶÂuAxaJÆ —Jāĩ x Á h۔(‰`!ÕĄLđäŗd4F#JËŋŒO=0îíé Iĩ6ƒŪŽĨAũŠ!­´Ŧ?“)ÛüĨą˛jW§ÜmŠîPׁÎļņ ūÖMOîқƝ— }Фą@’/ũkyŖŧķG7uß1ŨĒ­čˆåk™ēFëĩ@ĮnōeŦ{ å;ššîŋ›íoĪÁ&û2‹‰Ū]ŧˆčō;Ââ"B‘RFT¸Ęō–+%Wf\ūyœ˜Ķõ‘ǁs<2e61éc˛˜ÍÃ9ôV\úqFœšLą.Ë[ŧ @%œæu}5äŸx(ä$"ƒRŽķ˛!­ŖŪæōËŅh!‰Ķ‹Z›nBÂXZúĮ#›™ŠkßũčšđpWYFZĨ:Ô˛ĒiīmžÔDKbu2ĖÂķ˙Ŧąh”–€C›ēõÄMw=ģŋ™ŗzÍ*YryĘ<§8vįxØöIuAĢôo$ˇ`7ag Ū29|üĩžhÃ0fŸĮ/œ…&õOã0)`@Š”u2œÛ™N6 5Ņl*K4e›˛éäÛąéL 1%_;€ŲIoV•ĩ@Ŋ‘} °:Ë%ãÂ<ãĄX×Ā–BėwgĪžĶŨ÷ÎpŽß™V7šš,Ëe –DŌ§“oķ@ūZt–/ہ´ Ĩ‚{ ۄ“qaÆD Ą:"si!ˆôË,EŌD-¯~Ã"#MÁ h.Ō@Ũ’žŋí­ũ^¨Í.=cŠĻkÚní–ĸ2Z'UøÔ­c6˛•2NˆļZ•Ŧ@ļ‚–mîC–Mņ_4TęLúšŲĮļ•w×{•d}oëė=›.ũVĒ€ĸę ]ūņU—žT8Bũ3A¸ę3åɁ¨JcՄ\NŋK°ąŲØĀY´Ŧ%`Gh8ü?nI0ŒŊŗ˛ã†•5FĄRŠžEŸ؋ŊŽ0°z0ĩbĀL÷‡é—.4ĸO2zÍæ3Ģčüá%ö÷FėĶíŸÂūø*Fkņš-^ƒ\ĨÎ?…īdåV+IF)Ã˙šW.Å@ãĒČgÁĀÅ7đ›åJI2 kæōO僀 Éŗ’ØAaôļwÛ ‘i‚+F/¤ņÖøxíÕ×Â`jܛ!ĸĶŠibdzdzrė­ž™I蚲âæœĒ5ĸ u˛j 3'íAMˆX),Pq-,hŽsįbÜ gĪqg4Võˆk„ĀĻši$˙ĀØ[<¸Ã čJܪ՜ãÁ]Ēām@Î̐væxp+Ô/Ȉ-ÆŒEŗƒhSX›K`@‰4qI^‘{|bė F}õē;FGŋ0~r ÷fCÉo7H%RšdTöPĖ–žJ9÷Ēĩ”LÉėĩTu”—uT[œQ™„вĘ@c1x}iZā2„É9[W¸cįHuõęÛWm`4VßwÉe•Yëô*• &ĐãfüCˇÔlL_ËXƒÍëŠhŽŠ´–Rč)B_šLtiØ=Ųrņã’XK`?AœÔ•lÆļdĸ ¸V ׹$ ‡I@J“€)Ã"BĻŽˆ :"&čˆČN#b‚Ž@Ú#×yz‰RU@Âbî´–:Ą@q$‡HOrÉZfõ¤ä… ÍđĘ9Ūܧ‚×ÎņčbXV"Ōŗ¨GZĘ,§/mŁ,Û@>Îhėzø\AúŅuS÷•Å6?89tgŠŅģ ķ‘=Ųų…Ž$ā9€÷´{ZS=!KæėX=pįąÍ{ž>œîî$…X—ēÃŲ| ÕuĮ `<5@ģ@ģÆÆjąwv+"õÉúõ¤rD.øÕy*á*ĄJ¨ŨJ¨öJÄũ͏p˛+üD˜€KîOBYK‰‰yú[ļų§ ž=žĘQPÄsū §({äˇÁ>ķ;UģT„JöŽ}@li,Ŧ¨¨æaAļŽ–i¤ŧTå‹üÍHF0ō[Ā;UæwxLÅŠ5ОËŪáí‚$ąũ‰â|ÜU=üĒGļ`ČGC–KĮ=ģ†SĶŊÄ$bWũęŨŠßŊĄŠe÷ˇĻŽ{xcՓäžŊ­ëÛŧA„<ũˇŦŽ6X ŒĘĸUęÔŦÂbÖĩíŸßŋįĮˇwwŨøõqŨGĢŗ3 0.HŲ°û"šį6r`"biyŧ­Āßm"Áˇ‰ĀĩÁŸļ‹Væ/˙"Ĩå4x6 ĪÕ§­Á\4ãÎr”c˙„ĪÄß8dNˆ§4õōΌsŧx.JeąäŗyĨ!ĒØā_ˆKÂ\q„a…18ËmZˇęeŠB&ŅĒ_†3HLˇqäÛˇų2;ú|~¸üA­3Š$2…ĖnÚ,DŽ‹*$Ō „‹‰ w¯.WĒY Fā M@S3Ø-ÂĘĄšŠ ƒ?YcBž~ü=čaƒ!1 3ƒëjKČwīZOIļÃjDŋŠou3חŽnĘuejŗū —ŨSZÔ Ej›3qÄãįbÂäœ=-HۄÄYy(¯Ī™ãÄŽĻ_)Ųŗ¸ÚA*ŊVŅC͟NĶ&‘\ĨHĸ›@€€ERw™T ‡†Ąd´,­1ĩ­¨ÖüXH?^j˜Šą#ÖÆxšIEâŒÆm…Ÿ•÷ezC›ī+ûU­íŨĄÎ]mãËUÔķ$ ŒgŠi¸Ē57NYUN1 #—ĘYųĮU`ČŪŌ›$û0-ö-ė=dīŖGw~ ÚųäÉÉž5Spo§ng2Ŧ€ä@ÖįîÛ ^°yžrÕ9tā[™Įs÷õėš:;’š9ģ-ģ&ĶM*äa*Ē‚fj€Íé((šGs–4‚éDĄ™ē¸ČAH waĘ_œ(w+sßã9Ūéȁŋô^M*”šĸđv)ohÍņā–%b~āfņ%†Å‹XR&>5ĻMO:')oØ.ŋZčž2p;f=ãĄđũģrübņV(DkxI:ĮKŠŅZŧIIŦ^š?ž ôŖ=D~™œžŋó~T+4HÍÕŊŅļ[ģ āC2ˆT5|ް|”2/†?A ÄcšėķÆcČ>$OLé°UÂzˇcŨØĶÄõ˜s8°jØ Ņd¨Vgr-iweŽ^-ŠĪŗäüg_årŒœ‹ŊņūŲs?‡ˆ0gZr<8ŋž2ĮĢS…+ —Ÿĩž.X|É*ōnQŋÕ×đ§dƐĶ2ÉåϐÃ2Ę´×°Bz[—ąŌo“ĶLĄąėŨMcĩP¯ØƒPBĐnXd2K⪚kéSȞR™\Á™5n;#e@|ļ™JŪG=€G~ēbEÕ-Ps••< U;OÉ+Á+ႡöĀú*”÷:[ˇdÔIįžÜTz}f<×ÛSåNä:3ņlAŲÅŧëĢ•ēp?G~)„Ü”ˆJYS{r<”Ö;žãĄŧÎDŽ/J\” Á…ÖW?ąMŽaj™œxĨÍ$īËLĨ63!›!¯Ęo˙8r\¨Ü‚Re0i•˜TBȕŒÖ´u7“Z?ąI¯æ.Ę+’č2GqYräĶÂSls]]‘ļĖ3øl%f ‚ųAdŽl0ķJ¯•žyb$eˇl_ؘ›NgVæ6dŗm™Š,ígYļë)Ŧ *ŸÅ ŒBđûąs%Œgē1Į1Væø+™$*Ķåē4,Ķjũ y–†ƒ=Pŗ×böaôĮø.V.ÕĄN̚ÎßU0+!eŅē¨ņãŗm]HĖļ’ŨđčÅ^Ļ÷)[ļÉúéĶŽ˜ií‡Á^fžī"ø“÷ON6ooų6¨bnæ›zØø0Ļ€Y׏Ψ ^94oÉf+$÷öėqånHoĪlɍ÷´D2š‘LGļ.ČhŠĒčÚɤHŖ2.0õâœ+,ę ‰ĸopåx(||KއâG29~Ņ jUôôä5ZëŸ9.īö%Š| ؤvŠÎk]€ō|6"ĀáįS×ât*äđēQMr8žļã r• @q'ĢXž˙™*œE!ii._ŠÂČ'@ĖXmE1ÃåōõČa>XaņÁ| OÄ#ũ=ē\2íŠKFRHÅ|{ļ ũ=I¨vŌžBąƒÎ7Iõ">ũYé3ųÄįđ\ē-Ÿ“‹Ņö_€oîžmMÉFGŖ—Kôvrc$Ō˛Õ0{'ŖP‘ŽžTË @‘ŗéÉĖē\ļ'ękÉõdęŗ%]pžĸZ…(W#fT÷ =ŗ@ÁPJv]އrzZr|‰$ķb/û4 ˙ė~%ų—‚퐂ķ>§ķƒ••`Ë w,ĶpǚŋP?@ũȇ˙LõÁ’…ņų°Ú™ļg<ŽÁHkäØA89d˜Heâ™Ļ&cUΞîÃØœ1CŖÉhD`­dRČ–ĀŽg ĩ…ĮJŽM"Vž(Á^•ãĄ #›ã‘ŗh@ALxņŠĄeLSÚā-įc§ŖđëŽhčĻwôzœ,C’´T"ÕÃūoÜ­Æ‘ÂjPxŧ  ÕĒßĒĪvNĸdœŗQ˜erx9•Œôß8H¨ĢĐo/{{ŧļ6ë ė“Ÿ ­_¯äUÍZ‡Ŧûî„.VĄÜĄ´‚Wč6Ŧ&ڗá37ŪX3›[•Ėdr =wÚTĄ\MƓÕgī8ÆĀÄčlR¨2Kf¯,3ũĸäUŗ9Ęn‰I¯ åxA>opœgŠĢdb ôļđŦõ)Ųėr6ūtϧߒq.`Ŧžé”ķZėöõC]N┋ĻđŖū4løû”2šŅ;Đđ(ÎMJ´Ü›z€šZWjĐkûęȲ`Û0q÷XšJ% ÃSWĮ|ō˛.˙yųֆ b“¸Ehƒļ* įĶRŧš9žMĮ“ķ—ĪÃI˜¤8›ļŋ??J2C`7ĨTkņėRGÉ8ÃĀš/ÍØ<—R‚Ē8cŗ1ņ* Îō¤já4Ī8ŧŸ›—WR ° ¨Ŗ ŲØ÷:;úļÁ°ą‘ücKĻÂŨņZcßē×ÜCâ[Iá§a~%LY„ã¯Âé@!|^r¯†Á˙pá Q/$—í{g †Ņˇy(ŧ…ü#Å7vŧÆ7öš×Ŋƃ[ˆw%…EqÜ Å ã†hPM¨!ąP{5Āųíú†Â,ˇŅĘ&ŧ6X\(& †B*Rü‹ŧG§žŨgMl˜˛iMíõęÜ5R]ģũÉŨ;Ũ\ÉyjÜ5‘XĀå¯]{ļ<íÂ9&ŸŸ™ˆĻ#Ļ™u5™ˆitrøîrŗėđÍũ3m6rĪå‹ Ū2Zé0jĢžjBNxZ×üÚž<ŽĢJˇn-ŊT/UŊUīûžĢ[kkëÖbŠĩYļlGŪ$/؎mÚļÛq& YL2ėØqÂ2Ā Lx0^ĸ̆Įį& đH2É{ķ€!ŖĀÂ2nĪŊˇĒɒã™ī{q¤VU×rī9įž{ΚįüˇŖ{fC&XØÔäínk´ZG]ÛCÁŠŪąģ×'• oųŨ­ˇ{چ"›ö¸[‹×ĻÛķ¤ÂšŒFL=}Άnŧ åãĶÔw‰.hų<ŒW겎5H›Z-1€V45'1Ņ6”í^ãĸũ=hīŽäđüxÎ?jy‡Ų&Ž.čAúí+XGāLMŨItë\)9ėG7”%˙(cy§ÄHL× ājķôâ4íæEev¤š:Äo\[n-}ž”ųĀēŖ‚"i íë/ėčõD‡ÕåæčāĒÁhe5é†į⁧ˇ'Tz“†ãjķ7X ļŽŨŖģŖš7öāš‡_xpPėˆPŠÉŌĘō{x :ŋę]úh_ÍĨOCĢōsĖ,‘%>ƒĩvž Äj RŌōgڔ„>į[ŗK…ThMZ…V§UxaZ…žc‰džwÅp(øRr80P ūÂāHŠūĀĢųĪÆŦIķU–Ē—‹Æ¨ū}BģKƒÔįEßîéįDƒŧ’ĩ8ø‰ĄÍ7ŊÖ§*VĸŠ$qâúĀZ&M˜/ņ FŠČûĮũ‡ü” eÛ.BA0āΎ– %ˆč—ÉYÂA˜Djš¤ģLŌˇĻ ŲM”Īŗîŧm4gå‡0 _[ˆKkōR>„îfE=W¯‚¤ûvŧašRC2‘Ļ€*t/Ĩ!ŅŅG?UęPÉEZČAC{,šƒ?ĸÜ@íkuāwsŦÔĀJÅŖÔå+oxuŨĄ°Rۊ„Ū%´‰xĪ ę| ˆf@Ļ c™ųëßĮ¤ĘHŠ”ōŠÆŸ8õ!s™ >B-QT- °Z"šZ"š ­MH& DlQxŸŠ‰ 9Ē.7vŗ¯@qMķxM?ûV…öSö‚Ēūj‹tų-LJ V÷Ô\܇–RŦWH9bJ Ę;¨YŪ)­‰îōŽd…wĸ&b­(Åæqķ!3ĩ"˜EE‰yĖだ+’÷,>õ_`t­­b̘īC=ŋ¸0Ÿíz„7†C<ˇ [Đī™ 0p#úˆÂS‡’÷‹Ēžršø§Ë•Ņĩ0ΆØÂę yĐ—Ö l“5Ũ7‚ŠŊŦđ2øT˜<]⌴žáîdÛPrÔZ'-õ(Œ9 Ą ZĻęŌzx×û…¤øæJ#Ã=øiÚŌâĮUÄIĒŧ™*\I7š¤ĘIā˜ī‹*Ō 0&úSšÃx5 ų„Bĸ/•;R՘¨RpōōŅĩmęoā“kG“wškē͟[ĸ;o "zՁ„Ô ¤!9ˆQ J—”–į’ˆę’”§KĘsIDu!ãʕfkD• FDR#ĘL3ĸ:#ĸĢņ’E¸U—8bl˛ĶŠöJã†ũ퀔2|PŊDÖJB=ԘŌbÉ+˜+qèä•ŦfđÜJÉ+õfûá¯Ūqč‹[r‡ŋr~ļū•Ŋ{˙84Ĩŧöüūņâū~øŋ˙ú##Ŋ÷ÎŨ?‡áį=CėĖ5m{`løšĻéõž.ŸĻ^…ÔCU*UŪ–e E=Xƒ8DfI,čĀĨßKŦíXļĸcˆ_ąĸãæđÎ÷+čXFėV.č85éī)ęäĪh˛ëåŅŅąĩI”{ôWĻF\Đ1îŋģ¯{SĢ üķ_{p÷5ųËŨ­M˙sÅ´ü“XwÔ4úĐšŖĢîßÕi€ļeų“ë6vîēÛī§ąũލ{IÔėŧnU Ķ8ĢŽÃ`uGųŖ1ĸQÄFI@%}Ū( nŖÄ€Fœ?j ŠēânšO!ú؆ÛPū(?† ˙åķG+éŖ•ûl)œ'dæÛpú(žwQúh­ėīÖm|%énŖŧšÎ•ũĀÉm‘Uƒ1„Tm\Τ/ĪU( ŽFs~Ž’G稊˙71‘TLĪŠZņä3¸vLĖ‘™i!NÛøŠ$žœ$×_}¸’cÂĨ:XPƇCœÉ3d%¤Š ›>ņšŊ~!Ž/dKĩ+-Ō,ĩ$“q9}ˆSF>Cʔ …Ų0YšÛũKĩa°§=įÔxN5Mj§āŌ)•J…15ÚzíüúđÁ–ū0G)XVŠEX k¯//Aš ^´Ķ#ų‘ņ‘ûF΍0uđ}ŋ•`û° õ R#ÃX? į^/¸E ?ŒŪ‡ÄV‚đCé H3Ú_ŋ՝,2!Õ•ũ‚ĪËĢĪŠIuęVöŨŨvŨŒŽĄú~‚đô†…Ÿ‹ Ą Ō'AôM!`´:ˆž:kŋlMŊQŌąī”¯ķč(-%Áôũcô 3ÂĪ+Ēĸ Їōp˙+}äKĶŦn˜\Õ °4Âā‹įok‹õgíáš k áčÄņ‰@ą=j’SĐÚdeJ_ËP:Vˆš"…‰ ë a ]U‚Rbļnƒ—Û=vŊŋ%jЏ}ņîÛ:›w %Ôz¯æĄ VÁāop„›#_Ŧs=!r“9Ā"NŋÚĀëÄnb+¤y1ۚ D ĮO Øb;gåôėî1pœĄg7=v?1vŧč^8:Đļu˙ĀČ;k&ļOĖLPЉÔÄdã‹ĄũÓ?;Á-X‹ĸXŖRœĩęKxđÉá`˙+Wôb¤G/Ļ#ōo"č/¯5/u/”ÄMŒ@ÎLđž ČüŽũ/–āÛ&^‚īŗr %kQų(Ž>*Ĩ9oqÍB\'Ú|õdËV&Kųeē)ëę[V?2HZŽvGp´ØuœĶ#ŒÁģ­ŠŪh¤¯Áæw*(THîkŽgōÍE$šæƒŨÖ¸^07l}pũÄ=ëc?Eˆ…•ĩ€Ô :•ŠĢ„ ë+2ҰÃãZF:Úo.[í;V…d2K1Ô{híĸr‰ZŪëŋ$Đ_%ډGąūŒ:RŌ IIW$%]‘”&Ą¤¤W“xņÁŦI.ø‹NÍ‚š˜Š…ŦŽ"ÅŲ(U-\Ŋ‚ËáŖJđZsÁŦY(™‹ōL}:nã¯æ!ĸŨb^käī‰ĻĖģ Î{Ež}¨ârũ ­¸AjˇšŖ‚Q2‹ÃŊ"­o¨Å4ĸ˙ČÜEė"Nciĸ§'ģĢuŪēÚĘYü§Ų¸zWqzZÖZŊ°ąØŠVOØâXbÔQdƒ’Ņƒâø(ŧ)sEŠŪ_•˛1q؞“ąqõBicQ|ŠĻ$>F&,”dƒ …ëŅ“N[h—ĻãÖÎn^ōS!2Õî/ōõĄŧyŧĖoĀQôÅĩ•īVĻĢrŧŽŦ+ķ€ēTKĢĮasŊj…$üúE¯nE&!<(Čę—Lš /!'ƒäŸĄmIđųץD÷ˆĢÅĶ=<ōŪã.WœCZRM5Į{Š||ĄŖšhDSpL)Ü\…ē¤qš “bŽhāĨÍņ…RGĄš4b# _$œ…sD걍oĘ_r •Bērú)iˇũûŲ˙<Ŋ$)~™ūœ:ūRĸ‹Ōåb|õFœSĨéÕ8ā?ĸ9žžX]ė)vtxŠ E˛¸Q_h.ęŅ@ Žm­āH–¯L‰ËQWĐŌaĩžM"U| Q䋤Š*6oÔ"BB2ę%2ʡ.ūH¤yT1ŗÜú“pCÍĖr­é–ĶôË ¸˜w•{ęNRrÎYžäā•ĩ'\ÕĻ×ūŦe°ē˛¸ÅéåĩŦDõ:fčŒ:Fŗ;¨ā‘–¯/¯oäÛĄžųņæÔÎ#}ˆSĶ÷‡Qđŋ÷h/oC, …­ím B¨šw-Cė>~đøÁŊėÂ#ƒ÷ûÂļé…Ŋ¨j™ž¸ze€~ŽsŦZŖ#ō/+FĨ!įFį÷ē%Å 'møÁ° ĨęŖ÷N/”ö7bVnëDĪž$Ŋ@ŦQ‹KŠy ´īVĒ|–:ˇ< ęØ_]}”o§ĨLnÂë€nŽZ'¤÷Š+‹‰:nĘuŪđ2ŌĄāŊī+ā´"ėņiŪŋŌh…A{‹cš^x°ZÅˎ’F”­ÃqNԈ Y҈˛8ō÷‹qä‹îîqŦ÷g÷k÷OMí×RöÕ( ¤7ƒâœƒöuhŪ1ī+Žv3ÅxÜĶÖĐFļö…`‘F*Ā$™Š’ȋs=ԜX†°đāŊ"váGšJĩgm|TmÁq"h_(‹&kSÅŦé€jVÁžûˇ ÁîzëÜ+hÜ‹É>§g ÅtØÔ” •Z!KāfL\YŖ×§@>žF5ŠÔ×ĒCwž ¨ÂČ+ #¯,Ŧ@‘[Lķ8ˆ ~˙ŧčßē%KÎ-YrđķwØ#F SÎ]q‘Ũ’ßᯙņ†äPXÅX‡ķ€ŠĻÖoeQuĶÄȂRēA…Z9ęÅj5ę4Ą–ÖZ]ę§åz§ÉėÔÉÆžÄĄÁĘž9]lč>žJntŖ|e5bxlÃęÎÛŨIú*nņĩ˙7ž­/¸qy´~UĨųú˜‡ WŒ¸~3]Õ.hîļĄ%÷hhEŸÁyAČBnr„ Bƒ(Ú;@G;čH‚Îā=&0ÆK‹]čŗĀB2ķøž“NŖO ƒÍĄĶ\Īž•¸æųqūOķŊP䇂CíŸH€ú.ārųŒJęw2ĪãčdåįxŖ6jAŽÖŗÔV“EËP Ns-EžfP1H{ øn?ų-˛Ā؉$ôīūë~šŠ}Ü6G Œąy°Šāä‚g<ģé¤'R…™Jąö3‘ŲÖ'Ø#Ôa)ŽˆĒĄtī°>… č ž)Á›SĻ“%"ŧ~•ĸÔŧ?b?SŠĖ˛­O”đ3¤xĸ„xTC&ö­ˆvTsŌëÁŽČ‚Ũåĩ§Ú#-îČHŠoŊÆŨ v&] ^ÛąĢĢ*gûČD¤#¤Ī&ųųÔj•Ļ!ųXjURđÛcŪ¤ķ; F—ÅŲ2–ū°Zđáp iU‚´úŒĖ@„ˆVb+Ļën¸ &ŅŌøhAGÜŦ6qŪ7k= =Üx9R dįr $& ēʗ8_¯c/”ā••°unIIîR(Jƒ\ZM1‰Akō3Á‘M%-v/ho3m<“ŨÔXØÜfû˜Æ Ō‘Á¨?ëæŠ÷f×ÄYÁoéTkPŠå`üU~1L¯ŲßėoöD[žžJē›úāx@RbaŦD1!ážįa9ÖtÖé{Š›ĨžND>-?RŋC9/NĶŲ’“ķ=UâfÔĶĨ„<ōé’üČ"xsŒô^åh]€M&˛÷˜´´Üˇšã#ėí6Æ#!ŗJ=OV.g#yīāčČpŧ'¤’ËĄĨŪ¤ŅkX‹÷ÉĮĮd*ŽÕęĩ*ŖžĨŊæí;ļoqú•:T[R„Ŋē[σŖŧYD_ž¨´6_ĄI”xû€UIEÎ ŗŲOŠëd='"áU¸‰."įKÂŦ:ûŠ’ē^ ĨšėÖá ßmõęN–ŪŅŲģ%gķôlËg&"rsTöHd0€VĒڕ †Rä?‰ėIgŌãû:ĮC!b4uS^—Jyšúüfoŧ÷AØįƒpŧ‰qÎMAëęá9ģNg̓ɂ™°NkĩĘÔI/°DOyf•g,G*˜éŗÕí5ĢŠ ˇÖpēīĄSp˜ĶĀNÁû<ŅS%ĪŦEyĻd9˛hšzäņ*ԁ`Z$5œō ÍP>ŠöfBųŦ—eZ_<Ķę9s&<üÁūhP=L¯ę÷7 $MØŦ᎘ âÔ›ÃĒU+™SgfWĮ"Ķ-ēs¤É…f÷ų=đ‚ĖA´ˇáx–^Oh…y°ą ‹…|Š'úÎ gc‡‡ĩ‡°´ &Āž›Ŋ"&NO–Æ|gK1ūˆWb‡Ļ~iŽc–ËoŠåD‚HZFÉâfd5Uk5Ēc2­Ũh‚Ė^­‚úiĩ93œ5gLJ†dūAĢgIÚsļYNK9YO#ūƒoZœKKëDŗMĄThŒEÄĀ{än8f;‰Qb ņS12ŧôB&ˆ84oÖ]ĘÄáŋ ŊkŦģ('Ɛ2ŗëÁm…H>;Y{ē`Zc"Mœ\JNĩ@ķD­öNļĖz&ÁäɂxH˜BUôÜIäãS ŗĸŒ@#áĩŠ…œsõĘëÍH„7zŖ 29út žĀ´öt‰0ņđjuņd ŋå-ü––ÂÉz)ĐPu/‚ŌTC̀¯ŠķߞŠįĒõĮ7Īlš!ūČoL”ɤ#rˇF9ĸ×zģokvˇéYMČķDj´Éá:4RÜĶãJ„ŋM°úē'iĶķ*Õ×Û[íQģĻŊɡkRÍé‡ũ–‘ūx쟪b qKǘĩiÔŦ™×[Hi ĩų"}MNčĸ{"=.MÚæī0 šxēØh—1–Ī6´ęœacCī ”÷ģ\$m ~gņ`t}ō{äŊpF’ôō…ˆqĐA¨ 8s„C1k/Äg}ˇ™Ã•ŗ\=$1ŧ"ŽŊPĒ^SÉ*Ë-ZØCÔšYVy¯œˇ pÂimķŖŒ8ûČ*ŗPjsCûÚŦ@ūSUZۊƒédųLå¸~ū‰EŨërPSí€ã”dūˆ3Ę ĸô āÁΰ‹¨ Ō6gågpŋŪŦĄDŖD/Ûķ%kģc{ Ʌá†ļˇÖĨzũÃŲL•›lŖņ5¤ŧžTƒˇÖfŌ"SČHūēsšĸ1ˇKĸ˙H˙&bjaŠČ€ĮĻ<0{f*sz¤҆ĩįKuWÔeđ܄ĩŪ#p1ÔģÚŌæŒTˆ`õ{­Ų퍙Eãuéô ]c´R>{c€vŠ´×Œ<ŠPâĄ]Ļd­Orŗū§Dûēn¯Îú$43˙SƒúũeZČĪGĮī(ŽĪ ųÂŖGW >ÎģRąŽˆ}ŽŪ@Ŋ×73‘ ė;´690lv9šą&į4jm üŽü l-ō vŠņŗ‘ß„ũ#aBNېv͌}†?Zu PÅBŊk ˆ~AíŌzī]]ßŖĨI-U^Üčä§ģl‰XÄ\@F+đ>[ãŽÎšwPLF"Fä ΎĮ•§ą|AØá2hy, á Y”iH{ņϝ'SČ;€v4ė˙ŨR˙1ŽüE§ĒU´ŖĶā‘‚Á j ;i­&vŪ2Ûô)íæ¨hJįqŠOՔ*×YbįK–YmͧJøZҚÎ/N驔-I7LōĨæ´¨hāÜØ\ØÔfķölīļ&)‡Œt^ĢhP{ĩ*č)"ƒšĢô( +$ŲÔØWIđ•Å5yāeėA`äHŸā!­ļąWÂŗ>Îäš1ŽeŠŧ{EÄuԄŲ+ĨÚ÷ˇ"7qĢ/đ2t‡…Š3é8hõšÅ ų Z¯ ‡öût­œ‘1*KÄYūŌâņ6莘´BĻEčĐŊäˇĀėE^Düúk"Ö?įIxjë<ØPpęØÉˇ2ŋʐ™–SÖœeOūwŨ÷u¤N8ÅŠĮyŸZ ô^fb'KâfXÁ–S%|¯Ž=‰S3 ļÔ1Â)IÂE(4 ÷>ĩ|&FKsSõ^ä'BŠ,ō›š=)ˇZF1ršuFZ‚ÉîX÷P>ęÉ­ÍēÃ6ŋadB íÎBc}8ŖŽÅ{“ĮŠÍ&AÍđzÎvxÍæHĄ9Ü”j ŋŅŠ ¯‰Ú\~‹Ä;ų!ŊÎ1Ÿ'˛ĸņ,áw‡×y§r ŸĩĒÎş–‹:ę*.†ŧōîˇ^Å^ąÉ}Č>[˛ ÕŲ’á<ū´äã%î švˆ–ú ‹| äŖs✌\^nûúÕ*•J=&“ŧÅĮā‘ę1OĖ’Ņ2†¤xÁĸRČč­Ķ „ü…1 †Ļá¯aoâ_3YŽVéąL‹ŧ—1BQÜqJ釺Í&:ŋ’ŠÎ˜g<įĢNqÄTL—.P{ÎךÃUsfą7\ŪEwÉ{­~ŊYÃ4ėnėX›dĐÂ4ZyYkÎ[ŒVϚĒû›ÅS•‰ƒWVūģÁĄt”*Į°O ōīŠŋ…}ę#î™ŨoFH§Éy¨ĸT^ŖžÃ¨wuķ(æĨ“Ō]FĘ™ąÖ&fj›ĐTÁâ1@|-ž˛ˇôžúŨhR´Ô¨ãŖT(´bx”ē˛RNŅ_ ¯Íî5)Ƀ€ÜM)čČČRNSrÍhvčä䟐ä@Î[M&ĢVF}˜$ī 1B3NÅiËZ*‹T¨`ˇZ]ūTõč×Z^Ĩ€‚HˇM­Ÿ §•§TŌ’z7ô)÷@zÅx ˛ūžVĄ`ÍķāŅįŧ‚G)įÁc5+8fLJnFyu§dnTč†v;Ŧė¤ÄÖ]eŠ@ĨzZ[Ѝ|mSG°šŠĮĖNĐc¯ËÖeŗ@k—>M~„”霋‹ ÉiT´BÃ^ ΨĻIšZU>J‚ĮåŦ‚ĸUÍŲu ×̍5ŠėK5;'SRëÍĢĸ);§¤ åĘöæÕZLR,k+å åsôUŠúŖ|=›ö€æÄâgÃĪŪĩäŲģVxöH"׋įÚâåį˜`k<ÚÚŸ}… {ũˇāufšQ"ˆsM˜ }ŒGo °˜į™`#×į—ę'L*T­rYl¯C¯Ádrčå: 0ųvŋIĄUZ#nwÔĸTZĸnwÄĒG+ų—Ô jŊš‘Šuę?æŧqģJe{ŊIĢJeM"Ę.\_įčm¸…mĸí&ģa"sĪĢølī>6–ŋRąÜžG' v´“„ _äô4­Ôč3rÎnėŧ čd†€Ãî3ȕJ!āt„ĖJĨ9äp%hFąüE^Wķ,è8õŋ{œa‹Je ;+ËZ#°Í—¯ŋ§ÎāņŦ¸K‡qž<~‰uų­Ŗ %âjū*^ĀDÍEį  xzSˇ´­#jz"ˆšĸæŌcĘãI J&<ž$úL^‹xŐ´6ĩږ„Ŗī,låAâ-(WQą2´û1ÚdWI¨ņo"˙KY Fqã¤í Ēbu0ŨŨ™B?ĶŠUđņëę•bî"ėĐzķ㈘Ė'd‰|ãUÔÛįdž:´ämWņV$‹ę*%TžĖYŧTņ—*ŗßbņ *™ÆĖ?ˍõV=/°€)›—ų¤ĸéÁ{mŊLĻ÷Ø\Š¤õĒ‚÷<+/ŦđęCœ:Fžŧ¨ǰšąÖU¸€k}@{ėHÛ§6ĩ*|bǧŅōKå,ų2jâ#´FoAM¤bÍ~ĢŲ/¨ĘŸŦûvŠÆß >1a7lŖå*Ú¤R¨•@wŸÍ VĮļŌׯCNŧ9ņ*)ŖĻ‰)x§^ŊBĮۈ)ÔËō?’/˙ˆ{Ŧö˛ā_ÉÖuŗ€sRVėgđ˙s??†zcũû +€~Q7—ûÍ"ĢĀ™"ģŽđˆ˜ÚrÕMāŨ8‘^¤U %´ F5J,Ę/Ūp.Ĩו§õđ?đį ´ŅvšC!—LgƒôZEöÃį^%åÔĐVų|͉ō3ā7Ėc„_Õ…–Ŋ)”’Lá­Ø)“[u‚ČŖäqŗá‹čؒĮš(2čDčÍÕ wS–mQāÁ/ˇMmÛ­ĶǎÔTËD›Ã›hJŪ!˜<Éė|ąŧéĩ–7O­S1púeöŧüŖ7fg_˙ņn§e2JÆōˆwÃū ļĐKô‰3…^\]×K–čķ9ÔR=Ūę\…ŗĐÅĮŗR“Ņ ŠÉˆą•][ôÍMdXRúfA~æh[ÛBŠ 6ŊÍŠĖÖééišäf“C§ o?JZgßøŅË{…ŒdT:õwÁ3?| <ķĸ’gakeôÕō8līC×9đmzR´AŒÉņās„Œ°ÁßpN%Š‹ Jz÷ĩˇņ~Xāą%]OOã˛ôüæčđč­qYôVƒŠJô&KĒ7 › Îö4ũąŋ(é܅ō—ŋČęXHN9ŗū+įžŨ:5wūËëĄĩO1,ÂY¸ļîÜēŦHM7øĐEG\&õ„ž`āgcqķ`ãxą}đ„%ũ>ÄsQāsĸ7AĒ š˛š4€*ŽŌį4CÛLAÆzSđöÖgĪ}6’ļņ āļ įĀÆŋPjY†‚žÚú/ŸŸƒ2zâ: %ōįpLߋĮøŨđøgøø><ÆŖöŸ„>oŨün ōP•åņžąđ1šßĢēLœß+Ž%gų Œ7ëõNff^ŗÅkT‚ōËÎ5„¨T ,ūgå¯rfņ9Ę,M×˙•q3ÃÄzb/qņ!ĸ„Į;t¸Éu—uŗœ;ˆęËWEŖ\m|Đ?ļë_¸Ęú.:ÁņÎęõĢŅ —JøŽūœXįÜ?Æíú—7P]:Ē”ôĖˆĸ#eëāŨŌēŠæšƒ$žƒŪ^ -lüA#.ôĢ,$S•’”/%WáöĄH.ČĮĻNíŨx˙†xhũƒSž5“[FE-įŨVÁmTŧW˛/ífYŊ ōZíą rąŠ}‡ûōŗÛG› Ėš“îĄtÚMЁLķPZ8âīßĶ]=X°7Ũž}S0Û՗ßZ?05™hŲ8ēĘß=;Ųø@WĮέ[˛ŅM›'#öUckĸVŖ„,§ąļ•nŸŽ\jRaąZ]ĢĐú;Sžö¨Yˆvī¤H{[×@<ēĒP8›Ŗ{˛ķZ¤éļŧ_ጚ“;vîHyōųuBâ}žŸ!ļŗÄ•ęÂŪŊšíęZku¤č–Ņŋ![ –0’q ÖÃŪæņÜÆR-ÛįÉö‚‘ģz÷ĻŦĖZÅā–ßrwėڍvnܗA›^w‰Û7æ_Aü†üC‰8õ‹ĮE:sŽQ'I‚°čIܖߖāŗŅÃæJƒûē2xOė.iģĮü•¸øĀdAĖ--õ˛ “Ŗ0˜( tMČeEĄE’„ģÍ­›zq—ŪōĀ牪Ã×ĀzŸÚę 0ĐË0ģŠ–äē¨&e€¤Õŗ!ÚĩŠ?´m˙m;žØ™~dG÷Ofôœ/‹vĮLw¸;6uŒ>ÔØ´mÃx0ŲáŅ”_ņäÄøhČ×ÛÕjí-Å=›ããŖcŅĄá}­~V=2…VíZ÷§ųŽ PZ,+¯TĒ”ŽÖ´§)hĖl8BŪ¯ņ'Û|Ū`KûŒB¤åÚĨDąÅ•DÆFÆ"öl&CūÄsųŧė:äöCēÄYâ]ąîg·OŌë÷­9Æž'Ķ—vœbØnzäã—É8ąZVņgOlŪ‡ä jnĮžwbßzÄ~ü‚HM@Q°2{NQwŗĻ'ÃđßæūúāŲĩĮž”Äfĸ¨ĄšH/¤ą   bž9Įã%)ŸJCN’ ô§Ž"ŒMķđΒņ`aéK~ô×%øšÍĮæōŲŌæõ=ķ@}Ą´™ i,#ĩwŲø—*á$IS4‘8FHKõ!H*(q÷,ZĐKR´8›8$n[IWąD$Ã~‘ĐT#Ëh10ÆÎĮķ‰=‡Žv´nÉļ|õŸŊû[Ÿœō›ė‚Æhąe*ƒßkÍl˙Ä˙øõÉo€†Wf#Ã{ē6~Ž`Û Ĩ4­tƛk_>˛˙c}Į÷­īôÆĻ Ñ-'oß|ßDđÚLtüđđšĪ vŨux*Н´˙mˆõeš‹Ic{cãÔ`üŸ¤Ŋƒ‡6ûŗģvl 5?ōÅŋ=ôEÎmiÜxįÃ[5˛§Į¯”ëô:­Â˜ÛttՎË_úøŪöCß/ŋō7÷Ŋ~ųĶŨ m2CĢÍzƒM§DEéc}CE!Ú5žŗ­gßä{ßõtŨŌ–ÛuM‹”M<ÚĩˇŲ’íč'ôäbĐ ,x˛Įœšî"ˆ K}Ûõē™n\ŧ˙ëŪ˙uī˙*\äļúįpŲvãū¯Â\‰ÛŠŠa…‹%øũ-ī˙ÚÜyüōŊ\ēŗ}Ū?gëÅĀč]ëÆGŖĮ֍†ßyjķÄß9ZBŸ§žsßäŲ™BįOMN>9 ?Ÿ@1ŋë etÚjØ˙2ĢæIÛŗ„N­šŊĪ:ļ@ˇ<ŸŋvīŌ‹ü0uÁž™+á¯PLrÉ*!]šL&ĘRkPZ="eŦÉc<&ÕP†0Ú ÄhĩZEnŊÜĒDņšŠĨ6V![Ig… č$Cäã"{r'´Â…ôíūÚžž­âÚûÎ]Ĩ{ĨģIēÚ÷Ͳ,ɲ$ī‹ė؎ã8‰ŗ;vö} !HĄmh)”­ í¯íkŋ¯ ibB ´¯P\x…Ļ-4Ĩ´…Ōö×Ōž—וĻ@,3WW˛ã$úú2ųéęΝ+Μ9ķ?gÎĖ9ƒí-i@ žíąL&Ŧi ?ō(įķ59Į@S^ß()\)ÂŅŨ|œ^…!ŋ.´n/ŠX}ē´\¯îękJǝp…ō;4zéDžeCžYh ^RYm[ųtJÔĒÔŅœfÔŖ+RŦ Ÿh2*ŧŽÔ F`™=œ1ŲŌs3íûk9šĶAÍN'ĩ,ßĶĩôČHĘŅ}ÍĐâiĀR}˛S†œëąY|v“ū­k{üŅ|Ōá‹úh¨ķŠhC[t`Wovũļ}Ŋß֗ĸu/,‰ũ>ÃØ%údđ9yãāŌĒÁŽĒÁÁĒ.mēíƛÚLmļÆ1`Ëŗs—&'jîJÛ°§Ö•ÜSg›Ä’œjđ.mk–›Tš=Ļž=W}ŨXXHNÔ@g›Ú!ëŽĶ|SqÍûđrqsÔ`+ûßĘ9—ĸ§ÅCû[ö<¸ĢįšåęœB¸ėĸ]=]ē5‹÷Ü`ô$Ĩ¸Ģģļ͉:rƒš–õũu,­gHœÔYZ–^Õ1üŅᄯ}¤šcįâÔÁ…ÛÚĒxŊœŅâQLvöG|öe™úĄŽ#:,PŅÁŽõą9õŪ`,H‰NEP$ŪZ“KŽímÛļ¨Ņ€Su‹vÂQ˜dÉ:2‚UcIm…(éĮpúŅ(Ib‰1œ:ĨÄE˙HŌ֓â*v-š ĶB ĸ8‘ņĒSĨ‚"*ųXA+jĶbjápč`°bŽČU¸ąQDBϐ đ“u&ųûčR›ßÄLŒq˛‘&Ņ”ü”ŲŸđĶūû‚R܊ā'ŗá—Ņ1}”ÎĀŧ Ėjöē\"ŅŠG›ųu‚ūüĮƒDø|!\ßäY⹙×Ŗ"\ŠpÖÂY/‰pÖ2ÂY?ÂĪĨ <üĩąÚĢ>xĶ×6ÅžakÛļ°k§ÛÖĒ^=¸\ĐîĒgÂũĮÁĄOíęhŪ~÷v…Ŗãˆ_ c°—–FG wäÉ#q0a6“‰IcĀ\Æ‹× Ø•ŨÔÔCBMrĄQ™)xpŧvz&4dü—?Õ#‡øÍ™‰•ŒŖiH^Ā_€„˛Mļųdú:^Ou#ŸFȊ&J'ü?UH´J6‰ŖŸ*CâųzɁÆũØ˛ĪÁ^i.­Đ"\ÜņX*–Ã÷?Ę))’ŋi´zĨ˜›†ˆÚQ×ĶāŦF%O`Q:wNĩ! F.:Ĩs ?Gé!æx‹ĀŦ`ļūá´¸nCۆšuFŠĶSŦŌ1|MĮŠ[WÔØģ÷ŸÅŗp ĪĀŽuƒŊĄyÞ*ŋNr™~%´WÍ-t5lÚŽĀļ@ėģ Ō`¨Œ}iŧ?oœŋ82?™??’'xįžb_‹Ô")õ*öÍY\3éķQsV*WŒ}ęÛsęKØˇØW3YP€RŽûę˙IčģĢ}īƒ;:w5 :šāúÜâ]Ũ]ģņÅûįŨéÅĐ¯ß­_vaŽyŨ@‹ŽC&(†o^rÍŦᏎ@ān™ĩk0qdčÎ- GāÍnKČéxíK3õ+Ļ`/_ŅëĢ÷ ė NE˛Ę>rNÁ“]¸ao’%~ĨáŪę)Üŗj¸Ū‚¸ĮúGĸÎ$ų$B3{™KžZÅq°ž,”JBÔË|`Ô#~e–īŌ™üęI]ßG3äą Î’f_Âë¯õ w‰Jņ‹ Ø ž;õ~øw ä|˙žŊŸ_[Õŧũž•đkŲ~ Ė2ųx•ŒBŊŽšĸיFũĒ^×9ęÚ¤=ņ,ÚķŠēℊuP­ÛT: :ūėe´ēōî,đ* ŌÕkŅũF´ hfuos&ĢSļy%ÆĻ.Â!’Ž=ĖâFˇU˛Jō~F_ZŽƒõ[ ą+NÖa­ØŽ2v­›Ōén.ëtŲĮ2 ÜŒēÜqzKIĄkēŧB—Íë Ĩ7:§Øđ’:×tiuŽáŊÕ9VRgÔM|”ˇiŠ•/ueĖÖÄŦdfIG\OŖĶúHT?]nŲ‡Å{Gž ^•Ĩ^É!ëi8Ž,ģÕøb÷Žá ĨÆæ 8Đ9sFŗd=nKÍ܍MŲ×]ūoQH“ßÂ*–ŨRÆ˛Æ‹°,ŸWĘ`ö6˛_#ÚŦŧ]âĀ÷Fąŧ]ƒąˇ„ũZ…°Í”ōv™=/Ä/đ?ƯpsáŗëVĪI P3p\u×Ēļæ­_÷ļŪ=HؑŦQWČ.ī˜ĒģR™Ąž €*6@‚ÎŋnaĖ“H´Ží ęž}‹ĸՁœŠÍ>ģŨcw¤ēĒĒ{’všˇ›e;O9j{bž†˜ÍpPŧŨ$XDŖāq™ƒŗÖˇ×-•` ĒēkՑÉķÄ­dĢÁj5Ŋ­Ö/ŒÉGŖ…ĨÆĀ×ō‚u֞#~Qdũ›ØŌÁá)š aØ–!z՞+L+j+—… VŪģûŪgÜÂn•…Ī3f¯‚ŧīŠ5PdRHփŖ´Ų÷’ãįápĸ‹_Ā‹ŸÛA‹?đFyđ JtÛd—ÍĘã 9ž-9˙\Įˆv|pâ—Į'˙›XĮ˛ŋļ—W/ë!ŽųpK^opž“6‡˙1ÂōÉyŽ mĻÂ˙¸z‘—E¯Õ+īŲŪŗsI[DŠÜŗcë'‡c;ęÖw ĻdgnaCĮ¤„Ë7>įĮęũw/;đŊ;ôėųÛŽųâ†d{áŪåđšh+Ü GÃí!j ‚%ąeŊM>å‘ ĘÆ™ä1,¸)&ŊĨašĄŽĢƒ&ŠĻ|•ČŗX†–ŪĒ Ā´ÉjEkË\VkĢĄô=ņÅĄ`Ęõú¯%‹„ŗYĐQŧėm™ū6ŸvĘvaģ,ÛE˙ûÍz x­’5š/$6Ø2ᝁ29‰m‚­Ęž ‰é‘÷ęz8æ“pĖĶċzÔW͓ī…‹g ,]ŋĸrgōŋ2îLBîŒ˙¸ŗ$_ &á°ŪäˇÚŧ&ĻØÁ‰hßžžhŲSãõ§<üaŖĨ8†ŋ F@ĩß˙"S:Œy‘\6ŗÛĻđõœ¤zę'ļųđš?)ÛM(?fÆĒ°ū_ZđäK/ž:ąŽ1ĀžĐŲ_ėqj™Æão¤J=…v1…žC ģė˛Örąé¤ãāˇ>tāäžÆļƒß<ô!x=QŊ`o˙ĐõũūØā5s—_?׏ž÷o__ŗėkįžôésĮÖ,{đÜ w>¸eŪŅoíÖŽeÛ e…26SâP?.åõV‰3pŽEÔR´}á´gi.¯fĒ{ū%&ƒžėŊ‡# ĢŦšLŠ(KŲb)ێŨX–Ž#SŌõĻSœÔØT°IUĀ.Fļę8Ŋdē€9K8Uz ‰Ų¤&fĢbļ ŠŲ%ˆŲKMLW`8As†Æ˛á䝡m ’Ģaaļym_Š…mĻpZ/5/ÛŲžúöUIeö‘ĢNã)4mč—]&=#zŗĮj5vå]ׯĮį5Q8sp[xĢċá#ˇō†žöw>˛ûeŊŦEŒ"öC*M›;d/’ˇÃĶæœ:wø+’›‹ĄæĪilyĨsŽ4wøĢ*xŖšWaÜĩėŨß´ûĄŨW/o’taäšėāÎŪ˛Ņdyî°Ģb4Yן1j˛×”[žĢsäč”Ņl_xۖŗĮĮÍ%'3l&Č5 ›I æGÛôŒVYũ!gjÉŪŲpÆŪÄáTŨ’’Íä’¤Ė*ēmžB7cŨŪUÔy$´†Z‹ØĨ—¸ŅŌ܁Ës…iĨ/…qĶŧâ/k9!Íōķ:ŗ:‡ĐMüļb9yž2û“ž`ÚË?o2!ËÉø*8\å-ž^>’øiŅm5y]N KgԗŒ'?ķã/OĖ-ÛN —™°8āÔ ûŒ@ár ÆrL øŧĩ 0-#ŠgjFžîf ÁhCߞÄH€—Bų´…uŸ7Χ9ŧÃë›h/Mų4á‰ŧžõAĨ'(´a^Nhf°8†ŽĩEwŦ˙6Z“Īŗč ‹ą‰g …ÃĶPX’âĶx<ž*žJ|cÕôšqÕ'x*ÚŖŗ Ķá÷ƒiˆŌä4”&‰įRW}ãđ _Ũ¯-|ãĻáõŧ3Ū:¯véö6ÅĶšŠ¯qi[•Mßvīߏ¯[ūĩs_ŧįœzũúēĪî[Ú`üø7 Ÿ|áĻæĐŦÕ{nÅĘv ˆÖI`P{!ō€„\ č!Ųrļ‚˜Ú;˛ļV&:¤`ˆøXL‹ĶHĶb Ä4’Į´Đą1( x ŊdãĐ''!Īxø ŧž…ŋ)Ąāsĸz_Î ũ„¤v|㋐ĒĶ1\ĮSh¤í”¨?ŽVÃĮŋĢŌ‹Oų¯*ĢB' đ'hôSX})eč_nÂ"^š—-™°D$ĸH@Ŋ€~÷dÂŌ,Xǜz^ícŦÄ= æFŅōˆøVDüõāėŌp€‚4qĶ•ĸKŋų(B^c{^I<ęnė<žånAÁ#R-@ÚđČŗ(ķˆcQq5Zęx9˛ĸ*Qhf$ Ë…\mZG|PaŊfõljŧū'm}pjaj^ē+?ŨÖöŦ¸csƒÅíåUcŸË†ōzi]ÃP~šŧ^ŅP…Ö8ĒTcŸ$ä”ä’k5yÍäw@ŲU g#?.Ék îÜÉ7Ŗ! ­Ą(E@Ø"NTá3la+ˆ( b3ˆˆ2Iˆ!ĝ@ÅRš„Ĩ Åŋ(>Q‹™_Š•˙ËS(–ž+™Į&ĪįŨ°„ˆ†ŊˆxJD_D$E´ŨM|—°(F–”„ {R E“dmęŊ $Ãhąúâu:×^§FūŒ˙Zuƒø12YšKå&ū`Pģ…ķũ—(“§ÆãO{Äģ$KņK%uãj¤ø§rĀ ŌĸĮfBv%BF[—)¨sœ6ˆ˙~ĸÍ(ûđqüįÔīp†¤ ēŪs‚ø āZę70‡Öræâ/â[Ô2Œ–Ķ ßZĄæč´œ0ū~’zæčĩœĩx?Ø@_ sd-gæŦTsL(GÕvbøĪņÕfZņrUŊÜN /ˇg„ũÁg¨yš=SöSÁg đQYAŋĨ˙yt҇–-Ŋq0Rĩ]Dīv¤ēkęzĒMÎÚîx]w\ūæĘ{ļ7åļÜģføŪíÍõ[îŨ´x×,w´ok'ŧē"}[U¯—ÉZp->§ŧgqā‘GÕIÜ0ēާnÔVĀK9mÜôhA}TY§ŪËVz­Nv"įæ^^fŅܙy‚ā >›íFrŒŅSpë|ī ƒŗhšŅČR쉴ĮÖpîd=žŌ´ž4K~Rôo'ūœwŽá]yVo}Ĩjŋ!û qĻYšĩŠ´5VY_)ĀĮDö™,`+ŸđHŠģP§[A/윪ā[ |ąš“8ŠĐŲĪÔĩØ|K¸%áC§!´\ŨŌWÕąĻŨkL.ŸŗĖ7w¸=¤A‘DÅ$q÷ÕÎĪ×ÛR­fÅL VQqĘv īkœŸö,ŨÚŊI_Đ šfláü˛å3^Ę{į„zCŊŊĄF‚ˇŋæß_W}ÕĮ~äųĨ÷x¨Ú§;÷+ßŅ ųύ7]Âōéė÷T+`҃+|­ŗöéBį~JųN…á´HM—ZšŠŸ æõjEϐĮDā+â ŽéM 4øĐlŠ5ĐžēYąEļt_mÚ9L2,Ķŗx¨ĩ-ԑöCå '(CMK_¤}U›{ūŧĒžŦËŌ8Ôę3Hà VYqÉfŠĩÁō‰4åšŲ@wĩ$ëMV“Ím”zƒÕĖ잺ã}Eœp§ķęé˛ĩøIŧ{† gœbhā†=˛ģĒė†}_ü-ūÎyĢÃõ™˛‡ĶgHØ>Án1ŗāÍĀØk†#‘qÃÛGn~g ´Ų ī8tø䓯É?ã›đûϐ:/›1+ځũ˜pČÁcÔÍpXí†˙Aęé3OkH}Ŧ ĸ‚Į đᕮiá›Ŋ;úælíōúģwĖY°#蝺~ŅÛQ•ņÁėyWÔ%—hp΁álũČ s—7ģ]‹ģGrOËbHŅôä;āü.ˆÔe¤>–gU¨>ī:Dž§ĄĒú|A}đžû”*(}‹NrZLNIEi@|’Ô-ĸÅ.PÚŧ†#?īĖgŊY‚ų,šā@@(ŨŠa¸Ōŗ[RFé3!”N ˜&ķĸ^´ëąĒCFod7!Ú֊ĝfdCx}Ŧ ŲcXĻ‚×á‚×#[Ü`ĐĻrŽģ5\į1Ö'š¨JwŠ øhŽ3)ë”bsÖ7oĖšė¯M‚Eô7ŗÉ{´N4!ȑĖfŅ,œuŨ1ĮŦy‰A¯wDØÖ…Ø'Ëx=š7öÍ õ5‡úúBÍō°ũIŪ‡rš˜X jœåØ^A0{ŊÔŦC^30?¤ą—Š')ņėnø_]×ģxŨ ūĘŦÚ ŪØ…ō/Pæ‡Ę,Xúøôõ=u‰â˛ēxũ…Ä›ŌÄ!~‹U}›ÛŲ°@ëõ:Wuku0íäh{MÖ\;g÷g›Üؘ›&!Č‚bCõŗ"‹œĻ`ÆSÕSOôgŨz^í§,Hŧh÷K֐Èb— &ŽĖ$‚)Ņ$Ī k’Œöx[Ø]sëHG•ÕČ<ųŪ†ßŽâøÆ)˙pĮåe%ęô&AōA Ÿ˛‡§ |÷%W,L+{…Xnš†åÂō67W ŽÂ…Ė`pRO˙Á[ƒ6WÔÎÎe˙…?÷Ŋ1‡k7˛)"—ËŨ$D'Q‘El'™’3zq¯ô_B:k#~=ž‰rC=Ō ›ū˜“ƃ[(;Ėąh9­øGp‡ZFŅrđ-QÍąj9fü0ŪF™aŽ å@NŨ„­&ÆH?æÅüĒOŗėi,uúŒę•oō4fK9ā}y7-¨āH(Ô×+VtVĶIŗôų?r"ÚģÎsø­‡Œ‚'õ"G˜Y#Ū.9ÍQ܋ÜgŦŽ€Å@6Ŗ9%čv¸8]ŧ†Šb"ļ&GČųƒ ˜Ö*ŠĨ°Ŧ›-Ā–ck°-Ø.ė:ėPyįāÖ’BãõZT]Ŋˇf¯oíÆĐF]߀aËw“Ũbm֜-Øģq ;›íظ÷@q ­´šú÷ė›ŋ¯ë†ƒŊëļīŦßé^íY-/ZĻ,ÛÛévļ:É'÷ÜšzY{2Ųžlõ΃û˜Čæõ$Lę´¤ÄÕâtŋ÷@oČä Ċ˙\ũōĩ¯>`Վ rŲL]T운ĢUģ–Ÿ33îg^g>g” īÃ3~ŋü÷ˆ3ĩŲlí=čã\&I‡ĐˇbCü÷p&Îā‹Đį„eāˇTĘNøíŪÚÚ,îĶ øåMôÚΞĩŲ$üĮŽ ?ŸĄ~ĶēQ5~î0ū]ü1ęOXF‹(ŽŲ"(>žh\^“~=M8ĶÎtĖ?.áëJAJ(€ÔŲR¨<“öĻŋ&ŒĨŌ˛ŧ€ĘŸÔÅÆ :-8Āfš™~ā TĪÔ¨¨ûLfÕ F§ÁƒĐSŨ1X3˙Ļ‘LvÕÍķ;÷V‹zžĶ;8Į’ÖÔ`ŗ˙ęMî†TØ ZôœXâsĢUĘlŧ{Íúû M 0û<"#úÂ}Û{>öaŊQdXƒ‚đčp~üg8cĻu§°ōũz?ĻŌæVÔo0§K€2"{ƒY™ŠqŖQ"Įmcø­Į%5’@)ļܙqqBĨ‰ÁH`9^(—˛• ­xN ‡ ēēJǝ ‚w˛RņĪ“ĸŅ(NzĸÃ6J,õ€ÛûKÅī_áM&ÆkvÃ:Ãũkԟ`ŸĐÚđ}|Œúŧ˙wíū‡ÄëČKK÷”z;lĶ›ęũĶÚķđƒj›ŋĢļšōC3õGŦ§ģčqŦ  ĨCiŖc |$oČBFČX›Žˇ:¨b ĢĻrĘ $BÉ_âŦĒč¨ÅcMĮ Ú ')k%0L哊ØsNJŽxBõ‚B"=:å&Ŗ2J™OPė ¤%ãÍÕs6ĩ´Žrņ˛ž°rįÖē[úŦ‘Œ3Ô× w ×;sÉĮęƒ•3ˇ'ęsÖh+Ôß&F›V´yĸŽi–Ä0ĸĀļdQÃ Ersëŗƒõ.`bYÅÜÍSœ;qDŧvøŦžŌŽ„´[¤Ōî9•–Ë!íQĮÖ¨´Dcmõ;¨ÉĒ‘F-:?‰x*#Nb: Aōˆ7H5ƊzĸŅé‰Ķj8†ĮáÛ3ŠØĻĸ1”ĻgĀ[ņ\Ļp›7ņŊÄ2œĪcq@Iļ%UģÔ 6“/ĩ/ūZĐëāXlF´+_÷˙ŽŨķšÎ¨# ƒ ‰Ũ2J`,ŒuĢ1'BãAˇÁ ģa]FåJ(&¨Vœ-iáĄņŒļr •ß+ž¨QÅSQ›>Z¨mü€œĮfƒr›ė*ūļ×I.›ÍË °¸*ėŠGbņ%›€˙õ8€ŗĘŅãČߓĐIFü5ŠkœdéĪk¯k8˙ōb]%ë” ÜģÄVŒęíãFTGĮ8ŊGŗO=ƒņ¨ŅG,ęÚ1^€/˜÷”Įgeâ”Ô9Οŗ›īŪôītōnžcmúÎæĄî‘‘Îå-rËæĪlĒ3ŲđoŲLšˇ¯lØĐW5ņF g̊,*Ō`5ĨõŅãŽ(BQī3azĮø" ĄĩzFËõTÍ õZb¤Ŧ' äzĒōŅŠĘk֎rüŒŅ—ņ[Ę:1T U/å!UeģN€íˇRz m3ĨŠ?{~Ē_ß5Rė88'eJmcĢ((ķŦÎ`ãEO˙–­ĒdQ‘ j=Ē—å¨Ë%ØÆĀđ ŦJ@ƐĨ(Ö6¤Øņ&$æņT%TįYÕ튗ŗÚ™ß,°`ņ&vŧ€^8™2RS:ã%Īē âđkNNšr|N)\‹&] Įƒŋ†ĸîčMƒ Í+ķpįHŊ§Õņu–% ƒŽxÚjliķŦŧsCŽiË'‡ ÚĢEšZÂÉŌę˛V÷oi™ĩĨ/Äķc€`æÔ„ÅīI6YtMÛîYũé­ĸb…ÕžWņĘÛmWû$XâmÁžöŗĸõšX`Îǚũã—°iEĻĐ3rÉIMSĄö8áß7:ΐLßĨJÆF'@Eß)RįhƒŲeĩ: ¤Ž›Č_ų J‡ļ}Ō`Cņķe.ĀĪ!Įedˇ,žsP†JAŅX|ļøÁP’ĒÜÁZJÖŠã 8U:ŦeŅ æĢ&­áŠBß<×LETađĖŲ:ņlŠŲšK•ŧ 6MĨŕ˜4ÁiŅûJcÖjĘ 2”ŨŊŽÂ˘œ<ķ{ ‡Ŧ+*ŧŧ #Ú,(6ĮÔkõŲEú{ďŲb—ûY“A˙6ūƒÍO|“@1‰Hš„ߟŠäŋė°ĀŸ&ū‚e‡@SɈv¸•$.ÖV˛į2c`Ų‰ļ*ķ“`)恓ĸ[G…¤=€zÕ>ŖßĪh$¸¨ĖĖž/Gâ‰ĸĸŌÅm÷ÖL$;Åø›z(-#˛Ķf^C&€f;ŗŲÆ3ŠŅ봘š/ .‡]‚@+8,ĸC`đķ(#ymāŖîîšėÂĒâH9X/ūŠÍBķvSņE”F‰Ė,/øz™WTđ‡2c[ŗķ˛GŨ@ø^…Y` ĻūŠ;8œęĮŠĩH/‘A_E^­Z3Õũ÷ø Ŗ >‰ŋ€0  ëˆ#Žx_Så*ßėŨ wöxŊŊģ îęņŪhŠéŦÉtFĨĻ ^Ã<~zÍũģÚ ŸÛ°öū]­;>ˇcåĄÁ`zŲžŪ‘CƒĄô˛ëP]@=ŪHā0ļ>j´: ŽTB­H]*ƒjR ‚ļ Ē|KÕŌ‚7šb/+đÜ#§‘5ŧ sØũĘ}œl°›Ív‡_ģÕ`öZ-ĖA(Ų‰f üĢaЄĪ%¯ÃR˜wT$&• "1ũ§ÔÖ§ūúÆDt_– %c">×Č풉7%דúįÛŌAŊ…áõUÍŗĢ:×vx͙áŪÛÁ‡œ `sZŧž ürËfˇ8›æZė¨å‹E4 ÄŖķ‡wĖ: k~|š›ƒĩžHˇÖ¨•ė8ā5o*o XØ ĒU Ėå.|QĨRvâMh°Tâ)!{MeĄ&,—ģ`É=Rn™R^rĮį$īëÍn 3,%ë™ŪÄęõÛ¯]ĖI"7XŨ“q‰\$Ō• é8=lmŦš/vũ öšŽpzAŊˇ5ŽäCF“™Ņ™] “Í4Ø9{žÉa3)҆€#0+vEvX8ŗ0ķ[ÖEgåB:œôgz¯z@_Lnƒ¨įÃBŖ™ķĄĻë „[á8FaÆP?ĨĻ™æ€æJgh*œB ?žØ>čqģŧ8A§F]¤:´˙/ W_áŒFî+˛ß’Ā/$ŖČOœOķ¨.íØ1(ļĢãÆĮM'ĢR[Ā_Á(´pŦRūeŦ¤đžīā‰:[ÖĖęiv8ZVuw­jvl‘Cõáęz¯Q5„ãõ^7.8ŧ˛.ĩâđâ7ŖëÍ+ļwyĒú64lC×õ?VcOā ą Žįqƒu ˙é‰iãį'ˆ :āø ŋĪø“—â'õƒūÎOËF†{€ļšŨō÷K‹$›-2x{'ÛĖ2Ŋ•$uē‰2ĩ °oáVâ*,‰EpĢ_.ũU?ūĘ =^SēÁĩ*Ä3h…ÍW8ŒŦJq/pŌҚĻßÖîÉTšuÍ1Úũ`Î.Æû›÷6åwÕĸÃáŽ&fˇĻ•T›d‘b’YO˒ÁY›ųķs†šö¨5}WˆØ,,s"ī€ō˙…Į •jŽūwĩ–ÍjšņN@C=˜‰ĢUNśŽ`čÔ_zä¨ņWq%Üŗž=Вō@i$0ŽxsxūÂĄuŨzÁČvö]5>oC>ÔÁæk[ũ7ĪëX´ \—œ[īæ Ē Ö°hģę[:Å,ÎjR".QļȂ"›t´Iâ–/čâqŨäĪ$vo 6¨c%‚ÆŠ6õTy¨<˙cPtŧ÷h1M-ŧAYbu8-Å_p<Ŗû‡Î­ō/Q^˙ũG°/rTĮ˛ēŖ‚ËîáÁm<Ax18P<ĸƟÉëČŋ`vĖ€IĮ ø“øŗÎü) KÎ_g)™¯H4‰Ž,_ģ){ôßė’ƒ|N “Pˇ“"õ(kųã4[qŧ@cĒH*ŲzüČTHü‡˙Ūũ‘ËEũÅâr[ĶPŗ˜|‚š¯eŪÂL‡Bņ!áAø-ū^|ßÄmĖ[›U‰øíR…Ë'œœ–—aŋDē—žâô慉z^zéé‰93•t}Zzãũ“~ÕEéåË%v„ũŲTâbZēīéo†#•ôJÆę)WIc0Ŋ=•øÃüųŠ$ _&}GøŽØ#žTJŌĩĶŌx)ɁˤĮ§’ÉzQúũå’ų~KīTRúŦKlëlëėĸ–~xqrltŽt}ŲŊÉĶéŨá3_œüÆËĻŋŖÁņR }=|J‘ģŖ_.§ĒE•ô&JąĨ/OĨę5}j*ÅīĒÉ]Î\:%nIÜō˙ŲûđĻĒ­ísr2'-Å2z´EJ9-…–AlhĶ:‘ļĖBĶ$mi3ĐVC,Pĩ"â@QëŦl-ŊŠĸČŊ Öô*hŅĢTŠô_{īs’´/wđ˙žû|9‹Ļ{\{Ŋkŋkí'âĮÅŋAdĖá>$’PN$11qĘerxė–¤W“v—qģÆįõ,ÉųAŌÔŗ¤ĖMųhÂÔ ¯MŧqâŪIÛ&÷žl9ucīŸœŌkJŲ”37™n:”Z’úŽfČIÍÉŠŠ˙búpHBōsÁ’–$›ĶŪ HzLúšôué›A.ũžháRŪŖÔjO ’1*c)–ÚŒĩŋ#ŸdîĪz)ģãߓis§==}ōô 9•Ar$7%÷•97nî#ķŌæŊÔ]æËæĪšŋg~įÍî1  ’'<ģāĨūOČÂkBōŋAJūC)9]rŽä'}Ļ>Ÿ—Y õå 6$Ĩ’Ō°Ōžŧ8B’„$$!ų?)ī1 3Ŧ7Ŧ7Fķrŋņ~S”éA^:Meųe{B’„$$! IHūËeHB’„$$! IHū3R.-ה¯¯čS1÷Ēe{Ågæņæ§Ėm]eQ/,q! IHB’„$$! IHB’„䟐C’˙ģ‚ŋW6Z4 ũcš¨(ŠĀ- ūlÂq •ET¸x_f¨âũ|Y4FBE‰?įËŌ vĩDü+_–S7H–ņeÅĘŧ|Y)jđWQ3eņe5uƒŦ/‡…K傝áÔ4Ã[œ–÷‹åË4%ëĪņe%‹ōđe†ŠŠZŗÅAc$”:j _–ĩ˨IQģø˛œęÛo _VPQgø˛’.đWQŖĸ~æËjĒī€Ą|9LÆ Ī—ÃŠë` CŅčßZ]#ąķeâgR&~&eâgR!~&eiP;ņ3)?“2ņ3)?“2ņ3)?“rXx;/?ī¤X*‘â¨*Jš”™2PĘF9᧌rA[””ŋęĄÅ %+=ĘÂR:h+§* Ī‰k&øm‚ŅKāÕ#è,(•B‹‰Ē‚ų Í:Ѝ\bŠĐ\zŨxE ”Ęą%,üØ`L ĖÖ`ũ6sÔXô­R-™ŠÃëëAƒÆ˛°ŽÖA: Ôb~ė4¨U@+ęuƒ}N?ž"h7c –+ÚS†ũĀRSĄ^ =¨UŊĐ#Ņcã‘˛x7ô0^ÁģU0ׁ[Ü0ʈŊÆB{nËĨ˛Á&ä3žgÅ~„į›đU k"/ņ+Ë[$ŒeqģīŠlv/€õģĀ 3Ėt‚Ō03FböãĐÃO%Ė Ģ⑚q¤YpL‘ČŋÜ÷h9YbaüČ. îY;ąá_õmp|ĶåĪgŪ9C—s˛;‚ĀŠØŨŽIA@Hr[rĨÃķ0âŗ×ŠķˆūŠH ÷ô]XEō%¨HŲã…ä'#>ĮĖ|n!zĐH ÎūWæ(ÉâV~gڅ1Ũ**pž3ķ~FY= įKA¸a^îĘę8ŧ3z\6RÂũĒ{žë ąŨō‚ įé*|Ŗ0ãŨGģLJ6äĄr!ôáu.ė–;GōŅČÛ€`Í?s:]åiĀęĻ#GĐÁöŗy´‘}XCn'ū °û÷N8•W>åĐÎø#Įt!ûMX`â×"ÛĘī{ÆėāOá^AîEåü> <&ŧ˛ķ÷˛‚ ßģõ§Ā=8åģįŗ?`/üŌcėČof>×ųX5đwm+ļ5øĖ4ãÛ¸s“ˇņĘ{ åÂŽį<ėöČ ƒŪ!ĮÃUëŖīj„Ņ=gˇ¸nŲMđ}÷ŲüŽĀÜ ˇ`WāˆšĀI$ėa%ŧ;Cī„ē)ˆ!vüū˂ųVtÂĢKą-&ū¤rû÷28—=Ãī¸G‰Åoƒ×]štõ^ >á Ęā“Ļ+§ž¨Â~Ŧü÷Q8 ÜøŨ%ņŒ)Č#~Ekü˛F‚Î×īäc’ųpâMė’ÅÉml .÷tëļâ3B8e‚ߟ įDO9Ĩë,'Îd¯JyÜ=Ÿšú+ė¨ÃŪ‰YjÅÚI]ūÎ÷_e€pžeQZܛOe@mœ–:ܒ m,dQôĖ„Z:´ĻCK Œ(äûcđNÍÂįPŒ+ÆgŅĄƒ×<¨ĪÁ9.ƒbqÕĻÃø<ЅæjŠŲx -h+Ä#uXw.´æĀo-?ÍHƒ–b¨Ŗr&΂dŊ<˜EŪCdķg"ą´ÚY?ÂŽVeãËrĄĻũY|¯tgc}Č~´~.įųíĖā-Õ`!ÍHgX”ƒk¨ĩ~Bŧžc&Öæa ĐO°hąhåx+‡ü3“īA{„ėË  Ō`dakūKƒß`9ԟ ŊEø„ȇ™éi!öž–÷B›ƒkTd§Ō0äUäƒt(įÂOĻßw:üJlŅiëęģY¸?0ŠāĶđ¯iØsų¸Fv# ׊đ^ĄŪ8~/uG÷Uga&jņ( F\čgHf/ą^`'Y#?ȲÚÛ`[Vŗŋ#D‹Đ_Ėīôå~A^×`Ÿ ģ ũ+_I3ÄæN6‘KHas͇Íi+sąi6‡ŨæĐģĖ6k<ĢąXXšŧÂådu&§ÉąÄdŒË2•:LUlžŨd-Ēą›Ø}Ííb-ļrŗ5Øė54ƒEššąėõčWrĢĶ[ėl–Ūj°Cë4[…•Írhĸ ŗ“ĩë)ŗ9ØŠæR‹Ų ˇ°üŠ0Æ‹˛N›Ûa0ąČÜ*ŊÃÄē­F“ƒuU˜ØÜė"6Įl0YĻIŦĶdbM•Ĩ&ŖŅdd-¤•5šœ‡ŲŽāá5Œ&—ŪlqƧé-æR‡­Ąg+m ÖŅ[ Åa.cËô•fK [evU°NwŠËbb6X×l-Ŗ`¨ËT 3­Fp€Ãjr8ãŲl[fŌģÜ““u˜…ŲkœqŦŗR~5číPFS*Ũ—Ų*­îJ“F:M.ŦĀÉÚ6Ø d-hˇXlUl8—5WÚõkļ˛.äk° ĻF+Ŧe+cKÍåX1YČeĒvÁdķbS<ËÌq˛•zk kpÖģ‘ûŦād‡°8ĖNäQ“ž’uÛŅ2 ąZœæ[a¸Ë€– Hz6 ’Ŧ…Čc¨Đ;Ā0“#^g*w[ô?¯& KOD|7\„ļ`||âØ.Žw9ôFSĨŪąáĀ[ęgf9x܎š 6€o5›œņ9nCŦŪ9v‘ÍtØlŽ —Ëîœ8fŒŅfpÆW 3ãaÂWŨVîĐÛ+jÆčKgh(Œ´¸ zg™Í ‡QÅœnģŨbâ žxvŽÍ ĢaŨ@!"+jFŽ0ĀÖēLqŦŅ봁ɆÚfč5ĀüÖÃ6š•f— ԕÖ`TÁUĀ›C(”Ąâ.Į<0ē Ž8DĮ%07Í€ũŠĒ0*‚,Ģ‚EÍVƒÅ ÜXoŗSbÍ#IX  ŋg-‰"ā:ėģĶå0!…0]“°bͰ ÄJ%9F[•ÕbĶģzOO\Ė8°}¨āvŲ! M&Sa˛Øģzōp— GbÆqRa.5ģP~ +“Ël(ZÉŧĢãØRŊlĩYũ™B؄Xž &k|•yąŲn2šõņ6GųT#ō9e$l/ĻŽ¤Ļį$ØSōzŸ‘ƒFCn^dLČ5KHlØŨ]Ķ$re—DV€6Į‰ƒpƒ L0 ˆ ž1ÆąeHz(D Ë3ō1ø vĻŗļRHvVä=NÔĪŽ2HītÚ f=âĤ,ĢKOōŠŲž‰Eģ e ųL}l$ļȈŗ!Ų‡Įá<‹šƒčĮĶ Y/t[ĖĀS˛6Ōå 'Ŧ€ƒ!ŒCšÜ\†~›°CėnäŦĀ ĒKŨ(x¨‘g Ā&”ĸmv3ɨW4•<,I‚†÷46ĸĒÂVų;Q¸V0ƄmCą-‹L—@°üF3ŧ‰„âÆ–˜‚\ĢͅB†$s3Ƅ)|—ŗĨĻ.‘Ģę@Ë;]@&3l‘˙äų= xËŌ˛…ųEŗ4:-›]Ččōgf§kĶŲM!ÔcâØYŲEYųÅE,ŒĐiōŠæ°ųŦ&o;=;/=ŽÕÎ.Đi Ų|›[“­…ļėŧ´œâôėŧLv*Ėˡs="”åŗhA^Uļļ)ËÕęŌ˛ Ē™š“]4'ŽÍČ.ĘC:3@І-Đ芞͊s4:ļ XW_¨…åĶAm^v^†VŅæjķŠāČ̓6V;*la–&'/Ĩ)ëuØž´ü‚9ēėĖŦ"6+?'] Sĩ`™fjŽ–, Ōr4Ųšqlē&W“ŠÅŗōA‹㭛•ĨÅM°žū¤eįį!iųyE:¨ÆJ]‘ęŦėBmĢŅe"‡dčōA=r'ĖČĮJ`^ž–hAŽfģė AõâBmĀ–t­&tĸÉÁƒãÃB „>ø'|úXāûX@‰B üw~4@v/ôņ@čãĐĮĄēgķĐG]?"ŧú˜ ô1Ačc‚˙u@l’īPTgĩ’ęéņ#Ÿĸcáw;Õõ†ģü‹ĩj5 cDėՎ C㙨ĢßĢ/ޏÚņhŧD~ĩã{÷FãĨÔՎŒ„ņđ›BßPãņbøIÁ¯×€›Ŗ¨Ô Hd×SI8ĩMÆa:6ĄļÃM- ŧļī…ÚÃÔ­ÔNZDŊ@÷ĸĐÔ;ô@ę/ôę x˙<]@]ĸįŌ*úf:ŠļĐ×Ņ6:‘vĶ7ŅKčiôít1Ŋš.Ąīĸ+éĩô­t=Ŋ‚Ū ¯ĪĶĶčôAú%fũ3‹~Ÿ™MĖ, ŋ`Ōß2úGÆA_d–‰hæQŗQÍlÅ0gEIĖ9‘†ųN”Į|/šĮ´‰*˜ "7Ķ.ZÆü"Z%֊ęa7wÅ-zô*qÛ÷í€{ āŪ¸ˇîŊ€{?ā~p¸?Üm€û"]@+w?Ā=p'î›w.āž ¸ €Û ¸oÜu€ø~Āũ(āŪ¸_Ü÷€ûsĀ}p˙¸/2Ã8D*ĀŨpÜ €{āÎÜE€[¸­€û6Ā]¸×î‡÷€sOWܒŧ Üũ÷u€{,āÖî|Ā}3ā^ ¸Ŋ€ģp? ¸ŸܯîC€û8āūpŸÜŋŅCh5 {\@Įîņ€[ ¸ w)āžp×îzĀũ0āŪ¸_Z3ā~pˇîŗ€ģ“™& gf‰3ŗE#™"ŽYø ĸŠ€{:āž¸ €Û ¸—îģ÷C€{'ā~pŋ¸î÷W€û<āžØˇ<;÷Ā ¸“wā.ÜFĀíÜk÷C€û)hypÜî3€û:‚ŅépĀ=p߸'îlĀ=p—î*ĀŊ poÜOîį÷Ā}pŸ‚Ö¯÷Īô‘ˆ>(ę ¸Gî$Ā ¸3wāž¸Kwāž p߸7îį÷Ā}p ¸ŋÜŋ1팂ų…é+Ö2C!7îŠ[ĩ$÷ĩ€{āž¸§îy€{ ā^ ¸ˇî×w ā>¸ĪRˇŌ2@۟î\Ž Įēp—î[÷rĀŊpoÜĪîFĀ}pŸÜ?ĐwÆĩĸkčzQ4ŊYG?/š¸ŗ÷̀ģp/Üwîu€û!ĀŊpīÜû÷AĀ}pɜehæĶ›ųŽĘ|Ī$0mŒ†šĀĖÜ7n3āvîå]q‡_Â=pOÜŗwāŽÜ÷ĀŨ­Ÿî(#ÍPtĀ¸ķ÷|ĀmÜK÷=€ûaĀũ ā~pŋ¸ŋÜ?Ņn‘Œ^"BßW‹&îé€{>ā^¸ĢwāŪ¸wîÀûāūpŸÜߌ„q0Ŋ˜eL_ææf#sŗ‰ÉÜ€ģp¯Üî§÷ۀûCĀũā>¸/ĸsO.ƒ?ąąéKŊ^š„–Kíuxęėr)-—ˇÕÕÂS×&CO›Į<]*<,%Ũãy¸6=+¨Ģođ5Ô××Éå´\ŲØø8<>ˆ‡55mßž~ũÚĩxX[]]Ō€ĩ•Ô{R؈úš„’KÛYōā9ĩäŠÆ=uu¨G*ĄĨ˛6yu]]5Ō%[ę:И–JėČ,;n—Ŗ!0ˇ×ĩ{<Õr1%sŠmŠčARiu}}‰Į‰Ļ=‡Đ9MËÅʃlĸĮƒp5ԓū‘ĘiŠō…ˇWÃƒ× “ųåāAf€Ĩu+Ž‹är)CKÅ­D ˜-ĩ{|\DĢLLÉÄÄ:ĢAŖ7UH”TQįŠķCĘîJˆ;FĀ$Ę#Ĩi)ãAĮ‡ņ0`Ŋ¤ Ą䒂ô›Fŋ´bqĢRsSSÉėÔT\Eôx< sä2Ø .+‹Ķ­]ëĀ[áa€P)iĀÖÎ÷ČålĒŋb—ËųaWPPß!Qr&՗š*f`3ZŲÔVRHe}Ā%õ”D•xē2¨ C,ķx€e˙"3e~f*hšę€į€į1õ Čŗ—1Töb¨BB+dž`ŠJ Eq‡ÜĪQÔQR߆:Ĕ8ÚIe=°T!ĻĀRžĻ šVøiúoņÅÔ_7žâ0JŊJĸJĸÕ3Sö>v H-ž†‚€Dž†ĸ€„^qļ$ÁUĘ(ĨÜxîrū™:÷ú#!‚'”x)@%ĨUˆļÁą ãc÷‰{ōƒ?dС QÔ§ÜŌŽjģ‡ƒJBĢP8ņ ĸiUP<üAUãŧŅöī„ŠŠ„€¸rD¨d$"PY "ÂZ(pS $ø˜Pá˜P)h•*nëéTÅQ3¨ÕÔ xwē‚Ēö`ZT× GˇR"RĘY„đŊčņĘwøĢÕā>eEųƒDÆPj1?nojްŖM(ŖL"C÷1‹JIŠ”jxod(HĒį@Jõ¤Ēä´J1DOđĨꇠē˛}% īĘvL0|Äę„ p4UâIĨĀÅÔŊD§ÄMŠd Úë?AŧĒ0ZÕËå‹jˆmˆ­ĪĒĪB‰b…|…Ü+ĮZ|žz:O-ˆdškÁĮ 'l@u-,ÂɑkT2JOX•į˛GCMĄđP/Š.2_ĨptÔ2Z­ q€ĨĻ—ēÜ˙p¯ž‰¨7c"ĶCŊđ{J ÄRUčîę1īŌnĘŊ^rŽQæQKiĩ<(ĖjÕ4­Ž3\EËÃ_ö5ã„#ē"+•¸’’NNTU GŽ ļ |ŋäÃÍÃ_DP‚‚ü„ΈÔÔv$k% h%WŖ…§ĨNK‚ŧ*'^EneY5-RKũvD9-’K<äŌwj!îԗŝDŌڋĄÕ(î„Āƒ‹ÛpI<yjŠ<ĩ’VĢIäĨSą{@#{iÔd&ņā•‚+¨į9s ×ĢkÁ1b~:ğH$CQĸîĶgDzzm'ĘV @:Lę@Rî€jĨV…SáÔĩX< žßp– ãD­ ÕĘhĘî)Ą|AR-҆u‰j„wž į€§Ņs‰Â´ē„ę¸õR á‡§G{ėŠD÷A~z‰Īî‹ö¨å°pcsKˉļ'Zš›Õá´:ĸuPë ļÉGãNXNXå´´4­=¸ļQŨ¨ÆĒZ}mžŖž - Í oú}|XUGsccc31[2šĖįk­.•ļTĢå”ZŅx°>_ŪsĀîT+ôŊg,…dˆÚ/øō¸´Y*]ÖÜüΒ09ĻDķNiDĪ™Säū[†••MÆũ <“Ęqų$tiۚ›ÁIĨ“ä°g“KJJÚKøGúīh†gŠoĖXÖ}‰ÆÆ0&öų(Ęou˜ŒS B3¸ŗíDKK3?&čQ¨iE¯S­_qÍ]_ĸũĒqmr.—MVõ9…‹üPžMh–@Wđę&ävõÚjt|KČR°n~đC8ĨG[kˆ*ßdܔ´gr[Ü­Ôč]zĢj ŒÂQa"Q˜4€đCZTH+(ä ”#Ą&oȇ•ĩ´´HdT˜ŧŦŦŦĨN"…D`ąXNÔĶbZ"m‹@ÁY‚2(r¸—øõÃĖ0y zÂTtXØŠXVFMĻR| ! ƒor Ļ!ī;Á}jeJõ t¸ai3øM*UGq%éd)´6/Åá„띇E2¨fĒĶ#ĨĀ .ąīÁ–A°‡K[‘‰ž6Ą fˇũ” HĻj‘:ĸ§=˙gčŗ‘Ņb-įËņNRž‰Ę‡ž4ŽÕ8*­qlZÃĮfšl‹ņĢ^&(ŖŋYĮæč]Ön4vĀĪā-đģ1iđFÎ;ø~Šâ†•Y+/„Ņ2Qƒwp-4ĄL âRɨpF4PBqzŠr”ļΛ,ĸÅ …Ü ..¨eĐcC<ƒ`CäãĪĀmøoĨ ŋ31 74H™¸Ī6æö]ũiæÅč76Lzv‡aÆĖˇ7xŖŠ9¯¸‘ķ2ģ-EŽ›Ģ=ãi÷@ŗÜĖ…ų­…“‚âǰ™LąX)*.LˆäzŖŠá:n8A4¨GD…äËœ—ėaZB1^ēíJ‘—ĻŠŨĒá×>y¸.ļĪø/+n–ÖÆē5ĢŽyōáIĸ’­ģ3^P†=ũøą° í×Ī>:čGį‚N[Į ›F?đķĩÃë~žąī̇fÍü-÷Čcã^>­?RŪGÔ?Ŋ}u߈ŅĘ{ŠgŦōM3ž˛˙ŗĩŖži\9ö…Qž{~‰Ų,åė)ŸžŲäywZÉĻ[žøŦŅöbũÄĖĪ#Tģuķ—H ?ūԎĄIu}ēĒūôgŊ–Ūßåđģ;xKķã?ī)ˆÛ2ˇeîúāzo}ą¯ČtÖēŋ?5z•äž5 îN^ĢØ˛ŋŦÕZųakô“ŸŦôÖÛ˙Ō¯ĖGß0&?æ×š§ÛĪū6\üķbí>ˇûŒN}š3ãEo8ŖE ÄŅ6/­H¸ÁāŌÁáâ~â>ŧņsâžē„^gŦ??卄_į‰z)0‡Gqũ<}†'ĩ˙E—aWžKŊ¸äâžQ{ĮíëÅĄŅâ\n:—ŨŲ ]™ÆŋÂā°tûRŽ}ąĩŽáŋŪâãßF´‹x•ņ0„›-•C`J Ķân—%Ô9ŅĘÉüUUU=-`rüŽf‰ėŊNŦ攂JFŪ- ĒMķ¨SßoËēë˂ åëGøl÷îOũtÂqšĢ㞜3%Qš¨Ĩc~ņ&.˙ũNõc+>šîMņDų…ŧ/é}ŸXĶLy­7Ækí#Ũīį›ķûUī{įļ)ßx:wī3îDŨÉÆúYũ:ũbŊžßœŪ;Ēø-ēų|\Œėģã915û/L6 w[Â[§Ž vwŒ")5ųGŗ­q¯I{äÄČĸ?=™léķčĄjˋžZUŊ-Ų¸Ÿ^wöãÔ;öŽ(Z/™û×;öÅNŋæŅ$ī]cbK’#ΗüĀë<ųiâÅOĮnû"uÜĐגį%V؎œõ5­7ܡąîĖ7m{DĪūra~Į§Ë“–ũiÆĮ×FŸÕũ•ķJiHc JcM[Ũ~ëō‚ŋuâ4Öė5¤ąeH˛ˆåŽ'AÜo4ą…ærüåØXô­ÆœÍ’𔄄D$‰dŗ@•sũ!öņũĖú˙a6Ē[ķԈFŲŊ›=5};Ž/épÔÅũú÷më6dŧ¸íČÂÕc&Žr_õ¯KwF{éįo=2đ5æpÆˇo=xáĸxđ+”ÃŦ[(ŋņ­˜¨ĶąŅ?‰×k gŋxĨīÚs‘›Į}’b/˛M:ģ[Ģā˛ėŋ—{P}dÉÛœôĢzīŽW×”¯`Ī yrÜų[ŪluQĶ×ŧęžoW_ēû×Ũ%u7žūrô3ĨßxĢvoũ3ĮŸuŦčâ¸ŋūų–ug†tžŊeņ‘;äK\­3˛>8OĘĘŲ&wzNØoK>tfî+~:žšWô=O|YÛ˙ĀņÃ[ĶËÚšnėÆĄY‰íoŽxŒznáá;­#į-˙>ÅęųņÕŗ‘Ēo…lä,%éæ:”nü'sŽœöG*”ŽŽ/­}ˇdÂ7åoÎ˙ĐĢģ^lŒÜÄéPwo1äĸ활ļûI“Ä%ĸĒ$rTâXŽKHeHá’JĮ™ôŖ“&”&NJ›2:eėøÄŅÆ”q eúÄÄqIe†.)0Ëj<] 9æ}Ērō°į+Ÿ<ė=påØc†˛Ų8 ]€ĮĀb 0âīBô2šKÍĨā¨JÅÜV‚R ö. dÁßYÂÅŠ‘á‘4Ũ)qTˇpfŧ"š’ö‹>9ë͂CÃķ›QũŅšößūüú‡žķŋ\;ķ\á!sĻäÃĻ#g?īxpŪ {§Äú$ÚČÖÍ5u¯•í:ųęˇĸâá/Ū8ŧZSųLûyjîú× jQ;}+[ž÷ØU<"-ęųŦÕ+ūōū#7 øKŸÕ_ŨŊĨbD]Ť=y×%šcøíáûûæŽ}~fIūGĶ^Nš§3ūãŊ ˇ§-~ģúŨŊ¯.ž÷NË*ĮSß<~qËĮOč0ž]9E~zé{wŋļí•ÛŪŨ0sû­ŗ\“YúūĐī;&7%¨.Œ™b|<ŲVRpĶ‹éõų Ēģö/›ũķÁōUú“nj:´öˆ-ķ3_üús{ŪÃUž]”ũäß6,9ôēŧéŌ¤Ÿžq&KŸ›ųî€^ũiũáUƒ~đ,ĸķ_¸všsßąyÃnš8;ęĶēīʛ˛wŒ9uŨ]7.8z6)ũžÁ¯Ũ§^âō}͉Ņ[Åĸ{˛~ųūcŅģĖcpČāøžJ}ŋŠ$œûuŋÂ.ÄéTŠXwũęûˆ3Ōú1ĀÆ„\˙. ?Y†ŖHŪț:› ’'P×\f6č]&VãvUØfW Jî\2—čMH7–›É=1WĮr¨ú?w‡ūGų}ËVËŪOOf­ģaéâøŸŊūųo=8cxÁîw>ŽĘŅëģ÷vŧ—ŗÛÅąŊŋ•}Xô@ßėõ×N]÷ĖĻųÜõĨ}ÛëgWËz]oj[Ũ}dėˆUüđ÷ōAqˇ}U7ø›¯ōļm=0ŧđđŨŋjßU]đėŅ=Sŏũō„åūōbOeîYyôtlF|ĖĶ+ķ‹uę/™¸‹‹ęë9ëĒįpüēėøÆ}_Ũ¸ŦũũČå/Vęū¤­ß’EMË,ë3˛ėɍ_“.ŸöØ/ĩ;zgöQxˇÔž+ŽžDo\ _AEpį^üdxÆĢMŖ‹ļ<;¤Z“PÕōЧ“îŧĢ^ôüā°ŊzŽ~gØôĸÎ_$o˛*!ŋīėāzų3Ž„cāWP>īņv‰Ō÷ā^b1đo%!UđgB_ĩPÜōM$7/¯į–ßíéū´ˇ$ufĖÆĶ×EvÜđ™˛đ9_nßjØŽ˙Ãé鍨ŲŨo봆Įwį8g˙]oâ ČĄÍÁ9Ԑ֠YyĶÕߋũŨč[î(•ãĄ(č@Čâ2¸ô !埚#i˙¯˜ķ k*Yã8 MšĨH‡„’(" EŠô^K•Ū–€4éH,‚´¨ ŊˆQ–Bč ¨(ĸ7ā.pÕ{īŪ÷ŲķmÎ<3gÎ9ī;ŋųĪ<īûĩ×ŋ¸&}k\lģ%š˛äņŪŨ€Ņg=-@Ü×۝RúŦéJb-|ņVœģm­ đ‰67D7s,č´I}…éMŽ)N@dY}āÆžeĀętS"ˆ˛+^mzŨ€yL§4ev>Ūåehë›Ô *ņëäÄda~^¯wŸf3á´īЧŊXĩs\A>iĩxéėËbzt‹ļ–ō,7¸å§ŠŲPz‘—ü‘˛">āŽE/Ų/×A‰6MÂúĢړKÚ7B:$DŦ š—‚ÁŠW |xV=õ–€“ &ēόˇį:šÄÄį?\ėÕ3^ČņJu+“Ö|‡ižÃd [ģ•;CĀfÛ-{ʝ ģî­ŽD˜û°|æö¯žĩÚŪ|Œ‚ūāķúqŪæ*JL BĨÖåŽ<Å/ĄžĐ\f„ã‚"Ŗ[W./OŸQ„XŋĨÖ+:8Œ ÕVãˇ6_4^+ĪĖé9įŲ&äKub՟§9 Û*dø ĘE6īosĪ)jžŖēÎčš‹rĢū<Ą×Į×íØ˜ÃÅh”Ģ0KŦå™ģ_Ųcw/АrPŽ[–ZYXJČO÷c{ņãGũzĖ#ß"N 9-ĸ‡ghé”N÷ÍUõÉ÷Īpp—s׏ÅbÜ3$ė ]‡…å°;~øŖxŽ<܈ÅĩR°‹ĀR!°”ļĸ€.i`äßʀ°č˙ËTŒB ž:$ė¯8äĄ"@’°!…BHH…†ä~‰Ø+ūíŠ üžĀ=vIė ų\éúGøŨa;X­3uLyōŲ…]‰æēwjФØ(ÔëBÚiNu}Ė8 ^—jˤĒė’~ `B*žˆĄÅØG]Kũ™ß­"W=›čd50‘eP m¯x]"RtŧâUēYĪĪl”DG˙”ž Ŗø|é1Ũįåôđ#8š_ŠĶæ÷Ís–x–-•ēI)û2{‰Āĸ|;ząūą=3NMûŌS¨›§mʇ4ĨĘŽíˈ˜3piCoųL2žĢQˇ^YQJ}ĨúJ$ûkšĒ8ôBŒNÛ^Ül6IFŦü´iGÜgÔ šlUuEŠÔĩœPŅˇÚÆÉ<íŌö!uŲôwâ‹x˛UG˙ŪzŊOŋ9.5ĒĄ…ĮWšú W*%!}IōųÕĒ”ržâĮe.—)¨zŽuô´ú†œūŖû&ōüäëũAâ/ųfŧĐôz*„m˛Š†2 Öz´…™ĐČ>h¤1/§'ōŠ7°Ö*_Ŋ8ÛÚî4é3Ī?ŅŦ’ŲąÖÆa2ŋŦĨŽ(.M˜XļČĢø4Vé8ŨŠ ģ˛2´ĸ1¯+†@‹Šƒ/‡ž‰ĩ ´Žxe’mŲ…ūžâŪMMŧpV§uęērĖŖãšƒ…Jâžiī=ļšME!čŸĶnĘ霎Ќ>9žĢŊ•^Ų ’ī–109wĀÎ;‰?Āß!<¨K~:h¤ 9"3ØîQ"SøWŽ~媊ĮG뙤ô‰R{ją¸ŲĪsaūn{[¨:ųZų‘ę˙ĶĻÉoI^KrÖQb8mBícÎęæôēí#˜Sük˜ûũû"ÂōöĪM†C„Ĩ"’>œސ˙ķq@Ëé˙&ŗö2ΐŪĖŲŨÆcįõ ÜÉ×qá  âĖ)7'™&Ų^˛ëŊ8*ëũ8ǝqwRé—?"â"áܜ?b—7" 3& 1lđޗyŗĀé'ĻėR2̓04I­ÖpQšívŸ~÷đĪMō  ™fՒ‚MįQģf^‰BÚ!")ø†ŠŽŅ0MĘÕ6 ŽÍķŠ7ôû*w]gä¨á°Ŧ7˛ė…ƒ÷9RĨ§‰öŨʲA|›āĸ$ßđø­'‚@áļX†úÛ%”4Y+NāiųÂōÂŽĻęv\Į=Ė3ŌgÃˇZ7UDÆ?Éô5JŦy”ĪU­ômŌUdBqZt˛āc1C\í(Öéõągš÷ÔĨAAmī–ĪUŋeŽÖģh*…ōb ŠÚÚ=ĮíœQmãäáY\ãÛ~’Ē •ÃĘC´Á-­ˇS‰!žĖÁ‹ũį.;´Ŗõm#Û9í$q‘#›Û,ø›BSO q}Ģh;… ęė(9ĒĒ~Ē*?.Ļ&›ûëŋ=f§hšPč¤ƒŽŽ;ˆ/ãŪá-͇Ɇđ*f›¸Âãj ™Ą\}d°ŽĒŦBų‹§$Üē•ÄûQ-ĢtG•/ômîvŗknzÉ/myņl&†UãˁĪÉīMÅĮO7–ĀĄ‹Î2Ÿ+š ~îvɲũ9ÆÚ:ÍĄ&ŧøĀ(ž 5P•üίŊˇŅ­øč,ocmĩ‹-ŠŨYū P5×]L^kŖģģKˇū/Ú Ũ§H,E%KQai7¸~ŧxx8’öhoōųȏ“#iŽžŧFqX#éGk™|‡ )¤Šm7Uš8aã÷Ą0Æ XŖ{Ră%ļq„ũ‘&4Hc„ažp(ô‡é ŋĪ  å˙ˇžmx9Šû6S`dĒ Eár=ͅ¨F‘Vúâõ=jy$gPy€ĒĄeķŲ3ôg^8ōQč'3/dÜdqöą-'ĖÂa t* į¨UˇĮ)öŖmąNkČČWã÷zî&¯Äé…x–(vjvWv;ĸČFæësė d:Ũ:­wˆ;uĖ}8)ˇĒ5Õ¨}œ_LdžN›ž2^čŒ>ÆØV䖙=ˇĶsØ>žŧLí¯BOqÃĻŪ$Ĩ öV…;ģ%jôą2Fĩ.m E¨1;†&IĶJ¸ßą/žkĮņŠš—ŧC„õÍsšV1õTežÁÕ`j›%#Ėĩc˜k8/ˆužƒ”’ËąŗēË1gpÕf\Á_ĩfw|dnûyÎÎėyŨL5ŽÖųŅ€wÚīoũ•N>Âô~vģœ‡¤4˜ĶŦ­Če„ÆoFSÆã*ÁaK9I×ė^´B?ņöËŅÂÂČ(M0ÄtXQtĐŖūL&bI‘ļ%œZoęÜfŠ™fuŦ?`­ĸ ļáW‹”ū-^™ėŸx“ô endstream endobj 80 0 obj [ 0[ 507] 3[ 226 579] 18[ 533] 24[ 615] 28[ 488] 38[ 459 631] 60[ 520] 68[ 855 646] 87[ 517] 94[ 459] 100[ 487] 104[ 642] 122[ 487] 258[ 479] 271[ 525 423] 282[ 525] 286[ 498] 296[ 305] 336[ 471] 346[ 525] 349[ 230] 364[ 455] 367[ 230] 373[ 799 525] 381[ 527] 393[ 525] 395[ 525 349] 400[ 391] 410[ 335] 437[ 525] 449[ 715] 455[ 453] 855[ 268 252] 862[ 418 418] 867[ 418] 882[ 306] 894[ 303 303] 920[ 682] 1004[ 507 507 507 507 507] 1093[ 498] ] endobj 81 0 obj [ 226 0 0 0 0 0 0 0 303 303 0 0 0 306 252 0 507 507 507 507 507 0 0 0 0 0 268 0 0 0 0 0 0 579 0 533 615 488 0 631 0 0 0 520 0 855 0 0 517 0 0 459 487 642 0 0 0 487 0 0 0 0 0 0 0 479 0 423 525 498 305 471 525 230 0 455 230 799 525 527 525 525 349 391 335 525 0 715 0 453] endobj 82 0 obj [ 278] endobj 83 0 obj [ 306 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 416 0 478 0 0 0 0 0 0 0 791 514 513 514 0 343 389] endobj 84 0 obj <> stream xœė||[Eļ÷Ü{՛%Ģ[–teYŠmšw;.Š[â—ÄJ¤TšÆ Jī!̓é„Ū,,°YveX‡žlŪ.}—…]:öąÍ´ąŋ3÷Č-„öŊßoË÷ųXįūgΜsîĖ™™3#§†ĸ…‡ˆ„ëkę:ŋhŊ-‰ˇnAY}MKíŸŗ×î%äõÂ~Đļ8'ߊ¯zaöƒU¸g}×ĻĢŪēņ!eaB¸ÔžÛøûO{æ B–ŊJˆØĐŋiÍúGvŊ@HÅŲ„¨S×Dv÷¯}ž{+!Ģ? $íēžŽŪO‡÷<ūTā¯xęģô‡Ą^õԁõÛvųŠæ¨˙‰’‘=]ũ ›–˛˙BB<ëģvm˛_¤û3´€>ŋžo[×­—;…?ūęģ6t­ī{Qö¨—ģ?&$÷ęMˇnגsa<RũM[ú6ũÆ(ī$D—Gh,$åo}ü;åÂÕ Ÿ̌Pzā¯{ŸĸøR㎤c…ĮŸTdń%r`$°“1ÂUÜ|Ŧđ˜Ú s™FrÕI<Ķ/g‡ķn–f;üũ=ų{ šķ›ôD-äföāA÷%˛ƒĘ˜ žKÎúįôt–fi–fi–žØëÉ_Û&×˙3ûrŌ>l% €û˙Õũ˜N\ųĮŋēŗ4Kŗ4Kŗô˙&‰Ö“˙Õ}˜ĨYšĨYšĨYšĨYšĨYšĨYšĨYúOĨŲīÕŗ4Kŗ4Kŗ4Kŗ4Kŗ4Kŗ4Kŗ4Kŗ4Kŗ4K˙+â✌˙^üjPb¯""r1ԓˆ$ (ŠI É" IŲDļ’ģíeŧ–?}|\°R—ĐÖC֓-ĶژņOĀ}ũxüo`˛ŋüëĶowŋÕ—Ixˆ…8Ļu¨‰ģšŅ2IŒƒŲÎė`ö1į32— ķ7Ąųøíҝˆ˙’éÛū=3õ†“†bˡë[¨îģĢâOÅĮ %:â˙éßjåųCįžŗmë–Í›6nX9eŨځ5ũ}ŨĢW­\ą|Y(č\ŧ¨ŖŊ­uaKsSã‚ų õuĩ5ķüÕU•sËËJKŠ‹r˛ŗ2ĶŧžTwŠĶbĐiÔJ…\&•ˆEːĖzwC˜zÃQ‘×Ŋ`A­ģģ@Đ5MŽō j˜ŠåÂ?SĶšũ'húQĶ?ŠÉhų R‘•É×ģųčĶun~„YÖ„ōEuîĘ …˛Č+TÔPqšĀ‚¯ˇ ÔņQ&Ė×Gv ՇëĀß°RQëŽíSde’a…ŠJ(EĶܛ†™´*F(°iõåÃ,‘ŠékŖœ§žĢ7ÚŪŦ¯ŗš\!AFj_QImT*øâ×Ō>“ øáĖG‡.Ņ’î°OÕëîíZŒr]`4ÄÕ Õųĸéîēhúžw,0äžhĻģŽ>ęsƒŗæE“/`ĸbÖÍ}B ķîŅŋ͔tÅ%öB‹tˆ“a‚ö‰2žAa|.íË#~Ō •č`Gë<éļň?ĮОaÚōčD‹1@['Z&ÍÃnĒúpüŗcĀėæŗ2!úÂĮh᪜7ÜŨ3@ąĢoČ]W‡që FũuPđwÅĮZ?œ›ú]aÄZ†Ž`4ĮŊ)jp× x:k“¸YÔP%ីU4§žŽö‹¯ ×aŠ/wGđ)s¸ˇ. …$Dû5Õ¤x뇂ŊũQgØÖ ëŗŸÚ\QÂrûBt–ÜÚhú›đ:—đFÁ Æv‚ö„2šÔ#ãƒŦ ŅŲßwM4haē„*Ņš >ČØČ„ŧ%ŽAK3ü@…ķÔ. M5­]`s…\HßĐ%[ŧObOT6͗“}Â÷|m×P›v(¯īĢ›ÖÁNÅņÆŊŧŸ,EüÅ`!ŖĶš`ĸ‰ķĀÎ nE %í|ĐŨįša ųۃtl4ÖÂü6/v7w, ŗ_%3jØ^Šĩ(qAķD…­…5ØāŗMLĢPŸ/Ô'Ģ NhnœhvĶ~ õÎC—˛m˜ âÚ BŅ6_Číöš]´ŸY™Ã2ĸru†ka¯6@ēs7tš!Û7 uŒv ûũC›ęÃå°/†ÜŊCîÅÁ ›ĐųEÁ}ļ=ôŨ‰¤™iîŦW,Šv3û;†ũĖūÅ˂G´„đû;ƒ1–akÃ5ĄáTh á ņ R–JАVxZĄžAE&čێø ZE‚@¨÷Œ0DÉ&d éaQĻÅy…ųáōŌ3"Â˙„ļd2” ĸvZ\[-ZÚr?aéu6" `ŋBė—ųå~Ģf!¤TÉũ +gČaŖflÃās‘ a‡å~ÛÁĶĸ¸æ hRŲ⤠zNÕĻ9‚÷áĀS#, Vđ/ļx#ĄÃâäûá†ęîĸ‡@;ާ}™pIéĸ‹›Ā%d‹JáHęīęsģāėˆŌ܃ҧ}Å7 ą š‡ĸŽmepī… ×H>›|îŽ>zyî§wį>Áļē+D‡zŗÕģa÷Xˆ%’^7}ô ŅĢųʰ"ĄJ∠ųŽ„sCäíY†CŠžEŧ0Õ]6¨Ai-ŽPQ¸øioÖû†WJ=SáŗŅ‡Ę2Á+ôlQ0Ú>Ą"ė$ZØė‹˛æRh¤ƒg- Nd(Ž67Bxũ°ĒlԚ˛ÁøôöÔÔ61aháôˆīŦÉsfâZaƒ˜~­|X΍°{bŽ*įģaWĖĄ؉°#æ(؎° UļÆsļÄ›6!lŒ9*6 ŦGƒÂ)1û<€ukcö€˜Ŋ` B?BB/BtŖABÛV#ŦŠ%×ŦDX°aB!ˆ°a BĄaBB;BBk,š`!ÖZšš ĖGh@¨G¨‹Ųjcļ&€„yū˜­ Ą*fk¨D¨@˜‹PްĄ }–"” ŗb„"„BôY€vyš9ŲYč,Í}h—méisPĶ‹āAƒT7ÚĨ Ļ Gp"8ėą¤V€d[,Š ÁŠ`Á63‚ …F‚Ût(Ôb-AƒB5‚ A‰ @ĮŦí˛˜ĩ@Š A#ˆP…Ã‹Ā ˜q„1„ã‚ķ%Öž@8†đ9Âg˙@ø4fY đ ÂĮ1K'Āß>BøáTyá=Ž"ü á¯A•?#ü á]lû„?"ŧƒđ6Ēü7Â[(|á „×^‹™—ŧŠđJĖŧāe„? đ÷/ĄđE„ß!ŧ€đ[Tų ֞ĮÚsĪĸđ„§žBxáרų+„˙Bá#üáhĖy‰ųEĖT đÂĪcĻå"<‚đ0ÂC"<€p?ÚAAáĪîC¸á„Ã1„a´‹b_~ŠĩŸ ܍*?F8„đ#„ģîDģ;Đā‡(ŧá6„ ܊p ÂÍnŠģnD¸!fė¸>fė¸.fė¸6fė¸áj„ĢŽD¸áÂå1cĀečķRôy úŧá"t}!\€0„šįŖĘū˜1p:;ƒp6jž…^ÎDķ3NG8 aÂ^„SöȐ“™Ũø†]čz'Â|ÃvėË6„­øž-hžaÂF„ ë"§āPÖáûÖ" ČÅkúc†3úbēv{c†Ķzbj׍ޘÁFájފNX3œ°"f8`yL‡0ŗ,Ļw„‚1Ŋ`)’˜Žy&ĶÃųÎt",FXĶÃ1ĪtÄôp°3ímąDÚëÖXbĀB„6#4Ą°aÂüX"œ›LĒÔŖ°Ą6Ļ›PĶŅM9/Ļ øcē@uLˇ   Ą2ĻŖĢĩa.B9BYLį(é2Jbē2€b„ĸ˜Žž¨_T€ĶŅæ!äÆt49ŲØ—,„Lė’씁Ž]JC˜ƒđ"xRÜh‚š.ėpâûvÔLF°Ąy‚Á‚šfvЈ`Ā~ęņE‰:´Ķ"$ hÔ¨ĸš2Ļ]  ˆiWČcÚÕ2)‚AŒš"ÔäPČ"0Ä?8zc€ĮŋūøČ>ÃĪ üāO?ū8ĄÛųwāzœ&ô:?~ø=āQ˙ ø¯Đö¨˙øOĀī˙Č˙ü”ßüoāˇ@īM¨ŋü:đkŋü˛fķšįī_~øw {đˇĀŋ~ęĪ> ü đĶĀO? ükā_˙—úįęˆķqu†ķ—€GՙÎ_€ė1(˙\ŊŪéTŊÎųˆz­ķaõ€ķ!hyPį|ø~ā#ĒÍÎÕįĪT[÷Šļ9īžø0Ôc€Ã ū)đO€īū1đ!āßĨ<Íy§rķånįoWîuŪĻÜįüČožøfāƒĀ7ß|đõĀ×)ŗœ×_Ŗ¸ÃyĩâvįU€W_|ørŀķ2řÎK×;/QÜčŧXqĐyČ/>‡ķ8ĪæJg1ĨÎ3ƒ3 Nė œvh_@šQîŗíkŪwęžCû^ŲįO”(ööN=´'°;°3°ëĐÎĀũėų¤ŸŨī¯ė8´= ÚnØžm;÷ņvæĐvĻn;“ģaÉvív~;§ÚØØzhK€liß2¸%ēE47ēåÍ-,ŲÂ(FÆ=ŧÅæhôīŨĸÖ6ll l:´1°Ą}`tpméšĀĀĄ5ūŌŪ@ߥŪ@Oiw Ģ4X]ē2°ęĐĘŠŌeå‡–BĨÁĀRĐ_RÚę ,.í,:Ôh+m ´‚|ais åPs ŠtA ņЂĀüŌ†@= ž$k“ųdNK;К =!6Ļ&׿ˇŊiûĀ&"ļ¨íQ—˜äLbĶŦLm›•Ųh=Ũz‰•K°ũËĸ’ũQXļ<8Ė0‡„ß6F ô×ÅBũœ‹.615Ä^Ķĩ/Ƹ›oļׄšŖƒ´ė÷ åqZ& "BeØDjBžU[ˇoõũU2ĸ{S÷Ž3>ĸ}VË&$0 ã Ŧ?†“ qjXú×p~M^IC‚ÚŠféc\͙üjĐĪQĩw6$(J6P­lS˛~eumƒ_™•Û0säÂ}ÛVÁcÕÖm>áĩU!f;­û¨˜~ļnƒ:ũŲ.ԉī ÕVoÚ6!ÜöÍV˙ąÄüĢ;đoN–ÕĢ„?o–ŪDČØōŨN֑­d~Î%‘äō é&gAéZr3ų!š‹DÉĪɯČK˙Ë?aŸAcģÅ뉊û‘=!ãĮÆGĮ~<"ÖL“€š^ÄOIÆĩãī {oėvlD’H‚­šũ H˙Î?ÆVĶúx1­ŗįA9A°øPzĶØOĮî˜Ņ&ŌB:I€,!KIˆ´‘VāvŌA’•d5é"=¤—ô‘~˛† ĩ¯SH„Ŧ'€ûÉF˛‰l&[ †ÛČv˛ĘÛâŦī"ģɲ/ާ’ŊPŪ Ī=Bé4r:DūŒI&ĪBôû!ę4曄įZxŽ™ŒøNˆíDdOƒˆÍŒÃŽxÆķL!Nm;Aķ<˜3§Ų ķ4á‹jOøš/:&:ĸ)Žđ¤djÜ3­PozĖfFđ:A2ŗõÄČN/ßōĩ-? ˇßO:'Ö&Jw§ü#rˆüJøœĒO”î&?!?…\0L“{É}ägdd˛~ÔĻÚc‚dBįäōûÉÂ*x„<*Ė˙/ČQAö”ŽÄ[‰ˇÜ/”#O@z’z• ũ5™.ƒ:;[ŸEFŸĨPhųK˛˜Ü,&!‹QrYYå9*¸tvdgŠõDaâŗTFCfŽĢ\cķvØڀ8æŗT%šËtÕLNī(“Ÿ_fÍYŊjåʕ:_™%G3Ŗc tđ"ûwö˜—ú6õ‰!ųĻŦ`–=N˜XŊ›ĄįÃšš L*?ŗž€‰Ĩâ߲ ƒËjáÅė[ėņ6YFZz ķ”QŦŗŖĸD‹MĶËûėZŅbļ&ÁáÉJŪ’`҉EVŠJ*ÃC´æ‹+5ęĖß5“˛; Ŋ\”–üeˆģÃ6ĮĻ‘ëíFúw׎Ā=,ʅŨp-›ÃãŨžēĒŌ 7"1$،šdwrÁâģ÷W‡įĨØødĩZáö¸ôÉI‰y]—­nŊíÆĄĩr¨čßm!Dd€ģ‰‰xČjˇ# ­ô'šíÚŦQËėézÉÖĖ ŦBÍĶų/ŒŌØč¨ĻÎūQD×+s|™Ą ?T{bô.aŅ V MnČ U"‘!Оëĸ‡.;Ļ4hÄâ#Ō´äĪē}Ci”oˆ„/ŧéÂū3zęŗ ė­M7\unß\‰ÆĒãnsŲ ē/^WÖÕāũōŊėļĩÛöĐu°n;Œ(“ėŖãņķrƒQf4Ęŧ•Ee!ųŪtÁĀË>Lī5~Če`9e‰eŖLÎQ:sÚ§ķ ´ûÎ;z”ŌlAÛtŲ‡‘ô^Ūøa„˙ĘPŠŊor´œ+ß8ŗ01~Wž‰ÛΊĨ |,\ÅŦ1BVSjdc×Ö3whY-;ū•^-‘Îą˛ZĨ\ÄI“A9ö”Cf°˜•cˇÛ$FĢE¯’&X´4ZÃø1ņ‹0ūrrŊ0ū"ĸ(WĒōÔšyšŲ–ŗÕ“”ęIĩ˜•Šâ^ŗ:)UArr­•2;ĪSŽuĻõ:ÄĶ…y˛‘@ēü™* )čûųš™Q°4=ĨÄWĮdJ1IÅ*Fnä-§N ŗĮ_唇ÉhK”1÷°ÃŦÜā˛XU“ø’Ö&ÍIN”\#b×ōž,į€Õ)3Ã!ÁƒÛųåŲbšv—LÂíũōüIų)ŧ’î<^Ä>g÷Z| ÁHŠšáä­ÄŗÕī&•Yƒ3SęU;¤Ĩ}ƒ”deĒŪ‚JĨ5Ĩ×: Z3cŦņ°Í×<^4§ ŸÆĘôŨ iŒ × É6››ãÖpŌ™ņ1™õÎ\PÅ )˜n&nŋ<=Š/ÉĄŧKe5[ŦD“ ‘ncdz‡Éi–ũDgŸÅ%ŋFeĩš•Ŧ4!A+ëcä‰Nŗ)‚W“ÂÛSYĨ§}q‡×ÛÚļ0åøÃĶããäĮRŊŪ9‹e0cl“Đ,OÔ!Ę'f’CB4V˙ąž…¤ÂSEėŒ,ĻīO§ŋŨ”žm!ŋ )dXo‡æ{"ú~)Õ;‘žI§eĪxšÄė1•IEĸޤ˛•ûīÛŅueoAR9”ļõ\ՓORųōy‹N]^™Ēĩ•¯¨iÛŊŧŌŖcĒ=xđĘ­ 9+Ī_ļāļ[lŽÍ ”Ŧ˜—Z>ußN_éōy)åĢwļ“Žnūø1î ė('É"ģ'rdĩ߯ëĶdō9đcЧŊg0ëå2™fÎûæ^͌%/¤Ė)° „lÂX´˜7 ˛´÷"qKķœ÷#'ÚúâwŸ¸œ´Đ;ĮëÅ,Ęr ™ķûĪ_zU#Ģ4šŦf^/f.Éā?MŠągˇ”Øcųšúlũeíû[Ēlœ+3ŧbQeęØetž98]é÷76•fģĖ bĘRĻ,o­+ĶT5´$ˇˆ[ĻžzĶpT¸A…D +ȇ €sīĪŽīëK˜ƒ°jLû<ō fĶÖ?ZûŽÎ¸}ÅCÎ}ƒ(ž)¤ˆfŖA"¯aāPŽåėFą4¯2˙”p“ŽEĄVÂl”Šķ+ ÖĄŪŌpŅŧ—īėŖ-AĤ$Ô¯X_Vą¤$‰34Ŧˆ”Ö.+ĩȤV™R&ÁƒÍ´&ų[*O9°dlÔĐQn­lœ)äΑĒd'SIœŌâĪp•ĩå¸[Ē3ŧ5ÁBw]j|–š(Ėr9šû$ŗėÍKJ˛yEŽ$0.Aã1~î/nōØ4ĸ¤„<¯Œ÷5ō-ōø4ā,Đīˆt:ņvķhúv+ĖūÅÆĪ#'Qœ>]Âl Gō=&‡‹Ę¤›YøŽÉH'Ļņ/Ŧ,Ú¸ēųkã_ˇjs%:û"Äéø3SAv–›*›ŋ1ČéĩAČQ­Ŗž‡¸Ōģ˙Ū¯F–Ū•~9ąh-Ŧžŗ¤ŌĶAОĸoJÛ/Ę.ô‹DS¸ (žÚ,„M­ˇŅ7IĶߎHOČ4ßáë÷|rYpįÕ+Ãį3låK…R(ã'ÆŧļԊî…ežDS^kie-ą[›Žģä´U%ŲÁÁŽĻë.>}UINppY~{‰ÃרŊq{i~{ŠÃ×ÔŊiÜŨ>ģ‚{Ɵ'ã3'YYjWQąJ]¤.˛¨ÍB‡Ÿė3̊‹\"iîįŪ&ŗÚ‹m‰meŸM°0ũôœŖ9ZøŽ­H#,´¤īîé‡ú^8§[HË>;1’¸îā1-ĸsĻ_™§-ļx„Ąn2 ~ÄŦĻĒBS€~-ˇšáHdŽŗÂé ËMS §gËōŨ Žĸ9śÂÍ:WN¤Žhimž+=ģü˛+.ˆĖKä}Icí2]tđø‹žûĒd‡\Íķ2 —îlķ-(LވXz{C}~įē-›poŗÁNv7IĶéė‰ÉĞŦa,~­?Ģ)1Y—fŸ#1§4šq{Nî霪“ŋÖ8BTßĸNc;œ…^#'¨MÛËS[yęúüĩŲÕ#qĢe2ŊŲaH -™¯k›Úģ‡áĸßģ.suSĮÛa–H¸DfoK„os.^<ļņĢ[öļô–˛¸mK$4ĘĮGŲŋBÄNvæ=Iđ8™K á^á3ͅŸ˙ÃŪ—Ā7UĨ}ß{ŗoŨŌĻé~›îmÚŪî--Đ-m‚ŨčÂĒi’ļ4 IJ[uœR*Eŕm@ÄmDDteuÄqw\Į×QQYŪįœ{Ķ&Ĩ 3ķú~3߯}lrÖ˙ŗžįœ“ ‘˜_Ĩ;–J Aš€'+ŸF˙˜J¤ĨSr^zöÛåQãŸvĮš$ū;X8œŖ‚@wĖ&(—ŗ•OK§´ĨGeŋm‹į=zwĄ|î.ü‘KarōŽ/ČŸ eyę¯ZL$EÄ-øsš(›ˆJ)Š&’Ŗ‹˛'7•G ‰čd>/žšŗEyúfõĸÜ-rĪíŖāTīĨ![Å{Ũ!ņą7v †:}ŗMŊHžģÅ6Į{‹`ķ#~Â]"¸}G5r‡āá;åŊCPwQąšøp^ˆ$ŖLŸR\“—Ŗ›SRØQJŠƒŖ”Ą‘ōČō4meFXJ:]Uš)ĄŪ—)˛yJmRDJYm|â,d”X2…/đx1˙Ŧ)I;ŠÍ˜–›ŸZ93ŨîĨPĸˆLXŽb›ÉäÁĨRC ŗ$ģÉ~BCd‘Ŋå2Žî9ëĶ ×Ļ ŅwŅŊP“ļHū‡˙€‚<‘‘ˆ”dŖiP€ãĪZ°ņÂ'-gŊMX¸Ö†bä°Åø‡ē ÛĀx_¸PdĖžūŠ*’šŦKĢĘՈä”H*‘¤WjĘæNĄƒ5E•“Z ÔÌ6Š,#NRV֜Æ_žV›+‰ (Ė—Č„aępą4@&TįÖåĨΙŨ4%1"ŊP§N+Ž ŒÄŸön¤ŽđÛÁn3ŲįGåLyffIxęÔĸØ0m—ų`—`"ž˜Ë~VH›ˆ"’ŧĢ\! ‰…m \s—hĖŽĀ~ĖÂ~æŖÃ6Ų‰bÆlžŸķ%ŗû Š[Ü‚åņƒ’J:Ęן2cųŧkúo(Ŧ(ŦcT…å…u9*ū4íܙõÅģĨbÅ"Ŋ}aaiūtcfQiA“åĻÅä)žäO'&k°gņyųrEž"=ƒ1p:ÎĪË â…ĸŦ-ɋ¸ÃĒ3¤§čž1jũÂ9Î “œ۝¨čžąz{ę]čԛėwčÄ1ĮŖ˜”xE) Ž PđÉÕTœ&(L. •(á'įVe„Ū˜¤Š„АGMN‹ËMŽVŠų-Iŗfĩ”'˂Õ§`i€ķai|!“Ŗ ˜ĒMRĮōŗ “Ęgå•'ĻEf•LÂ­…8Į/ĀVŒK Œ &"ĨŠá‘úw¤¤twÚĸāČĀÄ(AXŦ3Ė/ÆąÍā0g˛<īâI“îļ]tÆčéuŧãĩ N )žP,IcÃËōŌ$žĢ &ĩG`Zvn„*¨įø!ᑡōÔōV­_f@ęĪČ¤•b1ô] {Zč;•ÍÍO ŌMä™dy`h>Ag*"JĻŦK ­LĨHו,ŠŊ3uoŌņ$*)}}„¯ÃG?!Ī÷„…ĐSÖŲ%Ōuļ’EIąwڒ"Ō×ÛüP.tžú/Ŋ§ÉŧÜÂ1‡Ë.žX&’¨éŒčÜbMaY&ąÚ45~RVœHĖG= ųɓĘ5šeyšø)——Ff§ÆˆEü÷DęĨ\!O͍bÔhˆvZaœ$ H$EF*ƒBŗ’ÃcĄ'7%ĩ&?F(Uˆ…°övŸûžēZđ" îélV “ŠdQ ĒT~FŦ,Œōƒ“͏ÆÖÕøŪ_Îŧ;>Zhې öŸV‹&™éę›UŊ¤ņŋ´ ]qÂT‘|ęž\ĨQōyīĀA\ MˆLPŠŌ iPz|LĸRDöxī‰ŧ?)Bd’€Œ0÷ŅXkP‡īãĨ3/āR‚CdĢō\PyĖ8PųŌu]LI”QaĄpŖVR•&2RŖģ2…$ =.‚ Åb(ĒčP)Y$’ĄģœLĉä@*Ÿž*Ŗé8q@€$8L!×$ÄBQ ē’„ž|”*ĨˆüÍ0BōžxÔ7čĶßįA‘â÷mžíŖß]āNúņš*Ē„ŠeÁŌŗW(ä”@,”‘›dT´04<qō­“AgŪBvß%ˆ(;$ō­ũc-ŽŠR¯AfV‡„„Cf֓˛pM8“Ā?›JJ‚"ĄQîĶČ[æ(ō*ÍÛB€ĻORĮų›īQB \¨ÁŪ\H#ŽaŸŌáĒq|ŧ*M#"„AǤĮŠáņĸ@ž2J)]2ĪuūQN.É}>77¤$8//čų\îŗˆ¤Kš;ÉHÖ/. Ūĩ`_ŖČuŽhÃĘØÉqCíJŨZ°Z<â%#Ž´ēũôōfHbÄüM˙Ŧq2Ô¤ķôdũãõ˙ .rŧ‘_(š9üįdúÍšđ*đ`yĖ8J‘l Gü3ējĪĪĄ\Ė;GFŖ¨fŖÁã-8Žūorąt"˙×dã:ÄD¤â˜ÆõĶD-Ž –Ėƒ˙*ˇVA6ÛÍė>/z˛¸˜Ë†r?ŽĄNEČ7ũĐjŲY{QYL–IЁĨes ‹5^Œēqœ;ąîŦŧķWgclęĮ–f-ãņļw´7g˜¸ŧŽ2€ۍsrQá›ÃØŽv.w°(Žnäōĩg+֐•ŽËáõōXy¸lü¸ÎkéŅA{I™€Ũ1ĖØĻngb×'ËW;ÂgŦl†íÅv2áõ4žÍz9M­xĨŲđšbWūųļGsØ]'ƧųEđøčŦ ˙Ēm}×ģķĶÜŪíÁž3ųíĄc5Ũ1ĮĘUęHVö$áÍ•Ž‘S‰īËvœGŒԔ=Ŗ_TąųĀÁŊ˛Ząåŧ^ØüdÆ{œ•Ë-,iÃŲ˙Â1Ęfq;į™Qtī ąúœ8ēpžŗrvFY]ķĨ…ĶÁ{úđZŲ?ĒĩØ3F\6ۺר<7v%¤ŽÉ œ§{ņiÊŊŧj„6dĄNáíËæ0įÉiÜęÍŖ'¯4˙Ėît‰ģ=ŖÎ‹AĮŒDķhcũäöäbãv‘ŅčžØįĘ īrČsM#+ĮísNaũÍF…ãÅfl;įw-ÖŲÅí>Ūs{fęäüėc6ޜÜYˆåāĀgr#ÖĶ)Fbt—›Ī~_ŒXȈuGvŗršŪĖ­UwˇcY}÷L+>Šģqlr2^ØˇPnņßįÁÛi>62ûÜ|×Ã%ãŖ7īčņŗ›vLvķÚ~ėlž1XĮčí•kô 6ējFw"¯ĩ„÷æ†nhŪēÅ'BœønfÃņÖåŗÃ˛RˇcY,ÜNÕ3âKß\Âú0›ķ¸¯ۈ ŪuíK—nUßžÕŌw§ņéQKôb;v˙‹~ôî=øæÉZÆâ#ŋ"žŖvY#L>{‡į"ų˜ÍüfŦwĮ›ä—ÅŲĶØb\īÔmĮ{„w—ņŊģy÷‰ņrŠ˙,7ÎŦ¯Ú9ŊĮßsđ¨kD{7ŽR;FgWŅųˇâ5ŧû›žĐáŪFĸj3aˇlÆ-hŖ!‹6CĪ ¨UCk5´¤ĀˆŽ?{j&Ū‡ô0Ž īq,F3ŧ6@}6Îq5ë¨vŒo,4WGĖÂLļ.´dҜš)nēÛhī§M=āRVnd>;Ųe]\V7˛¨ÅØM÷8@넎õJîq€B‹‘JFĐÍōBÁcę2ē@0‹+k$ &yy‚>H˙`düÂŦÜ<Ž'“íņsĮe4[ēŽ…HėڑíË;QŗÉf°[-îŦēSĒŅŪ¤k]‡§Ëãqē'eg›&wVˇwfLČöô;.ŖŗĢ?ÛØņ††ÂH[ÉčîpØÁđ0j”™ģĮé´Y!€P_=ÛŅ–ë§{ ”<(hQ32ˆ \ėąhiŗÕí„@fëtYĄ×C,đnwZ\ŨVāÚûąVŪ°“Aü8\ŪBâ =_wˆsÉŖEašæjŅ/đSo—ÕÔå#Y/0ĩÚMļXŖŌ;ė1ŠÖ4vyø „‹IËŽ&ˆyđŋÛã˛šØĀô2ĀņčÅ*ÅHĩX(Ĩ¸Đ 2;zí6‡Ņėo=#k*ˆ0P܇ ='dŗЉÆtYlN‹B~‚f‡#‡Xņzé˛ļ[=(O)ZAäZ5HdÎÔZēŨčYö‘ŒáuB* {V¯uĄÕi1[YWg6ĒeÃČy\nI÷â°ĀkÁŒŸ ĮKb/q#ęЈ—‘™8@'dXS6HpØÜūé™Ō/a*MČ9nŧ”@o0fA`ƒeĖZēÃÉ-X 3˛1Ø < ĶiG;$=;2Š'loœ]ēH ŖÛí0Y(>`Aę˛{Œl^ĩÚĀ2ŠŅO[ē…ËØ/§a‰Ė8+˛~wΡ¨Ų'Ü´\¸!éŊŨ6+Ä)ËašØ 8āE„4Ôĸœní@īlg(äî  Û{ĐâuŖF.J@ÃlPÜmAŠÚá´˛™õ‚ĸ˛ X˛‹†ŗ4ĸˇËŅ}Ņ2čqŲA 0; —bYXLo€Æ1ŋ؊Ū$6Ä!-ļølŧv‡-6Š[šeĖF ×åîBûBģÅoå}u!ön“\4˛]ĖhŊéutKcMëˊfmhĄ›šgĒuÕtJE ÔS´ôLCĢžą­•†Í ­ŗéÆēĸa6}™ĄĄZKëf55ëZZčÆfÚPßTgĐA›ĄĄĒŽ­ÚĐPKWÂŧ†FØß °´ĩ‘F 9(ƒŽÕëšĢôP­¨4ÔZgkéCkÂŦĐ ēŠĸšÕPÕVWŅL7ĩ575ļč€}5Ā6jš‹Ž^×Đ [o´ŅēPĄ[ôuu˜UEHߌåĢjlšŨl¨ÕˇŌúÆēj4Vę@˛ŠĘ:Ë ”ĒĒĢ0ÔkéęŠúŠZžÕ(Íx'ŨLŊ7ŋ ø¯ĒÕĐØ€Ô¨jlhm†Ē´ln™:ĶĐĸĶŌ͆dšæF€G愍æ5čXdjÚĪ#0ÕÛZtŖ˛Të*ęĢMöœĨ˜x<0ņx⟰íÄã_īņ€˙N<"øī|DĀzoâ1ÁÄc‚‰Į Æfķ‰Gū ŧ֙x\0ņ¸`âqÁÜãX›č{ūįžƒ_5ąŒī‡âžĩOŠđ^‡ŋũąŸjŪjšœ„1¤įRĮ+xüŪKˆÆSōK„ĮĪēÔņÁÁxüŨ—:^Š„ņđN ˙‹ĮķáW.!Č "ŒŒ$h2–Č${ˆbr1a K´’+ˆyäJÂFŽ"zy͈Až‹¸ n‡ywŽÁÚ4V`•Ö4ĀjŦų€Õ X}€ĩ°n„ąÁ‹,÷Á ŦĀĘŦ)€5°Ž,+`õÖ`­Ŧ €pĖ{h Ö>X€•X€UX­€5°l€u`-Ŧ[k3 lƒyøcQ}°ĸ+°J̰æV`õÖĩ€u `mŦ‡á1˜÷g,Ū<ŦĀĘŦi€5 °€õ[Āē°~X÷Öã€uŽÃŧ×Qŧ‹%¤Xj04Ũ>Y$BŖX5_, ÅBą¸X?ŧj•ĩ ũWNĄ„J῁ę#s„NŊ~ÕĒUܰáa<ˇ¯zw`@Č'…|gyų0Ą¸œ zWLQb~y9j&HÉįŋËãSbASĶFą”Ëæī5–O/o.ŋeāļa á$BR"âķųeÕCÃkē.*Šf™Ģ‡‡‡qą¸zhhØ)æZ‡†ŋētŠ’pŌųŠ'AâIä¤DA3LSĶüĻÚ@OŖ‡Ę‡ĘER$D‚„R)‘„HD‘!Ū§úDRR$Ÿ=ĐQ^ 4ŒUCŨb‰‰ËHLS*"DbmŽFSYŨ‚ ‚YķąL"!!W—3bÆ#ĨšĻ‘¸ÄĀÉ'ų‚¯ø|J*a7J¤4€V3ŠMŠMMMĢôĢ A×-Ŗ‡hN(vĘD¤ äõXėXFŠ—ī5Ņ•@HQPõßXÆ#e^ũ$–a‰e Rˆ$fÄMHb$ĸÁsbš -“r I’Ô”ĘĘÁÁĄ•ąˆ‹Ë*+O õ‰e¤xDl¯āh„äōöĘĘJWĘđÜĄ>ŲhĪūĮ/$;â%bQY%Īę“ķ(šĐ+='žÄPrZō@R„ū!ę˛B[™ ũĩí^y`ÍŗęgÕ)GˇĪß ?ûûR!%ņˆŠpÄ4û0$^€#6,‰îč8x –(HI Ũé^cRŗtA$“ŠŨm‚ŸƒâZŦÉ´oßžƒûä>}Ŋ ,ņĘą9įÃlj€EYû|gq´|eŸB@*„jĩšč'öî%¤@xJ ĸ⎎ŖG‡ŲŨĀģW Ŋ’2Ûė\9Õ͖›PšÂÕm×ŌUũ.›–Žĩ8âWŧē,PFĪd´tŅcŋÔq˜/‰yÃoĖxeňYÍ ÆÜ*”¤/Ķ/ûAAЍƒ1CĐ4@‘dŽŒ‘<*R@0FĄ4CÎ,ĸHūÆf:Ŗõi‰ž+v š(ÃԈī |“G÷Ė)ˆ˜x0~čö9/¸vŧųÃķUbųki[õĮķū‘¸qPŨÆ ō÷1ƒŧû7ō(’ĸ”y âÎ~ÛŊ|arp-x'Ŗ‘‡`zą˜ŧ6žPIĩĩä(™`T+Ĩ3î.ĢŊĶã°į1¨Q¤5[ĖŨģ9'–‰F-Reظ_…ȉgâP?OŠíoĩv[2[<Æn'ŨTUÁĆ+r ™Ļ(§¨ 8?T‹}ĒĖ’ŋŠd F†úeJ~}cSsN “ÄVcíUV'z4ZŨĸŖu- “*‹uU™yLE~fQN~~N“Āj=ŽF-ėffÔøZ˜ŧA2€v)5§˜{ŽÎ{}Ɲ?üákŧ=WiŋÛfØžžôäkZÛŲē7ßx˙Š3ũÔСčą}Õ;/Kč(:P}_ņrçiĨwœmÛ1l_w_ÔËßüŽ~ķÅNˇ&ÍūÛ[ڗŦuūíæwū™æā‡7ô¨>xv÷G9˂>YņIņ+ī?–sķß,Xw÷°ķúũž•ŅĶ{ĻönųÛãOū,|°āšũ!ŨĪUm’˙áÍēæĪ^ālQ;ޚtâА}ũá%gŌ‰÷>uô–ß<đvߎĸŒRí—K&GŲËM;-ˇŊļõ˛ŋ=ųÕãî˜'ëūņÂëÍíGŽ=÷ũĘ뤙7>Ņūésk–$m{qŠúûËäũmöß~'™Iņ`m$%`& āĢøĄKc>Ĩķŋ]ü­Úsëëõ7~{ĪoŽžĮPL_ͨBōOŊÖ\㔞,˙yņĪ;2ŪW°#iEâøõĖeŒacíFŨ˛*ÉeķEįB+jÍæžāÎq#ō"v"De af Ű0Iōë˜iŒŪ[g¨eeƒŪŪŪņX\Aö0J$o_ÎHŊ<ņ˜ÉCQ"ÚņĶiÃâķ#]?sxúÁ“?įĪŊâčŒõƏę>ēļípĖŨúD˙Nķņ¤õœ‹¨^įN}ëë“{:–ÖÜôũ6}"/ĨDÚĶģ2]gLûq__"ņũņ­y{˙ŧöú] ÞNˇmOrž°Ū”îZö[qDĮ­oß{6ú•ī÷~xlÍÃ?ß[“Ą<øwŅ‚MĮž-Šxķ*Wâ‹k­ļŠJ{ĮĄ•y7PK~xÅWŗ.˙b—‘šÛôĖa›˛ ŽôæNī[Z°äīŊ}õXV^įĘŌeOQ{>Žģŗ~Ī¤Ú”Í ü&~ŠåĢĢĻøČúŌö?l‰¯T=ôEāĮO_ķģĩGKk_ÜĨËø,Ą‚’Æ>ņIcû?YqęĘ%MŸœÃilŋ¯ÕdÆŽųU’E*“Ė.ú8ß~ŗ…nąvâ/€cŅ7Ârp6+bŠsrr .›VΝ"×Īģ@˙/fŖáëOÜ'ēiŨ@Øéäų§]Ãڟūąyõđ5ģ6™ˇ"{R^VėÍ}?]Ŋ-nÜyå‘Č'y‡k>;°ö‡Ÿų1ß\+=§ąoúĻsōõ‡ŠqßņoĢ0}ūūŸÂVžTŽ+xģØŲę(ũüA„1<ũÔMĖZų‘ÅĪũāž]Õûâõģo{V|-}2öŪ‚¯=ķŽ‡¸ėē—Ūŧųŗ}goøéÁųÓ÷<÷Pûę?ÚžęĄĖxšõį‚ן_tËGąį>_´đČīċ=īM×˙åk␞nŗ¨āÃ؊3W˙ūĐGsŪŋöģëãnÜōÁPøĶ'oˆ!Ÿ=ŖßĒŧ%ouŧ>÷Ô3‰w<ÕrxŠ=mî’/‹íßîū\)ûĖ›Ā"Wŗé& Ĩ›‘šNLŽŦTžOē:rĸ}čØü’OĪu>sųK‡vßŋkŸr ͌ēƒų‹îŽetcwš|&UƌÜ<†ÉÉÍ03ųícf~I{~f~n^qfq^anĻš¸ §Ã˜›[ßaōKzģųÃ&Á˃÷…ivvß{¸‡ēũÂ)pÜ åpēq„p8†(†Fņ;Ŋd2E™L1NFŸØÆĀiÅ'ę~‘7 ^„…‡‘#ÁábŽO1ĘåˤHB¨Š{cæ3M‡īšŪ÷ęÉSgžßķĘŪ¯Œšq˛åĩVđĘū#ŸŋwzíÜÛį§îč”īŽë~˛ãū7vFĩ%뚜ĐWŅũĐŠ¯‰9ˇ­Ŋ.ú¨äöãëĸĢ™m÷¨žũSíÜī2ō¯ßpĶŦĸ} ŅÔzū¯ƒAÛ žzHsčĻÄ-KŽ'%úƒŽ˜S˛ÎÍäÕ?m_ē1÷ŗGwd7͸B¸=låĄĶ.ˇüũW&ĻßĄÛšģtĘSfzVœŨôėuŠÃĻȘ“3ˇdÁ÷Ū=ŧđŽTĮ×ûút.üh{ҝ­‘ĩ7Žš§{¯=åāŠ”¸C'ém˛í_ŋ [wÛ{ î´.ŨTøj7}öÚWÎí{|uĄäėäЧׄnÛģė藃OßߖXĨŪŠŋļoŲņ_ēsjÄkĄ+>žaCWâpWéļg’?ĮיÎüūÖ°úŧ3æ7ž:í‰âĪeŊĩ}ŪŨU Ÿë;ļ}÷›–Ú–ģîûôžŸ7ŧyĸä´ųšî)â¯^ēũÁ'7˙éĒcwˏûĘYGBjÛ_Š˙ōtŲūŲŲSĖ÷9æ7MŨUŊĒqŖėú§Ž™õũŗËoüaÍūC+8j˙ļ7ëļ“Ûŋ˜éū|áŪOîX|hx˙ŲŌīr ™q,â/ģŋģíđōčoE-qīxyŽfę¤Yęw†ŋčÜoؚũfŌõ“süķüę›cžŧYžxpʗû˙šš‰OŨ¨˙ņˡ¨cŧģ`Á&đ%ģ HĒŽ|œûŖĮaįát*•Ü’ŧâÖo´f2BŃhˉ`Âũ%#Á a˜ÁæÍÄŅŧŲėp@ō„ĐĩvXMF…Žčņt9\VO?JîL“Īäåää1%Üssp5AÕ˙wgč_Ęī6Ųļŋķ†ū–ôĢfEümĪ{īX;=ĄéÁŪR7$~ņâÖëô0tđgĸWZo3ÜUyËCk.g’_'ūũĒ=Ÿ¯ūĀ_ķՊŖqGō—ßųÍ?:Ŗĩ§¯úx8æĶ6oz:Ąåđ ?éŽIŽ˙æĮŽäßõãÛ­¯ĻžYĶōđ˛ãĻÖdĨ<°Ŧą­YūOûķ‚UĢûōog3wūt͉Õ;ūŋúšS/)ŋījén~Tˇjƒž˜VÛœ’Öqīę^.™v׏C[ƒkC%ƒ†Nļõ%×Å4‰¯%‚˜š“ģŪN¨ŲŊ?ŗuÃcû*rzŽ§t魛ŒÔÎÅöĶ?Ŧ„|AsYëšûžĄeŪü~?Xd+8’q Ū|ōų¸§K”žcų|ˆŋeLPÂí a$j!˜%kØÜŧdŗä†Đ€į—ĪHYũa’ōtúߤ-ˇĪūāîMĻģŋzxõ?¨Ú4mã=ÖšgũC¤Ė˛0MėĻ```ÚXĩąbŲÔK?tŖoŖTŽ7„VŸ AĪÔüO1įÖDļÆņŠŌ‘nŌÃ$R¤j¤ĨGz“""%€HŠ H[zMT@Ĩ‰°ô" Č"MDY:ŠtÛ ¸ ÜÕ{īî‡ûė|;3Ī9gæĖûžßųŸ÷4AūīŦ‰wžCũ[Ģq=LkĻ´ë­Öär#ŗī w듕A}/YyŌąu7ĮWC˜oÆzÚW›ŸđąeŒŠMšÕ–˜g›ā&‹,ŽÅŦÆô,(‘-M6ÄĶPļßЙ\1a1,Jšžšqáļųmō*•tųlĸ¸€÷ĮO͘ (ũæĄIī:Nƒœ8wŸ”jĸBļ‹T›1ÜŊĩ*{z Ÿęä!üCėŒ?LY‡ļ}Î[ųk ËX ]ÜĘËjŽyƒ˜Đ6Y‰ķˇįëBhÁ&>üK@g-ÆÉڊŒƒ†•Ąī5kúûS?;›WHIĪ|ˆˆė26}—ãėQŦ 7°Øx3Č^lųf–Ø Lj}‡2'/~…öŠdísõŠ7B*§nßņ•­6hģ$Č|ܟö*ö’Ĩ–:k]EEŠžK{â+6›Ë8ŋC0Ÿ‡´į đ÷¨ĪJĖÖŽëtI ÁązĮÅu„l,įL— F3r:/ÖãD|ŠŽ,ųķ7fá›EÎ>*ģ Môˇ{čEd)hŧ§ŊÂ|ņķu¸Gų—1ãöXÁįúîkĖŽ`eŠ‹øęiū7•Ĩ1g)NCŠ“Kķ1E„T?Č̤k,~Ōđ;‡ŊVąÂ„åđNūÁyÎĖ%äø&™ĶÅhڐvˇöˇ^s…iŨ0ą¯ mVÖCú\ÄĄmé\Uč9v÷–[ŸüĄ Oi˙ úvQ@ūg€‹úŋLÅpøæbÅ!÷Œ„ y8 Ģđ rģE°SüĮ ü=;Ā;듨Aōšĸ•mĻcĐûC^÷đLú'jV™ķį!¸ÄŨg-îUSÉC(5Ą­t<#'ŨŸ0ŅŽČˇdP•ļ+ŧ c…!úŖé¯]Mļō(ÉEfĪēžīË2)§‘l-yuWâAuÉËT‹N[åŦŗ˙;8ę8ŗôLŅaŖįUčĄĮPrŋ"×ĩgžkŠÖDöu­šqyĮb/GYLÁQĒ_í§­ŠŅCô/Ŧķ‘b3ô –€†dååS–LŧúĻĸ7ƒ|ƙ̐į‡ÕÃ^—GrŊR)‹Eŋ‹6 ‡ŦĨ-Ļ”¤ȘˇUŠ|÷W+—•—$É_íËÁJž70Mä—nUđr 5ŠÉfŧT0üŲz yäM›•Tclōĩē&~_aNŅG]"ĸōÂé gäž_)KzpL°đŽķ‚ī… QdŽMÔ¤0ēŸ_Wõ¸ŌLUˆ|Ĩ7ČJú…ā”7šŅX+ b 4QW ÆÛ 7ąUÔs œĶQ 2Î "ë8Ģ5ŽhN7ˇúûĖ5je´-ˇ3ģą  ‹âÆŦōJ>”:O6§á‚ugb…,ĸ…!.Øˇ×í16åŌá/Ͳ­DE[ôl—ŒW;iØ<Ąũ˜Z¯m _]Ú7eĶk Ãg.ɂļMÉT1” ]Å1šk°žZZ§EđHīŒŠŨcį"‰ŗ?Āß><¨KŽîU`SĐņЀLv"ÔA§˙ĢßAų âņ‘RÃÔfĨ4˜˜+| ëŒ>X~ƒÛÎĒ!AŸ ‰ü[›>$ŋ%y-ÉY÷D‰ c‡ībîüĖĄ#Āāæ s˙Ĩ}_—ˇsķ|¸4— ā÷ JāÂÕ?ē“ąËü/™ĩķĩéÉÜ<í|ŧ/C]}=ĩŊĀĀ 87HärŲÍ=ąŲÍ=ų–ĢH*]ū=‹Ęi/— ĘĮũ#!æ˛™Ÿ>~6íōuČĸM=2ᐔH é ¤Khv˛JĒlĩúôz†}iP}GĶŠÔ¨}÷֚۰CŖ€l~Ú)ˆîąĩSˆTOég÷)•CPąŦˇĘ\ų•ÜÉ “ŗŽʘ Á5–‚ß°ëĪŽƒĩÄ[Ž3ÕŪžKI—ĩčēí M!ˆĢŠģ›#xŠŨŧ,ĶS§Ã֛â×´$F?)õÔË.{ ?xS"˛Ø3˛ÆP’!š–ŽĪ Lģz8zˇÎ9šŌ&Õm•ûŠ@ķ„ĻåÉũoĘ_ ŗEkšËÃ/‰@BËÖEļF%ųÜŌË-ĸ]Ŋ.VųļĒQR‰‹ĒāUYôi›*ôßOćģČĸYč˙FMÜéV+eŲĘí —9özmk•˜)2ņK~ZĪÚáô”ÕĄėk*TTŊTe~ŧŦ vv•+ŋ>áĸh;ũ”AtiÔIz!mƒh:$jÕ[ŦĨåSëę0e`y{@bmeYųǚ<˛OúnŪĖ ØÖIá-ú¨-ˆ}ŸģÕč^Ĩ›69,ĖĖäÔũ:X!čę÷ļdûSĖ<-vÎMŠä°HĄ76æį鐨ܛcj`؈5 bŽĀųƒ–OĶ”Š~ŧĶuŨLŒĘ2ģdj ŖŲ„čČōˇĸÁę¸ĖkŽ÷ôŧЁēĖBdô OQ ā)ŠÁdd.åŸ׏ˇ÷ƒ#ÜãÉįw#Ļ&‡ŅŒŧîbŋD c^e÷+RĀHS[šÃ-Í2Ķlļ3Žrw:Ã>HōŨ€ã*t0Sā,A+úÃ÷ŗß˙}ˆx+ô=ûėŪ×v|b3ž ķŠę¸ģ͋(ĄŽâPƒ­$4ieöZ,2NËxč>†g€ģk‰ ŽB`â4Bu‰Úzf5nøDŗ‰hC(Ĩx¯ēlZų_Ô/ā‡õÁæÆk—;Nķt[ōpąßw†<ˆūœÍh™ÔÎåė"UpØlĶi›|`’Áū*Îsg Ęžņļ8Ŧļi¤qŨî 3‡-Äõ͕+u= —^ÃOøģ8÷*×ÄužŸøIÄÁlÉúyŽĨ÷av—ú š0Vę\Ë÷‰ü‰=)ņåÁō¨&‰TmEJÎÂHÉZ9l. Ŗđ#:(Ú•ųŌGąEŊZÕīyzä´ar§'˛ę Z%âÁĸ¤å‰Đū;ĸ‚áÁl¤SGvM3îâ?Ž´°I4ĀyĐ$i÷#†d¤Î÷ŽPÂw7Žå`˛pØÎaųEúŗŠ!6—ÕGŨWMG`4ŊPˆĒ?IĻ[‘ú­%ļí*ÕÉŠdˇP×ĘÍO k=#ëē÷Lp Ÿ%•ßfPč6ËÁG{7đ4:8I?\Eđ`ÄagļpļĢpw[éËåZã52jäâ<•™dB)˜SuŽõ_k.o%X,@Ŧ‚Į´~z˛Qg§(„ĖģĨÁ5¤ņB}\ĀjĢīf("ģˇžFēåč,Ž%Ę#EZ&Ķ#4xĐ#ÍČčåšsŋčÍãl†A A‘GũžSxmv'¸ ôļ‰œAï~ë,žúˆŅ/ųūP9õt„ŪŠEŠCů\kY^@ɑa]å 5)0čbŌF(aŲ-z&Ņ_™tKnĪĖë> stream MicrosoftÂŽ Word for Microsoft 365 Thomas Willhalm MicrosoftÂŽ Word for Microsoft 3652021-12-30T16:10:37+01:002021-12-30T16:10:37+01:00 uuid:3E583F46-1127-4712-BF20-CB935514EA97uuid:3E583F46-1127-4712-BF20-CB935514EA97 endstream endobj 86 0 obj <> endobj 87 0 obj <<463F583E27111247BF20CB935514EA97>] /Filter/FlateDecode/Length 250>> stream xœ5ĐšNBAÅņšp‘Ëu\qWxmLL4Áİ˛ˇqÃÂg1F㠉•ž Ąą°3žÎœ?L1ŋ|“œ|'cŒ=ÃĄgīĐĮ |‹ØĄH=‰ô‰Č4ážEļ%rû0ų/Q8‚ $āTāW„?ĸõ*"❤x;sxž#^:ˇílĪšŲ†؅=؂<Œu/ߎ'b0IHA2… €",Øĩ•ãņÚIĄ30Ķ0 e¨Bæ`a –Ą+° 5Xƒu؀M×ķAŸ\Ŋҧčôū͇¸{mÛâ>Ũ@ô¯Œų%ŋ*< endstream endobj xref 0 88 0000000027 65535 f 0000000017 00000 n 0000000166 00000 n 0000000229 00000 n 0000000580 00000 n 0000002211 00000 n 0000002379 00000 n 0000002619 00000 n 0000002672 00000 n 0000002725 00000 n 0000002894 00000 n 0000003134 00000 n 0000003296 00000 n 0000003523 00000 n 0000003655 00000 n 0000003685 00000 n 0000003845 00000 n 0000003919 00000 n 0000004159 00000 n 0000004336 00000 n 0000004585 00000 n 0000020136 00000 n 0000043459 00000 n 0000043783 00000 n 0000044626 00000 n 0000106756 00000 n 0000202335 00000 n 0000000028 65535 f 0000000029 65535 f 0000000030 65535 f 0000000031 65535 f 0000000032 65535 f 0000000033 65535 f 0000000034 65535 f 0000000035 65535 f 0000000036 65535 f 0000000037 65535 f 0000000038 65535 f 0000000039 65535 f 0000000040 65535 f 0000000041 65535 f 0000000042 65535 f 0000000043 65535 f 0000000044 65535 f 0000000045 65535 f 0000000046 65535 f 0000000047 65535 f 0000000048 65535 f 0000000049 65535 f 0000000050 65535 f 0000000051 65535 f 0000000053 65535 f 0000203594 00000 n 0000000054 65535 f 0000000055 65535 f 0000000056 65535 f 0000000057 65535 f 0000000058 65535 f 0000000059 65535 f 0000000060 65535 f 0000000061 65535 f 0000000062 65535 f 0000000063 65535 f 0000000064 65535 f 0000000065 65535 f 0000000066 65535 f 0000000067 65535 f 0000000068 65535 f 0000000069 65535 f 0000000070 65535 f 0000000071 65535 f 0000000072 65535 f 0000000073 65535 f 0000000074 65535 f 0000000075 65535 f 0000000000 65535 f 0000203647 00000 n 0000203894 00000 n 0000240066 00000 n 0000240599 00000 n 0000292795 00000 n 0000293300 00000 n 0000293591 00000 n 0000293618 00000 n 0000293801 00000 n 0000315919 00000 n 0000319093 00000 n 0000319138 00000 n trailer <<463F583E27111247BF20CB935514EA97>] >> startxref 319589 %%EOF xref 0 0 trailer <<463F583E27111247BF20CB935514EA97>] /Prev 319589/XRefStm 319138>> startxref 321507 %%EOFpcm-202502/doc/LATENCY-OPTIMIZED-MODE.md000066400000000000000000000104741475730356400166630ustar00rootroot00000000000000# Latency Optimized Mode in IntelÂŽ XeonÂŽ 6 Processors IntelÂŽ XeonÂŽ 6 Processors (previously codenamed Granite Rapids and Sierra Forest/Birch Stream platform) introduce a new power management mechanism called Efficiency Latency Control (ELC), designed to optimize performance per watt. This feature allows hardware power management algorithms to balance the trade-off between latency and power consumption. For latency-sensitive workloads, further tuning can be performed to achieve the desired performance. The hardware monitors the average CPU utilization across all cores at regular intervals to determine an appropriate uncore frequency. While this approach generally results in optimal performance per watt, some workloads may achieve higher performance at the expense of increased power consumption. For instance, an application that intermittently performs memory reads on an otherwise idle system may experience delays if the hardware lowers the uncore frequency, causing a lag in ramping up to the required performance levels. To verify this, the uncore frequencies can be monitored using the pcm utility: ![Uncore Frequency Statistics DEFAULT](https://github.com/user-attachments/assets/108c7350-3fc2-4056-aeaf-ecc7c25da6bc) The screenshot above presents real-time data on uncore frequency statistics, measured in GHz, from a dual-socket platform (represented by two rows). Each socket includes five dies (organized into five columns). The first three dies contain CORes (COR), Last Level Cache (LLC), and Memory controllers (M), collectively referred to as CORLLCM. The final two dies are IO dies. The ELC control has parameters that can be adjusted either through BIOS or software tools. The default parameter configuration is optimized for performance per watt, ensuring power efficiency. The alternative configuration, known as Latency Optimized Mode, prioritizes maximum performance. Below are the PCM statistics from a system operating in Latency Optimized Mode: ![Uncore Frequency Statistics Latency Optimized Mode](https://github.com/user-attachments/assets/70310bbc-725b-4450-af7a-1db2c04291dd) ## BIOS Options for Latency Optimized Mode The BIOS option for selecting the Default or Latency Optimized Mode can typically be located in the following menus, depending on the BIOS version and OEM vendor: - **Socket Configuration** -> **Advanced Power Management** -> **CPU – Advanced PM Tuning** -> **Latency Optimized Mode** (Disabled or Enabled) - **System Utilities** -> **System Configuration** -> **BIOS/Platform Configuration (RBSU)** -> **Power and Performance Options** -> **Advanced Power Options** -> **Efficiency Latency Control** (Default or Latency Optimized mode) Should this BIOS option be unavailable or if there is a preference to change the mode during runtime, the PCM repository provides scripts for changing this mode. |Platform |Script Type| URL | |------------------|-----------|---------------------------------------------------------------------| |Linux/FreeBSD/UNIX|bash | https://github.com/intel/pcm/blob/master/scripts/bhs-power-mode.sh | |Windows |powershell | https://github.com/intel/pcm/blob/master/scripts/bhs-power-mode.ps1 | The scripts require the pcm-tpmi utility. There are several methods to obtain this utility: - **Download or install precompiled PCM binaries:** Please refer to the following link: [Downloading Pre-Compiled PCM Tools](https://github.com/intel/pcm?tab=readme-ov-file#downloading-pre-compiled-pcm-tools) - **Compile the utility:** Follow the instructions in the "Building PCM Tools" section available at: [Building PCM Tools](https://github.com/intel/pcm?tab=readme-ov-file#building-pcm-tools) * For Linux/FreeBSD: Copy the pcm-tpmi utility from PCM’s source 'build/bin' directory to `/usr/local/bin/` or execute `make install` in the 'build' directory. For Windows: Copy the pcm-tpmi utility to the current directory. Once the pcm-tpmi binary is correctly placed, you can set the Latency Optimized Mode. ### Setting Latency Optimized Mode Linux/FreeBSD/UNIX: ``` bash bhs-power-mode.sh --latency-optimized-mode ``` Windows: ``` .\bhs-power-mode.ps1 --latency-optimized-mode ``` ### Restoring the Default Mode Linux/FreeBSD/UNIX: ``` bash bhs-power-mode.sh --default ``` Windows: ``` .\bhs-power-mode.ps1 --default ``` pcm-202502/doc/LINUX_HOWTO.txt000066400000000000000000000004531475730356400155720ustar00rootroot00000000000000Refer to the main README ("Building PCM Tools" or "Downloading Pre-Compiled PCM Tools") sections Running tests: * build PCM * as root run "sh test/test.sh" - Automated CI test workflow: https://github.com/intel/pcm/blob/03ad6b0228c9e8a8eb022708b53a61875b9447bc/.github/workflows/ci-test.yml#L40 pcm-202502/doc/MAC_HOWTO.txt000066400000000000000000000047771475730356400153100ustar00rootroot00000000000000Building and Installing Note: xcode is required to build the driver and dynamic library. Requirements ____________ Building and installing requires make, cmake, gcc, and xcode. It has been tested on the following system configurations: OS X 12.0.1, Xcode 13.1, Apple LLVM compiler 13.0.0 Build PCM and MacMSRDriver ----------------- mkdir build && cd build cmake .. && cmake --build . PCM utilities will be located in build/bin folder, libraries libpcm.dylib and libPcmMsr.dylib - in build/lib. Automatic Install ----------------- cd build sudo make install Install command loads the driver, installs the library into /usr/lib and installs the library headers into /usr/include. Also PCM utilities are installing to /usr/local/sbin. Manual Install -------------- Build steps are the same. To install do the following: 1) load the driver by running src/MacMSRDriver/kextload.sh 2) copy build/lib/libPcmMsr.dylib to a location on your path (auto-install uses /usr/lib) 3) copy src/MacMSRDriver/MSRKernel.h to a location on your path (auto-install uses /usr/include) 4) copy src/MacMSRDriver/MSRAccessorPublic.h as MSRAccessor.h to a location on your path (auto-install uses /usr/include) kext Signatures --------------- As of OS X El Capitan, kexts must be signed. So after building the kext, kextload.sh may fail with: /System/Library/Extensions/PcmMsrDriver.kext failed to load - (libkern/kext) not loadable (reason unspecified); check the system/kernel logs for errors or try kextutil(8). In this event, you will need to either disable System Integrity Protection or sign the kext. You can disable SIP by rebooting into Recovery (reboot, command-option-R), opening a shell, csrutil disable and reboot again. Signing a kext is more involved. You can't self-sign and will first need to obtain a Developer ID from Apple: https://developer.apple.com/contact/kext/ With this ID, you can then sign your kext with codesign. PCM Execution ---------------------- Now you can run ./pcm utility. See description of other built utilities in LINUX_HOWTO.txt Logging/Debugging ---------------------- Sometimes you will get errors while running utilities that may come from the kernel, and you can use something like this DTrace script to correlate it with user-land behavior: $ sudo dtrace -n 'fbt:mach_kernel:_ZN*IOUser*:return /execname == "pcm"/ { @hgram[probefunc, arg1, ustack(20)] = count(); }' -c ./pcm Various commands that can help diagnose errors: $ kmutil log stream $ kmutil inspect -b com.intel.driver.PcmMsr pcm-202502/doc/PCM-EXPORTER.md000066400000000000000000000033571475730356400153670ustar00rootroot00000000000000# Intel® Performance Counter Monitor (Intel® PCM) Prometheus exporter pcm-sensor-server is a collector exposing Intel processor metrics over http in JSON or Prometheus (exporter text based) format. Also [available as a docker container](DOCKER_README.md). Installation on target system to be analyzed: 1. [Build](https://github.com/intel/pcm#building-pcm-tools) or [download](https://github.com/intel/pcm#downloading-pre-compiled-pcm-tools) pcm tools 2. As root, start pcm-sensor-server: `sudo ./pcm-sensor-server` or as non-root https://github.com/intel/pcm#executing-pcm-tools-under-non-root-user-on-linux Alternatively one can start [pcm-sensor-server as a container from docker hub](DOCKER_README.md). Additional options: ``` $ ./pcm-sensor-server --help Usage: ./pcm-sensor-server [OPTION] Valid Options: -d : Run in the background -p portnumber : Run on port (default port is 9738) -r|--reset : Reset programming of the performance counters. -D|--debug level : level = 0: no debug info, > 0 increase verbosity. -R|--real-time : If possible the daemon will run with real time priority, could be useful under heavy load to stabilize the async counter fetching. -h|--help : This information ``` The default output of pcm-sensor-server endpoint in a browser: ![image](https://user-images.githubusercontent.com/25432609/226344012-8783e154-998e-48a7-a2ca-f2c42af9c843.png) The PCM exporter can be used together with Grafana to obtain these Intel processor metrics (see [how-to](../scripts/grafana/README.md)): ![pcm grafana output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-dashboard-full.png) pcm-202502/doc/PCM-SENSOR-SERVER-README.md000066400000000000000000000075511475730356400170670ustar00rootroot00000000000000# Global PCM Events | Event Name | Description | |-----------------------------|-----------------------------------------------------------------------------| | Measurement_Interval_in_us | How many us elapsed to complete the last measurement | | Number_of_sockets | Number of CPU sockets in the system | # Core Counters per socket OS_ID is the OS assigned ID of the logical CPU core and denotes the socket id, core id and thread id. The events below are followed by the same {socket="socket id",core="core id",thread="thread id"} as the OS_ID of their section with source="socket/core/thread" appended that denotes what the quantity of the event accounts for. For example Instructions_Retired_Any{socket="0",core="1",thread="1",source="core"} refers to Instructions_Retired_Any for socket 0, core 1, thread 1, and accounts for the total instructions retired of the specified core. | Event | Description | |------------------------------------------------|--------------------------------------------------------------| | Instructions_Retired_Any | Total number of Retired instructions | | Clock_Unhalted_Thread | | | Clock_Unhalted_Ref | Counts the number of reference cycles that the thread is | | | not in a halt state. The thread enters the halt state when | | | it is running the HLT instruction. This event is not | | | affected by thread frequency changes but counts as if the | | | thread is running at the maximum frequency all the time. | | L3_Cache_Misses | Total number of L3 Cache misses | | L3_Cache_Hits | Total number of L3 Cache hits | | L2_Cache_Misses | Total number of L2 Cache misses | | L2_Cache_Hits | Total number of L3 Cache hits | | L3_Cache_Occupancy | Computes L3 Cache Occupancy | | SMI_Count | SMI (System Management Interrupt) count | | Invariant_TSC | Calculates the invariant TSC clocks (the invariant TSC | | | means that the TSC continues at a fixed rate regardless of | | | the C-state or frequency of the processor as long as the | | | processor remains in the ACPI S0 state. | | Thermal_Headroom | Celsius degrees before reaching TjMax temperature | | CStateResidency | This is the percentage of time that the core (or the whole | | | package) spends in a particular level of C-state | | References: https://software.intel.com/content/www/us/en/develop/articles/intel-performance-counter-monitor.html https://software.intel.com/content/dam/develop/external/us/en/documents-tps/325384-sdm-vol-3abcd.pdf - Chapter 18 Performance Monitoringpcm-202502/doc/PCM_ACCEL_README.md000066400000000000000000000142711475730356400160220ustar00rootroot00000000000000## Purpose: IntelÂŽ XeonÂŽ Scalable Processors UNCORE accelerator(start from 4th Gen IntelÂŽ XeonÂŽ Scalable Processor (codenamed Sapphire Rapids)) including IntelÂŽ In-Memory Analytics Accelerator (IntelÂŽ IAA), IntelÂŽ Data Streaming Accelerator (IntelÂŽ DSA) and IntelÂŽ QuickAssist Technology (IntelÂŽ QAT), etc are key feature of IntelÂŽ XeonÂŽ Scalable Processors that can benefit the Intel architecture platform performance in the data center industry. The accelerator and related software stack can be a key contributor to data center system performance, but sometimes it’s NOT easy for customer/user to get/understand the performance data of the accelerator like utilization, throughput, etc since low level hardware event sets is complex to understand without the deep knowledge of the accelerator hardware/software architecture. This pcm-accel tool will sample the performance data from accelerator hardware and show it to end user in an easy-to-understanding format. The goal is to help the user to quickly and accurately see a high-level performance picture or identify issues related to accelerator with or without solid knowledge of it. ## Command syntax: pcm-accel [target] [options] #### the target parameter Notes: only 1 target is allowed to monitor. | target | Default | Description | | ------ | ------- | ----------------------------- | | -iaa | yes | Monitor the IAA accelerator. | | -dsa | no | Monitor the DSA accelerator. | | -qat | no | Monitor the QAT accelerator. | #### the options parameter Notes: multiple options is allowed. | options | Default | Description | | ---------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------- | | -numa | no | Print NUMA node mapping instead of CPU socket location. | | -evt=[cfg.txt] | opCode-x-y-accel.txt | Specify the event config file name as cfg.txt.
- x/y is cpu family is model id, for example 6/143 for Sapphire Rapids. | | -silent | no | Silence information output and print only measurements | | -csv[=file.csv] | no | Output compact CSV format to screen or a file in case filename is provided | | -csv-delimiter=[value] | no | Set custom csv delimiter | | -human-readable | no | Use human readable format for output (for csv only) | | -i=[value] | 0 | Allow to determine number of iterations, default is 0(infinite loop) if not specified. | | [interval] | 3 | Time interval in seconds (floating point number is accepted) to sample performance counters, default is 3s if not specified| #### Examples: This example prints IAA counters every second 10 times and exits > pcm-accel -iaa 1.0 -i=10  This example saves IAA counters twice a second save IAA counter values to test.log in CSV format > pcm-accel -iaa 0.5 -csv=test.log            This example prints IAA counters every 3 second in human-readable CSV format > pcm-accel -iaa -csv -human-readable ## Prerequisites: Linux* OS: FreeBSD* OS: Windows OS: - Install and load the required accelerator driver(iaa/dsa, qat driver, etc). Notes: - QAT monitoring and NUMA node display feature is supported only on Linux OS! ## Tool UI introduction: Common indicator(Column field): - Accelerator = Accelerator device id. - Socket = CPU socket id where accelerator device is located. - NUMA Node = NUMA node that accelerator device belongs to. - Inbound_BW = Data throughput input to the accelerator device, unit is Bps(Bytes per second). - Outbound_BW = Data throughput output from the accelerator device, unit is Bps(Bytes per second). Specific indicators related to IAA/DSA:  - ShareWQ_ReqNb = The number of request submitted to share work queue of accelerator. - DedicateWQ_ReqNb = The number of request submitted to dedicate work queue of accelerator. ![image](https://user-images.githubusercontent.com/25432609/224027332-8846dff6-f71e-4daa-a189-730e68c7e1b2.png) ![image](https://user-images.githubusercontent.com/25432609/224027445-2b08e89c-4653-4f39-971b-a7dc76bd7349.png) Specific indicators related to QAT: - util_comp0 = The utilization of the compress engine 0, unit is %.(Sapphire Rapids platform has 1 compress and 3 decompress engine per QAT device) - util_decomp0 = same as above for decompress engine 0. - util_decomp1 = same as above for decompress engine 1. - util_decomp2 = same as above for decompress engine 2. - util_xlt0 = same as above for translation engine 0. ![image](https://user-images.githubusercontent.com/25432609/224027570-e433aeef-c2ed-418d-aa42-18eef0f1b645.png) ## Event config file: pcm-accel tool allows the user to customized the monitored performance events with the config file as advance feature. Customize fields of cfg file: - ev_sel and ev_cat field for IAA/DSA monitor event. - ev_sel field for QAT monitor event.  - multiplier/divider is for event data display calculation. - vname is the event name string(column) displayed in the UI. Please refer to the spec or code to learn more about the event mapping if you want to customize it. - IAA/DSA: https://software.intel.com/en-us/download/intel-data-streaming-accelerator-preliminary-architecture-specification - QAT: please refer to the [mapping table in source code](https://github.com/intel/pcm/blob/f20013f7563714cf592d7a59f169c1ddee3cf8ba/src/cpucounters.cpp#L915) Here is the content of the event cfg file(opCode-6-143-accel.txt as example) ![image](https://user-images.githubusercontent.com/25432609/224027717-1dcdae9e-6701-4b6f-90a0-8108c4ea4550.png) pcm-202502/doc/PCM_RAW_README.md000066400000000000000000000343451475730356400156500ustar00rootroot00000000000000-------------------------------------------------------------------------------- PCM Raw Utility -------------------------------------------------------------------------------- Disclaimer: in contrast to other PCM utilities this one is for expert usage only. *pcm-raw* allows to collect arbitrary core and uncore PMU events by providing raw PMU event ID encoding. It can become handy if other low-level PMU tools (e.g. emon, Linux perf) can not be used for some reason. For example: - emon kernel driver is not compatible with the currently used Linux kernel or operating system - loading emon Linux kernel driver is forbidden due to system administration policies - Linux kernel is too old to support modern processor PMU and can not be upgraded Currently supported PMUs: core, m3upi, upi(ll)/qpi(ll), imc, m2m, pcu, cha/cbo, iio, ubox Recommended usage (as privileged/root user): 1. Install VTune which also contains emon (emon/sep driver installation is not needed): [free download](https://software.intel.com/content/www/us/en/develop/tools/vtune-profiler.html) 2. Run emon with `--dry-run -m` options to obtain raw PMU event encodings for event of interest. For example: ``` # emon -C BR_MISP_RETIRED.ALL_BRANCHES,UNC_CHA_CLOCKTICKS,UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART0,UNC_UPI_TxL_FLITS.NON_DATA --dry-run -m Event Set 0 BR_MISP_RETIRED.ALL_BRANCHES (PerfEvtSel0 (0x186) = 0x00000000004300c5) CC=ALL PC=0x0 UMASK=0x0 E=0x1 INT=0x0 INV=0x0 CMASK=0x0 AMT=0x0 cha Uncore Event Set 0 UNC_CHA_CLOCKTICKS (CHA Counter 0 (0xe01) = 0x0000000000400000) qpill Uncore Event Set 0 UNC_UPI_TxL_FLITS.NON_DATA (QPILL Counter 0 (0x350) = 0x0000000000409702) iio Uncore Event Set 0 UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART0 (IIO Counter 0 (0xa48) = 0x0000701000400183) ``` 3. Run *pcm-raw* by specifying the obtained raw event encodings to collect into csv file. Example: ``` pcm-raw -e core/config=0x00000000004300c5,name=BR_MISP_RETIRED.ALL_BRANCHES -e cha/config=0x0000000000400000,name=UNC_CHA_CLOCKTICKS -e qpi/config=0x0000000000409702,name=UNC_UPI_TxL_FLITS.NON_DATA -e iio/config=0x0000701000400183,name=UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART0 -csv=out.csv ``` 4. View/process the csv file using your favorite method. For example just open it in Excel. -------------------------------------------------------------------------------- Collecting Register Values -------------------------------------------------------------------------------- pcm-raw supports collecting raw MSR and PCICFG (CSR) register values. The syntax is described below: Model Specific Registers (MSRs): ``` package_msr/config=,config1=[,name=] ``` static_or_freerun encoding: * 0 : static (last value reported in csv) * 1 : freerun (delta to last value reported in csv) Examples: ``` package_msr/config=0x34,config1=0,name=SMI_COUNT thread_msr/config=0x10,config1=1,name=TSC_DELTA thread_msr/config=0x10,config1=0,name=TSC ``` If the name is not specified the first event will show up as package_msr:0x34:static, with the name it will show up as SMI_COUNT in csv. PCI Configuration Registers - PCICFG (CSR): ``` pcicfg/config=,config1=,config2=,width=[,name=] ``` * dev_id: Intel PCI device id where the register is located * offset: offset of the register * static_or_freerun: same syntax as for MSR registers * width: register width in bits (16,32,64) Example: ``` pcicfg/config=0xe20,config1=0x180,config2=0x0,width=32,name=CHANERR_INT ``` From: https://www.intel.la/content/dam/www/public/us/en/documents/datasheets/xeon-e7-v2-datasheet-vol-2.pdf MMIO Registers: ``` mmio/config=,config1=,config2=,config3=[,config4=],width=[,name=] ``` The MEMBAR is computed by logically ORing the result of membar_bits1 and membar_bits1 computation described below (PCICFG read + bit extraction and shift). The final MMIO register address = MEMBAR + offset. * width: register width in bits (16,32,64) * dev_id: Intel PCI device id where the membar address registers are located * membar_bits1: mmioBase register bits to compute membar (base address) - bits 0-15 : PCICFG register offset to read membar1 bits - bits 16-23: source position of membar bits in the PCICFG register - bits 24-31: number of bits - bits 32-39: destination bit position in the membar * membar_bits2: mmioBase register bits to compute membar (base address), can be zero if only membar_bits1 is sufficient for locating the register. - bits 0-15 : PCICFG register offset to read membar2 bits - bits 16-23: source position of membar bits in the PCICFG register - bits 24-31: number of bits - bits 32-39: destination bit position in the membar * offset: offset of the MMIO register relative to the membar * static_or_freerun: same syntax as for MSR registers Example (Icelake server iMC PMON MMIO register read): ``` mmio/config=0x3451,config1=0x22808,config2=1,config3=0x171D0000D0,config4=0x0c0b0000d8,width=64 ``` TPMI Registers: TPMI ([Topology Aware Register and PM Capsule Interface](https://github.com/intel/tpmi_power_management)) can be read with pcm-raw as follows: ``` tpmi/config=,config1=,config2=[,name=] ``` * tpmi_id: TPMI id * offset: offset of the register * static_or_freerun: same syntax as for MSR registers Example: ``` tpmi/config=0x2,config1=0x18,name=BHS_UFS_CONTROL ``` From: https://github.com/intel/tpmi_power_management/blob/main/UFS_TPMI_public_disclosure_FINAL_rev4.pdf -------------------------------------------------------------------------------- Collecting Events By Names From Event Lists (https://github.com/intel/perfmon/) -------------------------------------------------------------------------------- pcm-raw can also automatically lookup the events from the json event lists (https://github.com/intel/perfmon/) and translate them to raw encodings itself. To make this work you need to checkout PCM with simdjson submodule: * use git clone --recursive flag when cloning pcm repository, or * update submodule with command `git submodule update --init --recursive`, or * download simdjson library in the PCM source directory and recompile PCM: 1. change to PCM 'src/' directory 2. git clone https://github.com/simdjson/simdjson.git 3. re-compile pcm Example of usage (on Intel Xeon Scalable processor): ``` pcm-raw -tr -e INST_RETIRED.ANY -e CPU_CLK_UNHALTED.THREAD -e CPU_CLK_UNHALTED.REF_TSC -e LD_BLOCKS.STORE_FORWARD -e UNC_CHA_CLOCKTICKS -e UNC_M_CAS_COUNT.RD ``` or with event groups specified in event_file.txt (with event multiplexing): ``` pcm-raw -tr -el event_file.txt ``` where event_file.txt contains event groups separated by a semicolon: ``` # group 1 INST_RETIRED.ANY CPU_CLK_UNHALTED.REF_TSC CPU_CLK_UNHALTED.THREAD DTLB_LOAD_MISSES.STLB_HIT L1D_PEND_MISS.PENDING_CYCLES_ANY MEM_INST_RETIRED.LOCK_LOADS UOPS_EXECUTED.X87 UNC_CHA_DIR_LOOKUP.SNP UNC_CHA_DIR_LOOKUP.NO_SNP UNC_M_CAS_COUNT.RD UNC_M_CAS_COUNT.WR UNC_UPI_CLOCKTICKS UNC_UPI_TxL_FLITS.ALL_DATA UNC_UPI_TxL_FLITS.NON_DATA UNC_UPI_L1_POWER_CYCLES ; # group 2 INST_RETIRED.ANY CPU_CLK_UNHALTED.REF_TSC CPU_CLK_UNHALTED.THREAD OFFCORE_REQUESTS_BUFFER.SQ_FULL MEM_LOAD_L3_HIT_RETIRED.XSNP_HIT MEM_LOAD_L3_HIT_RETIRED.XSNP_HITM MEM_LOAD_L3_HIT_RETIRED.XSNP_MISS UNC_CHA_DIR_UPDATE.HA UNC_CHA_DIR_UPDATE.TOR UNC_M2M_DIRECTORY_UPDATE.ANY UNC_M_CAS_COUNT.RD UNC_M_CAS_COUNT.WR UNC_M_PRE_COUNT.PAGE_MISS UNC_UPI_TxL0P_POWER_CYCLES UNC_UPI_RxL0P_POWER_CYCLES UNC_UPI_RxL_FLITS.ALL_DATA UNC_UPI_RxL_FLITS.NON_DATA ; ``` Sample csv output (date,time,event_name,milliseconds_between_samples,TSC_cycles_between_samples,unit0_event_count,unit1_event_count,unit2_event_count,...): ``` 2021-09-27,00:07:40.507,UNC_CHA_DIR_LOOKUP.SNP,1000,2102078418,76,70,56,91,88,75,76,158,74,60,77,81,75,74,71,95,99,95,125,87,68,136,54,91,65,84,69,46,75,100,92,68,67,70,68,80,72,88,80,76,130,71,102,98,79,73,71,109 2021-09-27,00:07:40.507,UNC_CHA_DIR_LOOKUP.NO_SNP,1000,2102078418,1218,1280,1187,1310,1268,1287,1282,1331,1265,1267,1300,1270,1258,1307,1289,1300,1410,1378,1312,1316,1367,1337,1332,1317,1584,1519,1569,1557,1483,1537,1545,1520,1562,1527,1575,1540,1530,1581,1476,1525,1610,1680,1581,1657,1565,1613,1596,1600 2021-09-27,00:07:40.507,INST_RETIRED.ANY,1000,2102078418,705400,44587,45923,238392,53910,69547,46644,46172,44740,44732,45692,44864,46105,45352,45057,217052,46511,46671,46893,46459,53739,47021,114133,46339,61649,59027,142096,48048,98178,48288,162122,474329,48046,49795,78239,425635,105512,69933,49827,48913,71549,48451,294858,312316,149586,540477,49115,55144,46788,61681,82964,81127,116227,85776,453369,145979,81007,82269,83580,73595,73355,73751,72599,47169,47767,48191,48131,48359,48621,67664,48227,532184,49686,48704,324264,48539,48795,48609,60275,518368,116077,163734,526815,50650,140337,666605,47935,1368049,47243,337542,47153,46882,46925,62373,70186,466927 2021-09-27,00:07:40.507,CPU_CLK_UNHALTED.REF_TSC,1000,2102078418,3618636,384720,589092,2143512,766752,724164,803124,627312,541548,538188,534324,509964,535164,527436,529284,1366176,488124,491820,533148,543900,608580,577920,1145172,602196,919632,824544,1429344,692916,1092756,700644,1298640,2487156,736344,841344,1324008,1855476,1260084,1104768,658308,5805324,851424,766080,1909740,2170392,1313592,3986892,683844,986832,659064,642432,682668,772128,1076628,710220,2514876,1085112,715344,700812,676452,594468,577668,590856,574056,597996,525336,551460,548520,561624,569352,741468,623196,3124212,592032,596400,2265312,556584,593124,546756,766752,2547216,1047396,1280160,2704884,525336,1200444,3255000,497700,13643700,481572,1601040,515592,523740,503664,854280,603120,2305128 2021-09-27,00:07:40.507,CPU_CLK_UNHALTED.THREAD,1000,2102078418,1723000,183219,280560,1020631,365140,344897,382467,298699,257868,256243,254471,242757,254794,251172,252091,650377,232442,234209,253807,259024,289817,275179,545244,286717,437888,392646,680513,329759,520244,333662,618356,1184347,350594,400648,630580,1517122,599939,525847,313441,2765951,405441,364827,909395,1033366,625655,1898427,325614,881026,312798,305884,325245,367890,512845,338440,1197524,516836,341497,334581,322975,283138,275031,281300,273347,284616,250171,262581,261182,267455,271097,353013,296757,1487751,282516,283651,1076725,265489,282845,260889,365411,1212743,498705,611118,1287439,360493,571158,1549944,236616,6499483,229820,762766,245338,248648,239640,406676,287582,1714659 2021-09-27,00:07:40.507,DTLB_LOAD_MISSES.STLB_HIT,1000,2102078418,10093,1178,1186,2593,1184,1356,1182,1201,1187,1200,1191,1179,1189,1179,1177,1444,1218,1205,1158,1183,1216,1190,1789,1184,1388,1347,2207,1384,1566,1352,1541,3221,1374,1398,1580,11223,1690,1427,1398,1356,1531,1388,3429,3567,2136,2639,1354,1393,1181,1188,1457,1456,1801,1437,4698,1697,1426,1434,1418,1452,1396,1394,1434,1164,1349,1349,1356,1318,1354,1528,1349,18546,1168,1160,8935,1166,1172,1167,1194,4432,1801,2341,3152,1190,1777,4328,1178,4396,1170,1939,1199,1150,1158,1197,1187,12441 2021-09-27,00:07:40.507,L1D_PEND_MISS.PENDING_CYCLES_ANY,1000,2102078418,682630,81530,114229,363299,169260,134931,441644,183870,89947,95379,98135,81156,75366,77990,78734,178321,52738,53883,57241,56306,65514,94824,152070,227164,87723,80980,300491,70675,148506,70130,173723,628031,142178,161405,503099,383743,255465,317627,67134,1509172,105102,242908,300344,336683,157280,555052,84017,615357,526290,88531,117674,387708,192129,157226,451213,201430,103646,106302,112452,86251,83203,82880,80239,189044,72389,73820,75135,70746,84963,106517,168907,249006,117006,109389,320326,98291,168531,100734,206075,647276,167155,154684,495947,359092,257614,322235,78189,1473756,148139,278653,308380,343576,166510,556816,90475,306546 2021-09-27,00:07:40.507,MEM_INST_RETIRED.LOCK_LOADS,1000,2102078418,3462,231,235,1159,259,277,239,237,236,238,236,239,238,237,237,1114,237,237,238,237,265,237,555,237,277,278,542,237,431,240,389,906,239,238,385,3973,435,280,238,238,401,238,847,1238,604,1948,238,238,235,275,266,267,428,277,1287,399,271,277,272,239,240,239,239,237,237,237,237,237,238,347,237,4266,238,238,1174,238,238,238,270,1361,526,697,1101,238,615,2172,238,4276,236,642,236,237,236,275,299,2842 2021-09-27,00:07:40.507,UOPS_EXECUTED.X87,1000,2102078418,1152,12,13,496,46,17,27,12,11,13,10,14,11,27,12,1591,11,10,11,13,23,11,257,12,64,52,216,31,231,31,1668,5944,31,30,85,710,101,54,34,41,100,33,1852,1561,423,2348,28,46,14,23,155,57,82,172,2776,281,19,52,107,18,36,18,19,14,11,10,10,10,26,57,10,108,31,33,151,31,32,30,63,3700,361,509,4610,31,396,1814,31,5607,33,4175,31,30,32,47,78,471 2021-09-27,00:07:40.507,UNC_M_CAS_COUNT.RD,1000,2102078418,37565,33584,0,0,0,0,40306,0,37373,0,0,0 2021-09-27,00:07:40.507,UNC_M_CAS_COUNT.WR,1000,2102078418,58994,53007,0,0,0,0,25088,0,21901,0,0,0 2021-09-27,00:07:40.507,UNC_UPI_CLOCKTICKS,1000,2102078418,1300347171,1300351441,1200328715,1300297715,1300303139,1200283803 2021-09-27,00:07:40.507,UNC_UPI_TxL_FLITS.ALL_DATA,1000,2102078418,132768,150840,0,285147,269190,0 2021-09-27,00:07:40.507,UNC_UPI_TxL_FLITS.NON_DATA,1000,2102078418,298203,319302,0,293389,264875,0 2021-09-27,00:07:40.507,UNC_UPI_L1_POWER_CYCLES,1000,2102078418,0,0,1200328715,0,0,1200283803 ``` The unit can be logical core, memory channel, CHA, etc, depending on the event type. -------------------------------------------------------------------------------- Low-level access to Intel PMT telemetry data -------------------------------------------------------------------------------- pcm-raw can read raw telemetry data from Intel PMT (https://github.com/intel/Intel-PMT/). Syntax for a PMT raw telemetry counter: ``` pmt/config=,config1=,config2=,config3=[,name=] ``` The fields are the values for the counter from the Intel PMT aggregator XML: * uniqueid : Intel PMT Telemetry unique identifier * sampleID : sample ID of the counter * sampleType counter encoding: - 0 : Snapshot (last value reported in csv) - non-zero : Counter (delta to last value reported in csv) * lsb : lsb field * msb : msb field Example for https://github.com/intel/Intel-PMT/blob/868049006ad2770a75e5fc7526fd0c4b22438e27/xml/SPR/OOBMSM/CORE/spr_aggregator.xml#L15428: ``` pmt/config=0x87b6fef1,config1=770,config2=0,config3=32,config4=63,name="Temperature_histogram_range_5_(50.5-57.5C)_counter_for_core_0" ``` Current limitations: this feature (PMT access) is currently only available on Linux (with Intel PMT Linux driver). pcm-202502/doc/STARS.md000066400000000000000000000002471475730356400143710ustar00rootroot00000000000000## Git Hub Star History for PCM Project [![Star History Chart](https://api.star-history.com/svg?repos=intel/pcm&type=Date)](https://star-history.com/#intel/pcm&Date) pcm-202502/doc/WINDOWS_HOWTO.md000066400000000000000000000144471475730356400156160ustar00rootroot00000000000000**COMPILATION AND INSTALLATION OF UTILITIES** _For support of systems with more than _**_64_**_ logical cores you need to compile all binaries below in “_**_x64_**_” platform mode (not the default “_**_Win32_**_” mode)._ ## Command-line utility 1. Follow [Compile the windows MSR driver](#compile-the-windows-msr-driver) to compile the windows MSR driver (msr.sys). For Windows 7 and later versions you have to sign the msr.sys driver additionally ([http://msdn.microsoft.com/en-us/library/ms537361(VS.85).aspx](http://msdn.microsoft.com/en-us/library/ms537361(VS.85).aspx)). To enable loading test signed drivers on the system: in administrator cmd console run `bcdedit /set testsigning on` and reboot. 2. Build the *pcm.exe* utility: ``` cmake -B build cmake --build build --config Release --parallel ``` alternatively you can perform `cmake -B build`, open *PCM.sln* form *build* folder in and build required project in Visual Studio. .exe and .dll files will be located in *build\bin\Release* folder 3. As Administrator create PCM directory in Windows "Program Files" directory (e.g. `C:\Program Files (x86)\PCM\`) 4. As Administrator copy the msr.sys driver and pcm.exe into the PCM directory 5. Run pcm.exe utility from the PCM directory as Administrator For Windows 7+ and Windows Server 2008+ R2 the PCM utilities need to be run as administrator: Alternatively you can achieve the same using the “Properties” Windows menu of the executable (“Privilege level” setting in the “Compatibility” tab): Right mouse click -> Properties -> Compatibility -> Privilege level -> Set “Run this program as an administrator”. ![Screenshot](run-as-administrator.png) If you are getting the error `Starting MSR service failed with error 3 The system cannot find the path specified.` try to uninstall the driver by running `pcm --uninstallDriver` and optionally reboot the system. ## Graphical Perfmon front end 1. Follow [Compile the windows MSR driver](#compile-the-windows-msr-driver) to compile the windows MSR driver (msr.sys). For Windows 7 and later versions you have to sign the msr.sys driver additionally ([http://msdn.microsoft.com/en-us/library/ms537361(VS.85).aspx](http://msdn.microsoft.com/en-us/library/ms537361(VS.85).aspx)). 2. Copy msr.sys into the c:\windows\system32 directory 3. Build pcm-lib.dll using Microsoft Visual Studio or cmake 4. Build 'PCM-Service.exe' using Microsoft Visual Studio or cmake 5. Copy PCM-Service.exe, PCM-Service.exe.config, and pcm-lib.dll files into the PCM sub-directory in Windows "Program Files" directory (see above) The config file enables support for legacy security policy. Without this configuration switch, you will get an exception like this: Unhandled Exception: System.NotSupportedException: This method implicitly uses CAS policy, which has been obsoleted by the .NET Framework. 6. With administrator rights execute '"PCM-Service.exe" -Install' from this directory 7. With administrator rights execute 'net start pcmservice' 8. Start perfmon and find new PCM\* counters If you do not want or cannot compile the msr.sys driver you might use a third-party open source WinRing0 driver instead. Instructions: 1. Download the free RealTemp utility package from [http://www.techpowerup.com/realtemp/](http://www.techpowerup.com/realtemp/) or any other free utility that uses the open-source WinRing0 driver (like OpenHardwareMonitor [http://code.google.com/p/open-hardware-monitor/downloads/list](http://code.google.com/p/open-hardware-monitor/downloads/list)). 2. Copy WinRing0.dll, WinRing0.sys, WinRing0x64.dll, WinRing0x64.sys files from there into the PCM.exe binary location, into the PCM-Service.exe location and into c:\windows\system32 3. Run the PCM.exe tool and/or go to step 6 (perfmon utility). ## Compile the windows MSR driver 1. Download Visual Studio ([Visual Studio download](https://visualstudio.microsoft.com/downloads/)). When you install Visual Studio, also install related packages: - under `Workloads`, select `Desktop development with C++` (select `C++ CMake tools for Windows` in this workload) and `.NET desktop development`. - under `Individual components`, search `Libs for Spectre` and select `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs(Latest)`. 2. Download and install SDK ([windows-sdk](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/)) 3. Download and install WDK ([windows-wdk](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk)). Please select `Install Windows Driver Kit Visual Studio extension` when you install WDK. 4. Open **a terminal in Visual Studio**, then go to `src\WinMSRDriver`, and run the following command to compile the driver. Then you will see `MSR.sys` in `src\WinMSRDriver\x64\Release`. ``` MSBuild.exe MSR.vcxproj -property:Configuration=Release -property:Platform=x64 ``` ## Known limitations Running PCM.exe under Cygwin shell is possible, but due to incompatibilities of signals/events handling between Windows and Cygwin, the PCM may not cleanup PMU configuration after Ctrl+C or Ctrl+Break pressed. The subsequent run of PCM will require to do PMU configuration reset, so adding -r command line option to PCM will be required. PCM-Service FAQ: Q: Help my service won't start, what can I do to diagnose the problem? A: Please check in the Windows Application "Event Viewer" under "Windows Logs" and then under "Application". PCM-Service writes its messages here, just look for errors. If you can't figure it out how to fix it, create a bug report and make sure to paste the text from the Event Viewer in the bug report so we can diagnose the issue. Q: I see a message in the Events Viewer that PCM-Service does not start because the "custom counter file view is out of memory", how do I fix this? A: Despite that PCM-Service is reserving more memory than the standard 512kB this error can still occur if there is another application that uses performance counters is initialized before PCM. There are two options: 1. identify the application or service that starts before PCM-Service and stop or disable it and consequently reboot and try again 2. [find your machine.config file](https://stackoverflow.com/questions/2325473/where-is-machine-config) and add ` ` to that file pcm-202502/doc/generate_summary_readme.md000066400000000000000000000005221475730356400203550ustar00rootroot00000000000000Step 1 ```pip3 install pipreqs``` Step 2 ```cd /path/to/pcm/scripts``` Step 3 ```python3 -m pipreqs.pipreqs --encoding utf-8 .``` Step 4 ```pip3 install -r requirements.txt``` Step 5 ```python3 generate_summary.py --help``` Step 6 ```python3 generate_summary.py -{argument1} -{argument2} ....-{argumentN}``` pcm-202502/doc/license.txt000066400000000000000000000030561475730356400153370ustar00rootroot00000000000000This notice belongs to the code originating from "Intel Performance Counter Monitor". Copyright (c) 2009-2016, Intel Corporation All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pcm-202502/doc/run-as-administrator.png000066400000000000000000000336311475730356400177470ustar00rootroot00000000000000‰PNG  IHDRGˆøeosRGBŽÎé pHYsÃÃĮo¨d7>IDATx^í}€UŊ÷”;sgn/ÛK6=ģé !H0ô**_āáDTåņQy”T‚DJ€J‰@€P B€ôF ŠģŲ~{›ūũΜģ7›Í&ŲÍ&”Į ‡Ų™šgΜų÷vØ--&co&K˙ĪęēÁ;xËb°k†! ŧ,3™,Ŋz6 ũˇ/ũ ˜€†Á§$pÃZœũB¤åY†ã8Į¸\ė?_~}ƌ3L“‘FÂĨĪé­3™Œ,ËÉdŌįķŅ!Yļ â7ļˆĖ˙E/ãO– †Ž(Ē ^/‹?3‹aķ3w)âĀ@fī ķ[‑Į‹‚ĮÚčįyÆÔa~ŋ8˙ĨWfœw6Į2"Ī8??ĐuŨáp†ą™Ļ UÅéĈȯŽodķčAہcMĶp:yŽÜãįä÷8/‡‹8đ…ã „PCĀoCe,ׅēĻZ–QZęúįK¯sMdq•ˇ íī ņ9€1p=ĨŨza×4[dč6&ŗ"kšžÍĻ=.—$‰ĻnoĐĮąqŪ/< \h€= | Å8ÁēØKbXEɲŒY^zcᛧvŠ$ĐÕí.nô”x”Ķ~ yŊŪ<XĩÛ´Q™t\˛ĐŅÚĘa؆piEhd‘[ņ"jmu`@= | ÅĮ P’üĮŲúkrTZVÕäŸp8¸|ųŌ‰“Æģ%‰ą žĩÁį3ß°år9Q! ŲBšŸl]ģŧɰĄŸČú`U@ƒÍ×'âQ¯[64ÕĐ5§CČeĶ~$ō”÷ °eĀWG?Ią‡Ė@|(ØČ ´Ō2N–ÄÖÖæP8(ËNhédę3‡ō@ŒG’$M͍Dƒúņôé́ y>°¤QÃDp°:ÚÎÖfˇėS_=Æ4 âäÅ=ã0I0ā˜.`ÅöK=y€î"íT,i#ˆ0 åÁFĶ5Á!áás˛& ˇÛM9ÆĐØØ¸nŨēĶN;­Ā ؏)ôãsœfϞöãōphŌÄC5Rą¨ėręĒ á)ghũÁ‚üŲSc^Ã0“Č@:!Ŧ@xĻeP=„ˆ•”ˆôwf2@™¸?ŗņŋuœö÷ę˛ ‘Oh1:3.—+•J†‚ŪææfQt€úJ’Ģŋ_÷°Ü‘Ā „Qa  ũ|đÁšįž[蜃oXŧID:đއėīų –ŊØyŪpģ|­\6åāYJƒvHāôšlŠT !Į“Íf ĘąĒĒ 8Ģ’°ŅĀå?QqŖ°ĩŌŌãƒˇ “N§++Jë ZŅHģÃåŽļĩęēĻ̚Ĩ™éxZ!2ļųŧn@ŋėĶŠT8’%)™HĀ4›I§ĸXUYžˆĮsŲ,Îē‹F<ŸÍd>†žŒŗxĪ@f€Čŗ{í]Æĸ‚Õ谐ōu…˜Š=P ÄÛú1ėBhģpĀŌ-F7M¸ÄLô Ē@‡ˆ¨9ļ‡ *ũļė–—–ĨSŠ$ģŖņ˜(°CjĢÛ›DŲåwKē’Q5Íö&QgG›šš€¯UU•h[[ˆŦ tô‘H d¤˛˛‚ē6Ā”JKÃÔeQ܊3pĐQh 8ļ°}ûv ‰œ1ÔŦĻdt5ĢĢ9UË芕 Ëgfc3kÚÆāūnĻĒ*č§3kš¤aeũá–wÆ:[ÜNNtģ[Ûâ’(¸$> 1œsđ‰D­ĢĢŨž}'ĀrOh3ÍÍ-1pœĒĩĩLĻŋ*Ū˙՜ā@>ØącZ*qŸ;ä÷„|nŋO‚hî¸ķėŒhĢ48‚뇀đ3@w{ŊĮęšT6ģíŽģ†ÖšūĩŖÎŸņõ›6Éŋe(ŒŠŠNGKk  (vÆb ;@ÃKÂNl 2ā Žãņ8NííĢųE‹oŨßĖI’‰b!A3F›—ŽvīÜÖ´k[#ŲˇÛÛa ‚,ß6×äYA2ôuĐ”lƨD:“Íå**ËīžëŽ?ŨsĪGËWlŪ˛Á+ įįÍb€u^ŋ×ãqU”W„B!Œ5×jēŨ°é1 Áž 4đ“;=ĄČúú%žō÷uWÄiœĀöcØÁr‹×īÆysXF‡â€ļq׎˛pîŊl:eę*´{Š=Ä|ڛų’š]éTīą[ ;ž@I,­ųŧ|ķÖ÷žyΌoœã 7ü\RļļˇuŒœ4ãŋ>Ē%ÖīÜą!–6ˇnoúŋ˙uÍ9įœhÖŦY›7ož6mÚĪūķ÷Ū{ī7Ū@Ü_4=účŖoŧņF0 <‡üWūû'āā3@ƒäĐBĖ1,=účŖeee'N:t(ŦĨÜø†Ęqõ•ãëĢĮÕ×Á/6Ļ~"ŒˆßļrvYîÅĮMœsē"ˆR, …ˆCœđ°žP;m×ŨʕËgßõøŒį@ōųÅ/~ąmÛĀ}$šëŽģ^{íĩŸüä'ģvízøá‡;î¸úúúGydÆ ô}€ ûâŰéržŪ“7B¯ŅB´Ļ1D€¸RxÚÖĒV˛ËG™Ú?<”`' $Xdád€Ž›É䠅™LUa‚4VSSƒž'OŽ;餓.žøâO?ũtņâÅ/ŊôŌ’%Kf˘ÂdĨH ?~<<|@ˆC%%ĄBākņ+gāĀ3@an’AȎĩŖēąúmYˆDÎŲ[Û”Ī8€Č. å$eĨ'ŸrĘk¯.° “á-]V[S3hĐ ¸NâÉd*•’@ëÂ\qō;wîÜ~ë­ˇâOˆ=č,,wu.×ÜÜJƒ`‹[q>īŗáž‹ Đ(Û –O‰ h@÷~Å2Xœ×ë‡árJÉxÂát˙ņÎŲCÕ~įܧžvæc?u÷×PáāÅöÎøŲgŸũ‹oüáxúé§Īœ9ķ÷ŋ˙ũЧž>~üÄW^yžĐ~(Á@b ŽŊ^øāĖĀ_žØCqˆŽk™Ũd}›Ü¯_ŋrÄ"‰´d‹L{éģ{Ī\¯:1XŒS!z9x§nĻ“Éíáp‰Ļ•lÚ´9“ÜYYY]Q5.›1~tŲ9+V}0ī™ŋ3ŦcpŨđ`0Œ¤…+VAŽ‚t˘18€ ,@ą ŦÚpĀŽ‚:îTüę_ĩ ŌŽFų"44ÉĮ{Ŧĸĸb„ Æ ƒhŊw˜Đöü=顔üB4Ŗé0bŠ‘ŽK7J*jšZ;ôlĒĻŦ|âŅĶĘdžp&ôņ&ãđK'M<ę¨I“+++!ņĮãɑ#GÖÖ֎7ä  .ŧååå@€>”E0•~Õžeņ}Đ @Čąã! {/ĪŲ“.ŨŸApííHZy}ĀŧŨ­‘ŌŌj¤g†ËÃņ”â<ŠhōÖégžuú™ßRUÔÎöŽaÆ‚Ģ€˙âqŦĨJ0T C7hoo‡>U2RSŧˇ8ûēoųôPržpƒ=Ę@ŋ'qĐāA™´Ēk ‡XNT4+—Ig:;’{gSk¨Âo‰]ôŨKn˙ũˆˆcƒÖžmÛ6H>Æ ũâĢĒ*ĐÂkF}{¸d{0~¨øƒâ ô6H~ë!ŨãOęĻ4rîPļX„î B…’IQs9ÅWVĻ:<Аp"­N ƒŲÜŌZ^^™H¤XžÛŊģe„q™TÚ-ģ|/|Æ;vėOHg2đ'ĀO ë*8ĐsP‡8m“_+™ŒWÕÖFb1Ņʝn‹D‘M‰¨4R 5šˆ|^P}Č6íŽyÛŅJyM<™NdŨ^IuĶSĶõ\ČãņøÚĸ‘PČ1Iā<؆ǁųĀ hj,™€į›Įđ;kŨõ:Ũ”~¸áāπށ_áˇđQā6Ø hâFQ J9Ŧ ‘NˇÉPTy*n˙Kf bĀĄŧ-€&\R˛kįN@B”1xđ`RŽH×w75Á°C-ž4Uí~_(€NüVQĀ+TdŲåB  đ’L%ãqÔĄFėpž *n[Ĉ:AÄ:ęú°Ŧp ˇė”H5%Ķ"a§AøëhpME…{Î.ŌDŊęā#…ŠŖÎ“âöe#øņˆ_Ú˛”K1F Mp )ŗ*KcĄd€TšÍĐë†Kø9ŖÆ@!¤•A?†…E î4g-=Ā4ĐhK@dˡ4Ü>ƒÜ9 twō,@wp<  ŸNĄN…¤ŗ3*8œp{\Ę{ mVggŸÚŊÅĄ"ė3€KX0A>ÄV=ĩęîĢĒĢĐ ë°ũ“ Ęņ~Ĩ €8…rlÔwæ ‡KËËŠã péEž$á͊Ķ_Qža”‚Dž˛5"eu;zčüU  °Zd_;X9œkŸĪOęT&˛v>8įv{=n_—’]@€‚:õe‚¯ø¸  Âūŗ{÷n=h*(+ĸâūüį?ÃK÷ԓOBndiDÍfčuÃAĄß$י%ųŒ(núäÄ–ļ´´<ķĖ3ĀĢĨ‹?øpŅ"€;•éqIÜt‹ŸØ1í„nÛˆÖŖ%D8(PR’#L€”äųD4ģĢ;įĪ˙'8@6Ģč:Ęæˆ]ƒ¤€Ē-Gpŋâúŧūüx4}“Š+` ÖŋųÍo h~ë­ˇž{î9€)HAh Ųû{[€>ĸ>Üšmíڛnē 9čŽe€,ĸMįΟp§ú6p Õh6oŪ<ܜWöŅ` ü’ÂFL fYč7n\°āõdB…üe#ėj‘ÃŲMĸƒ. BŸ¨ąGAh iâ0äláŠyķfŪ|3RanŋũvtUU!´TIču€ĀŖĨŠēE?ofĪžŠ[n™‰§āčā ¸R áķī2",0‡4°–Ęc’Û \‚Ģná…čdĘQã.ŊôR¨Ų^=ŠTîá6ké„#öĄŠąčhÎ-L×}ëī@°ŠŧJŅ˙wŪšđ Q"ĨŊ­mȐ!H [ģvíƒ>øã˙øk_ûÚ¨QŖ^}õUúʕ+9æ˜SN9eÁ‚ ÁĮ{ė~ôŖēē:´4!ϟË/ŋ|͚5gœq¤ŦiĶŽ‡Ö" ÆįΝ;yōdtžeË0(ĻqōÉ'1é K—.EįĨ­6vėØŋüå/čüškŽAųąc§NE &„mƒą\tŅE[ˇ6O™2X΀<ž††ä6⇍U†z}ß˙ū÷qæˇŋũ-ąâöœƒÂíä4a‡Š%ĨĨ€o8d\Ȃ˛žûîģ˖-ė>ņİÄ_pÁW_}5’*Ą9ĐœĢŽē ĄŦĸh NgGĮķ¯ŧ2iŌ$Hų5•åeĀ7z¸ãp˜n¸ ųĢ_ũ*äáÎĶėi¤h~ûÛßnjmŊūúëqĖįģßũ.ž lšiæM0[!fû?øÕĒĄl|į;ßyîš§3Ų˜XÚO¯ģæé§ŸŪēuËŠ§ž á Ú<0pÖŦ[€0Ī<3Čr‡ęP?čG*ŪpDgāâDĀ}ii)@äÄp ” ŅĒz_RR‚8Ā+Z˜Œ@ė!{šˇnŨ ¨%L#—üAō C4ĸÛx[(1t čŅÔläGâ%­‘}8žä’KšZšpŗ×Keô¯›Æ¨úz¤ČM?áø¯OŸžbåʎHįaCc‰xg{dķ–-ßûū%Ûvl÷>ŋã„quįŽFôO#—€`H|C:t0$¯T:ņ>ŧ˛˛<\ėč(ÆđQ@=‚A€2 8H]wŨu÷ÜsN‚6#Q"bĄA>W­Z Fĸpkk+õFxâ‰ŋūŨ,d“Q̰…ī¨3a§efų hpo^ĄjR¤Ąû"dūüķÎ¯Ž¨ĒĀ÷,Bį¸įíE˙ÂĶ˙ūÂķ°VGEՆ†—.Áđ`úĮ p@KÁ$(>“ÎĨ!䗗—ĻĶÉ`0P^Qęt īŋ˙o´‹-PK%aZ[[0ÔĶ<€m÷~ŊbׇcŽ 4¸ ÜĐ_k‰b7DĐN\ēīžûāÎ耲üņŋüå/!UC D^ ŸuÖYP$ž9ĸņ(ũ‚Ņ'ĩ´â|,žÜãčú1nÆĪO8á_tņEąd gtŨ‚“ pû˙Â_U[ķĪ—_~üÉ'ŊwĪŊšęę̇ vëmˇ7A{SĻN ƒõcFŸr꩟nŨ|Ųe—ĸp,*ČÂr´hŅ;7ßüĢ!CëP æ”SNŌuåî€á0ØŲĮį(öņ9ĖĀž˛(…‡Cۃ e‘æ+ŌÜûlûË#C˜YyEEG{; ôž†-v!ĶŖøŨēĩkA†!a¯Xą ¨ä’ÛÚIŧtyIX–ądŖd˛Njũ‘D< e•4ôdFvˆ.ž×Q5R”šleĀÄ‚ŖjąhÂK͆.-…7Auz–Øûũn_,ÍŠI×w/¤˛ææv ;,1 Jŗ °9E…„ƒ×Ô4%ČėKžtdHaíˆ™ĢŽĒV5UDŨĐáBļ+\0pœ)9 5‰IŊĘâöś|čūä‘Ö‡€.ôp„Ad!‡ÂD [Ŗ˜(„ /á˜Ød›ŽÖöŽŨ- íQHŲmmxŽHMė9§ĄGÔ脞Lƒí›ļ$bąĸŦíl›Í[ˆë=ã景I "0Ō{gô>Í4"ģ'ŨļŖötF#¨;LBĩeÂg }eŗ9T83iljĖdŌ-­ÍíímЀĄŊ`~3™T"ŗ#íŠÛ—rX Õۈ˛IԊí•1˜U6&"í'3A‹ĮáuĩxĢāÄįQ3…„ČsX\EUđé@#‰îh:u˙“ķĘ*Ģό7rčPUÖB. ž™ĮTą€ĻåĀĸīŦÛčPŖ'Ž2™ãĖX˛cĖ ~p?Šā`üãDŠjĨƒ‰ģ,1 w†ÎĢe¤"Ŋ€„€9` Tû%KŊĸø5ĸÍĄ^Z\]ČĐšHZ°‰*ĸˆÃĄĸD¸!¨ŊvL[׋nā0G˙¤e`hƃH &™fDÉ~´Ā` N”Ö†éžŲÁP¯ņt\ÂØRƖ‚0KųUiqųĘDˇåĀ=i˜ŊNc¯8€rųķ=×2DB1û0ąIŧœøjvÕtS`4ŦČ1ˆ@IX;PēV‰ĨozōŠ’Au§Œ7ĻÚp @ŅM/“ã—*Ī˙kŨ–2>=ļ} ĶŌb°5MH‹&ī––×ÚSĩr) Ļg–éąRÉįį%<‚“ŊĒĨqiŋÃDuÆËfYŦ)€āԗŗå"“‘@R )4W´W!&ú"‡#‹ŌôR*b& VtRHĘŖ ĸ?íH!“ŦbÂ(Č"5&› †|ŠTáqĐFāÁ†œ„u nŠ6^€4e BĀŋ€īĐדt]ō}čú~ĪėŠ×˙Žųå‰ēU(ëķų@ÖÃ_ņnl¨U3 ž>!t5AVP™ ōk9´dē`įá|ŪãNüŨĶĪ•U'M˜Ø0„ Eö°V  kÍč˧šÂŅæĩˇ]]ąŠ 4tę‚.ķĒ"¯@Ÿu%4SS8—V#ånC2Q/+z4bđ ›ĶÍŦŽ•wd9`ȊX9C’¨ŋ ŌđAŅ5@$,6Dĸ°ąĒ0–Ā9d˜õĸĶrÁ=7VįxĐ]ĘøöĘü„Ö‹jĒmmHÁOíÄ5Ģt$;—Sā°˙õšĸō‚Ã^uS@%zŒķhą›Ė ]‹īĸ’†v°ļįæųāŧ}Œ vˇHŲķ9Å€°p–S ‘5 @8&†Í2N)ŧ.{xˈZ؜lJį˙uî‹/ŋj 4ję„ē¤ļ ¤h|Z=kį“I؍+ˇŒrō[g^HgB?ųã 3•aF‹3V‚‘Ā_ė„/+Ëdĸ >"œĒ|9c!–S#덁i*ŖbD&PÆD“„=IN"Á˙ 9D°Ø7“õ‹ļoéĸôĀHö]ŗŊö˛~ĸÁp*Q< 8§ą&ŖäÉ,ƒ"_'“@eRˆ=xĩ đ)“!7@*#Ն9xˆP„?ĘÚáΜĘ(čú]4ã´Ā7 8†ØōË^؜ēOØÕ üj߃OIdN¤\ˆ=…T™|#ÃXi&ÛžķÕW–ŋõҌ§˙žā™įr5UŖĻMĒiã@sĶn‹ĶsĸŸ9ÁBLįęõێ/ lœu—ŒMŧ˙IōųjJēĀÄS–%'ãi&—ôUÖ0 ËøƒD$‡¨­ ĸā(-¤›Œ!ũĄÎ(ĩ‹ %íčĸ°L0@ŽS)v€9ß° ‘ũŪdtŌ›ŽI:ĖKę{'|ĸ+—‹hŠ,#ÉD€nM1>ä`lAŒ ŗ†§c…ė5xžxû`†ōúƒôŪ^tâ=ú@÷ˇØĶ5ÕiŠíĀf€€™ŧF`lúLÉv2˛ņūũsŪyOũãÁ˙)Ē™8Ž~ø0* A.ætÍÖ(¤ž_ša[ƒĄ¯ŸyUŦu÷Ųoü+qÉãH"w%Ķyáûk !Ļ–‘Š“ĪĖiœÃKÖŪtXTgBĶuØÍ‰‚ÛåöC`1dKĘÄ3@í¤H‚‡8„duvĸÚ(Ä!âį"2Ö=ā ÆwoQģĮû*Æęč˛ĢЍ”m[Lˇ$G⠟ÛKōô‰LH?ˆ&kf‰Ęé°ėŦ:ÔėC˛d’IŒ įsifP’Mq ÂØcßÖcļAˏ'֐qÚK÷RëGˇ ,]í€Ņ’7( EÔā55-‡S“ĐR@Tĸ-N?˙ę=ŗ?üĮÂY>=û…—œ5US'ŒI<ž¨3g6ĸ:wÕÜEÉ%’sĻ ĮhnéÃO6,úxí‡Û:–7fš_‡büąxËÂÖmo/~ëŲmŸŽ…Ž 5”$͒T^2.ÆJ,JËCÎWdX^Uâ†üīq–”…4SAhŗÅꊑĨ-vIí‚É‹&ũģ/­Āp(-88ÁAZ¨šČ1Ā1r\xb腀/É úB"ôōššÅljQK)>Đn†øŌtÄpĻĨ-+gø]^ˇ[S%jēŊDg_Z{%OrgŊĮĪ隟d'ÆôG^ļ¸Ž`œ<#ņDæ>Ėėĸƒ˜0ĩdÜR3Œ–‘ĩœW‡ÆŦ†ôŦ_SĄüQä# $)đ?, å"ǍšRÎ(ĩĐéd˛¨§EÚ;MÖĩ=Á>øü;RŲ°UëV›™§3sM¯ŠÍmZķnKĶÎ,č§Î&5ÁĘ JÎ?Š 8Ą@°­Ĩõwŗf;vø°Ą×˙ô§Ÿ|˛i°WâGAŸßįö$cQËÔ Sęc ú×ŧŗ–ŌÕĒĻ•'ÉÉÁ“õæ›ī<ô×{QÕÔ!XpDd”(ë0˛zZ52Y-ũūĸE?ēōŌt.Έ&ΧԄnårzæ_ožų“+/CΌŠôąE! ÆĘą°0›*V—ĸ-vËĨöjņ'Ųmŋō0ŌøË;$Ė&{Į—Ŗ­Ž? ]r9ą~°ėāX(ĢĻîÔŅŌ°&w@yĀ!Ø˛Ŋú—Tĸ¯,ë¨ŅLL+5\úŽh™T9˙­Õ'œ÷ÃÆöx6Ū4$ĢėYiļŋV_˛ÁŌöɜÅÂå„D„úĻ–“—Ŧ&2%•Ģ,¯Ü´æ“Ų¸kۖ­Ųdö•—^6Ŋ,‚NåĪéaoØ-zŧN”’hdg5KMå,Åp r^]OWõŒę—Ŋö1$Ëបt[ĀÃ9ų\ĀÃܜČå\NĮƒĒ›Ųēi•ĨÅün&vĘdÆŖ[.äh*OԊu„šĪ?â0ØYˇ!…š3+øØqĮ6<õG,6á–TŸÛ@ë÷˜įžsŌō%oŊŒ–k yY뒐Å%đLÂ+›ˇĖŧū…įãĖ$ŖĮE.ë÷ņ~Ë[)C‰¸DÍ%é/'ō9]‰hšN ŽGŨ„ōVÜË XąÎdYÎŪŲ v*G"!Ëéĸ@ܡ(ģƒlAđâ%0-cO` ?ķ–[ ŗ­Īų˜Ou¤ƒ9M{û ”ođ}˙Ä'ŧģøũĘáãVo˜­Dwo(›Ļ ×ôíkKÕīnS Wf…*JG€ū#”ž(=*ä˙°2qĻE~DŪĩpá[CëFŒn¨ßŧq;âpÎ8ũ”W^y3ā [–~´Ōī Ŧ^õ1 ĨĖ}ä‰uë>Š*̆]ĩŧŦūļÆģgĪūËâw?LgrX?gņâĨ˙mîŌeˇ ‚¸’PÉîæŨĪ=ķėÜ'æ")~đā!N‡°đ͡˙ūėߛšZvlŨyá…ß{é…Q¤č‡˙ZSWģģĨũąš75ˇŽ; ^[0zė„…o.llnÅųD* Q¨¤´âŠyO5zķĻ-üí‘>ø+…?˙ü‹ÉDR–]Ēĸžŋøƒˇßz{ôč1÷ūųŪ^|1 ŖĀŅ#>ŧjÅ択Šņ“ŽZūá’'æ>ąjÅĒŅ ŖŊnīĢ ^[ą|Å'7••–ŖĻW ŒĮSūAēJqđ XĸÁ &hkę<§"z9ØÛá—bXŪÔÎÜąlŲîuŸ;ã?ׯYĮzüĨ550É0}÷ éhUXkœ–Âkq'ͯ̌GLJl‡Ž-^߸]ûAĮē—Zš7ÆŨĮ7ųŋ÷adr“ëØŌYĐr˜Váec$E4Ū4PĨ‡Ô‚ŠÁiimKÍ™ķø…]’{éĨWmߖ¸üōkq)p_pÁĨ•xûíĪ;īâöŽĖŗĪÎŋīūĮĘËKļniO&áđVT qJĄûî{$1~øŠ@°Š´ ”pl°Ŋ]K%‘]9xęŅg\ŨīŌ)ņÉ'^ũŨoīĶ4īûī­wšĒ°ėĶ5Wßōû[ØŌKūãĘĮæüŨHĘw˚ķĘs‹â-Æĩ—˙ íŦ›îēuæ=>GÕÍ?ûÃúe;×/ßųģ™÷(ųîģĶOEyôĶĀ*š˛ŌQO<ūō}÷ÝUą([]9ö˜ŠgŪøŗÛSą[S5:•ā>|÷ã™3īLÄŲåËļ\sͯ9Î3ûîGįĪžČ˛ŠZ– ĻĶXą3hš˛iÂNPÜË ˆŦNV“d ‰Õ%Nw‚ūû¨0âwt‰1d^“āębt ÖFŧ,„Tvp´62ƒ¨’IAÄp°X9Ęd&§TV՘ÉčŲŖË˙īi#īûŸ˙ŧčģgß4û͛įļŪųrâšeŲĶ-z]¨=ԃÛOĩe+a›°Ģ$sŠ5—Ÿ)­,m?zԘQ6o¨Ē ‰.gu]M"“Œ$ĖQcęÔ!û\S§{Į]ŋšę§×nŪē9•cœ.§' W֔,YąüĄŋ=ÔéĖ(™•k>NeSŋüõ‘xÚí“T¯¨ܸģķŽ{ØąĢ­=’Ū°qû˙}ËO¯˙ų…_Ö֑„´ēvøĨ—]ųß7ūÔ#ĻM9ūfŪtÜŅ'4mkר!cœŦģĻlđĨ˙øš+ŽūÆŠßnŨÕÉÎÉžņëŊ– Ņđ­˙sūŲįœUR^ķã+¯i;"­'ŸöoÍ8_ŧī.^rûī^ˇņĶąã'L=öÄcĻxÁ÷Îũ÷ËÆMš:ëļ_˙eΜÎhēŊSá÷÷~øãožķÍÎ#¸DÅäT“Õā1„ %Ū”b;¤Qŗ&đ€ŗM1qZÄeå€Åâ>ų–kb‹Cõ[čŌôT(XéH"‹Õ; ¯ÛèĻ3ŠOTŸųtÃIC™Š5mRúũŠrž•ÕšĄÕ}7]r|ūįķđnZ.KT^ÕøŒ)e-ÉĐIέ"R§3ģ{úiGßxËĩĄj÷ ^”‚ā9 …Ķä:3MY+šŌ#CĮTÅĩŒ%eSfgBëä2xëøĶNÜžģƒw =Ģ –Č›Rņą§įĘ~÷[īŊ=íįw$“§ĩÆZá-dQë>lIkj¸ēD 8;3‘ōA傋IĒIŒGeņ’L‘Ƨ5GēØ|t>eą†MÁåÉąq´ŧ•ä‰â ŋo†‡{×J÷p)“K\f@RÁn‹cøžÄwOŠ88% †qؖ AčĖāŠÚtĸŗŧšßēcņøá‘]ģBŪēAĩS–-Ų0ũk'•¸ËžmšŌšœÉE…īš&0}ĀëFęE§1RĶͰĸ6ŒņäSs“Šh}ũČ~vííwÜžuûæt&‰{° ™=2ÉĀDDŖV•U44Œzøá‡zčȜMßqį/~oúôã"‘Žp‰'™ĘÕ ŽŲąsë}÷ŨģhŅ[ājgœqڟîúãŦßūáw܆ŠpOĮbˆv"fréx*’ŠbqÚ,Ē^ —HÅŨ>'ŠcoØ´S°ŖqkIYØádcņNTąxņĨŦ]ˇzøˆ!ĸ“÷xåģgßųáŌÕē 5žT‘[ŋa͜îû÷{˙ÂÉp‰oËæõO=ųÂÄIc>^ŗōÖÛîŧâŋ.TWéõ0™l|ãÆõí‘TIŠՄኃjY$” ŗØ|āãÔY” DiASŠPMøh\Ę)­!m€FC]åõ¸„[,OöųÛs Žá B Ãöå6ˇE}X/ūƒE9M­<ī‚Âi<î„Ũ™Ū¤JJ´=*3ĩkWkj[ôüFxێT[áö‚Öf˛!;PŠ0ŖĄœNƒS’95ƒúmĸKt<ĄIc'×UÕ¯Y^ZĨfôĶN>#ËøÜË~pų؆ņ ÃĮ Ē\W98( yÂõ#Į\ūĻÆÖáƒF¸e6žģđ?ŋ7ŦvxëîˆĩÕuß:ķ,öāBA_ĐëNÄŖ?ģöÚ˛ŌāˆÁƒ+Pä=“ųæŲߘ2qbũČ!@¤O8nĒ`Y`ô„1Ō`¨<ŽûKüõãęĢ×Ô­;iŠB–×Tā’ŋ$0ᨉeåáų/ūŖ˛Ŧäø¯O›0vôØŅõĀĶ0jü˜†ēÚęáCęÕTšJÃÁīœ7cÔ°!“FnmjDš˙I'N¯1ŦĨšqčāA×^}%BĩĒ9é„éˆË2‘¤faCƒ]‚ŗRŠ m­¸tHô?ëĐxâ^ĩ…L Âŧ‰ e5ä$ØĮš-ËWl˙dÛô˙¸đŖĩŸ˜ū`yum0Dtb;€ „÷ CÃŋĩ|ÅHÅ0fߏÚ#G?7o—šƒeRec/.økm•Û­ĒK7­ÜZzŌY1ZĶđšĀ×ĮއGĩ)ĶÁy~?›ŗâ)Äjˆ.ÆpÁ2oÛŧ 5 }|4Ē"‚QkX¨%qiœčt:Ģ;φ~œJĀÂĘ|R*ŖŖđ-ņ%Á{Áą^7‹žx4UVáÉ!ØŲÅÄcY š(đ0CĮ”\ îH‘Ã斞͙ÉĒnkūĨ‚~ONn šnD˙#Lĩ¤$ ]Āą] Ō‰bxđôÁl—@… EGMDN …$ŽK4/‡æč”•…âņ´ßīÆĀ y\näēa ¤ä‘€MM­H‘# Ííøŧč‰t Ž—Ë‹ĀŽ|íŪ^#Aú¯ZŧĶŽ-Đ@ČY]°4ë,ÔYAV?ĖÉЎĩ4ī4Íš˙Ũ_Ŋų™y>õ¤Ŗēō¨)Sézdˆ•@X3 —! 1X•1ßŨĩžĻ%iÜp?ŸUÆ.˜ĶäČúE9ƒB= ŗzõj%•ĒĢĢW­ĐŽÍˇcō„ą2lތ ʈ@ÜÔđžļŗœ°Ÿn 4Zļ´Ü"€0"ü‰PSp2ˆjĸŌôĪøAsP~Ķߒ툕?ɇIÛN÷AŅPŽēO ˛īŖĶ3°8™bŦ‘=D€ˆ%1˅¤$Ī ÉEHĻ…LŽņ–Ėš˙õ×_ŋų˙=ņØĶĄĀ”iĮ6ÂÆœ !ļqGú›;VŽO;Õ+æ°ŠĖЗīHÉ+ˆé@yv˖­X\ˇOž:īŲĮ oâąĮ>ŠāÄ û~BĘ .8ô…+Ū;–/I^ũ€#‘)}âĻÆÃdM“Ķ˜œ‡ClĻČ CH*\X$m?8°4 ų…Ė’^HGWˆŲūËņökš>ˇ›÷_RûsŌ˙Î#iĢ+͐ĤÛ+uaCü|Î`:[æ>:˙ßo_˙Ė#/žđ4_Y6dÂQutےē'x˜3—}ŧdTVhžaNãęuĖ™›e-ÕÚéq °'ęš4Áix‚mķÁÚœžûĮ^&›ŌõL€ˆ]įIļM×Fpŗ×š/ĪW<@Ií/ĪK|ņGJĮ`ց_Ę›ČsÃ—lYÕ+H!§ģ­ŖŊMK^õˇûž~îqŽŦlĖ1_ĪãŌ‰¸L"{ũxįļMĄöŒöč[ģV¯ÛėĘe}°ã!ųĘJÆÛĄäĄ¤9’åèpŽ|EÔY@øĀ~"ā{Ÿ¸î8PĀ„î°Ū]I Ŧä`Å]žāß§ˆŸÁbņiĀÂC(?dzÄN€p jæTŅb9ŲŠy¤‹¯ģōŪæ•6füÔĄÃ |rÁR:ä_oŋ1ŌĒrÕ2îãŲ'4šélgJƒL6Ī´Â9dc‘ü§{Ūëv°āøRŊnMĪđ2用—v+ĘBŸÍ§#žbB3‰•$;Â`BíŪ$¨ȑeBîģg˙ÁURrĖ”éÆÚ8@¨2Č:ä ܍ ÖJii1§ĘŦ—É ˇP%!07b˜ŦŠŌ ËH’'šÁHGÄ:HvÆÉ~q`˙v˜= € Č&Ã` ũc0`1vu &ËĪfĐSēķē#ôˆbˇ€c;čĮŽ BRą Û’¤Ŧ”*.A[0LQˆĘŊs˙VRQ}ė”c‡Ĩv!ä› LÚ3 ŧ&¨ú€=ļÆrŽŦ™VŌP,E“Ë"RQH¨×B”iŦ÷‚Ú:šŠ Ô},|ȝBפ%™ÎŊļdöU”IAë’=4gÅ| $î(ų XšßÜ˙ä*R{ž #ų˛Īä` 5XČ#†mĘ۝‰€ĀVIŒ:žYŅĢ´$ĖyôQŅ-ŸxÜ ]ū8ė‰M‰ā€sđ9ÛYąäCül0"K:2˛cxƒY ˜@qPkĘslЇcz^S3ž€/LËnë-[ąėėŗNom”•„0J°Š˜ƒŋ{äĶ“q°ŋā Žģ˙#ØcãPß˙uŋŸĶļĐoc§āėĩw]ÕéyÂuíŖ}Gbœüؑ_׏Qî˙}āÜ÷‰¤ŧíIéūDÚĮáz.í­Į,áŒė”÷ū&ö ĪŦœæ~üC ~IįŠö@[C3hoôŪ”ļxßî÷Ķcxx‡ŪmE‚a$Q†4Đ÷9Ũā'IbÎNA”LžŊãĒø‰÷Ô3ÄâŪ=ÆIG‹ ŠH€w!¨€c„O鄱î[ OĮG#ĢYI“ąÁSŌm<¸Š3´íī8÷W{>WIá‚nŗ]˜sŒ„ž§ĪížË?z†ûë‡Îįž/ž÷¨úūÄÂ{õ˜m Wx:Û´Ũw>ģۚõúKˇnųtXCũĘ5kęwīlRSCä|¨“pÁŠvHFür{úÕŧIeâ1(Ā—&Ŧ'ŋánę˜ÂĶč!A;ڀÆûŠy>^EĢŽÖ–}ėÚŨjé d'āĀ’Š0!ІŦø%ËX‹‡ŲčríOĄ+˜S wŌ (vltŲížotqz÷ƒŧÎMŊrūģÜsû! !‚Â̧ŊQŨŊ` Æß‚{ûpŲvПdßWčūŦî>õÃõ\ē ÄžĻk..õ¸§īßĨ`;îŅC>vĻëŅ…īũ~ˏåBe­ÁŌÜöģÕhÕLHAÖR;o`j0Ģ/[štÉô“§Nq^ŪB - B 0E [ÉļëFéȎ!;A+[NJ@CŽ ĖGyzš§ 4‹Ûí™DdtU\D ˆ>á€ũm÷ô°DfQž¨?‚áē#ĀsMwģ}™DĘ 8P@Š´Ã—]hß qVû>”Ô´„áîplˆ)ėîĩ¤Į=:Č;`ŊžQģfÃ>›å!ų°¤"nÖ@afD#' Ļyew ?.Q™ÃÍđŅææˇŧ~ÔQ“ētb|žŽ7Ę[2)Мc€+ū´ã/laOÄWa%O÷"Q`Ë6$‰NRdז™ ,Ą •b„͎x/“TÂCZ(‘Vē۟čųžĮœÁŅ×§û‰p PFeŒŪoc€-Ŋü  ōœ īc뽔ģtŖŧųŲ°Mųy(܃ņ@{ęĶûlņÕlÖHŦø 6ŗŽaeõü\ŲD L4 ƒõš×w„ų°ˇūáĨKCĸ–ŗm*GáōÚa$ÄFÉb@ŧŽZ˛×­ÂIܕ J!ȁEÔ9”І&Ƌ(ëDrĶÚõĸË=t¸7ŠX jÂFéū$š3ļB€ĒŌč1_2ũēĮōû!ö"cäĨ*0LŦ\DŊŊ™Œ*ˤėÜAEĄŽׅĨ}˙Éá ęŖ;ˇ(đ ÔíÍc;­W–g ũĀŌcDåôÜ΋ŨŸeĮڒ{t$“ô÷÷t@…{Š´ĩqĀ–IÆ Á Ŗ ôj_°ēđ. VĶyą”^â+ūIeûíöš?˛ëElGŅTNUŊD@Š)¤9ÔU!_D•PŲVcĩxjKssíHâø˙Ɲ_’s4*IENDŽB`‚pcm-202502/docker-compose.yml000066400000000000000000000007521475730356400160440ustar00rootroot00000000000000version: '2.0' services: pcm: image: ghcr.io/intel/pcm:latest ports: - "9738:9738" volumes: - "/sys/firmware/acpi/tables/MCFG:/pcm/sys/firmware/acpi/tables/MCFG:ro" - "/proc/bus/pci/:/pcm/proc/bus/pci/" - "/proc/sys/kernel/nmi_watchdog:/pcm/proc/sys/kernel/nmi_watchdog" - "/sys:/sys:rw" cap_add: - SYS_ADMIN - SYS_RAWIO devices: - /dev/cpu - /dev/mem pcm-202502/examples/000077500000000000000000000000001475730356400142215ustar00rootroot00000000000000pcm-202502/examples/CMakeLists.txt000066400000000000000000000016251475730356400167650ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2022, Intel Corporation set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/examples) # Linux examples if(UNIX) # Example part file(GLOB EXAMPLE_FILE c_example.c) # create static lib example add_executable(c_example ${EXAMPLE_FILE}) add_dependencies(c_example PCM_SHARED) set_target_properties(c_example PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE INSTALL_RPATH "${CMAKE_BINARY_DIR}/lib:${INSTALL_RPATH}") # rpath to libpcm.so target_compile_definitions(c_example PRIVATE PCM_DYNAMIC_LIB) # -DPCM_DYNAMIC_LIB target_link_libraries(c_example PRIVATE Threads::Threads dl) # -pthread -ldl # create shared lib example add_executable(c_example_shlib ${EXAMPLE_FILE}) target_link_libraries(c_example_shlib PUBLIC PCM_SHARED PRIVATE Threads::Threads) endif(UNIX)pcm-202502/examples/c_example.c000066400000000000000000000072511475730356400163270ustar00rootroot00000000000000#include #include #include #include int pcm_getcpu() { int id = -1; asm volatile ( "rdtscp\n\t" "mov %%ecx, %0\n\t": "=r" (id) :: "%rax", "%rcx", "%rdx"); // processor ID is in ECX: https://www.felixcloutier.com/x86/rdtscp // Linux encodes the NUMA node starting at bit 12, so remove the NUMA // bits when returning the CPU integer by masking with 0xFFF. return id & 0xFFF; } struct { int (*pcm_c_build_core_event)(uint8_t id, const char * argv); int (*pcm_c_init)(); void (*pcm_c_start)(); void (*pcm_c_stop)(); uint64_t (*pcm_c_get_cycles)(uint32_t core_id); uint64_t (*pcm_c_get_instr)(uint32_t core_id); uint64_t (*pcm_c_get_core_event)(uint32_t core_id, uint32_t event_id); } PCM; // lgtm [cpp/short-global-name] #ifndef PCM_DYNAMIC_LIB /* Library functions declaration (instead of .h file) */ int pcm_c_build_core_event(uint8_t, const char *); int pcm_c_init(); void pcm_c_start(); void pcm_c_stop(); uint64_t pcm_c_get_cycles(uint32_t); uint64_t pcm_c_get_instr(uint32_t); uint64_t pcm_c_get_core_event(uint32_t, uint32_t); #endif int main(int argc, const char *argv[]) { int i,a[100],b[100],c[100]; uint32_t total = 0; int lcore_id; int numEvents = argc - 1; /* Seed for predictable rand() results */ srand(0); for (i=0; i < 100; ++i) { a[i] = rand(); b[i] = rand(); c[i] = rand(); } #ifdef PCM_DYNAMIC_LIB void * handle = dlopen("libpcm.so", RTLD_LAZY); if(!handle) { printf("Abort: could not (dynamically) load shared library \n"); return -1; } PCM.pcm_c_build_core_event = (int (*)(uint8_t, const char *)) dlsym(handle, "pcm_c_build_core_event"); PCM.pcm_c_init = (int (*)()) dlsym(handle, "pcm_c_init"); PCM.pcm_c_start = (void (*)()) dlsym(handle, "pcm_c_start"); PCM.pcm_c_stop = (void (*)()) dlsym(handle, "pcm_c_stop"); PCM.pcm_c_get_cycles = (uint64_t (*)(uint32_t)) dlsym(handle, "pcm_c_get_cycles"); PCM.pcm_c_get_instr = (uint64_t (*)(uint32_t)) dlsym(handle, "pcm_c_get_instr"); PCM.pcm_c_get_core_event = (uint64_t (*)(uint32_t,uint32_t)) dlsym(handle, "pcm_c_get_core_event"); #else PCM.pcm_c_build_core_event = pcm_c_build_core_event; PCM.pcm_c_init = pcm_c_init; PCM.pcm_c_start = pcm_c_start; PCM.pcm_c_stop = pcm_c_stop; PCM.pcm_c_get_cycles = pcm_c_get_cycles; PCM.pcm_c_get_instr = pcm_c_get_instr; PCM.pcm_c_get_core_event = pcm_c_get_core_event; #endif if(PCM.pcm_c_init == NULL || PCM.pcm_c_start == NULL || PCM.pcm_c_stop == NULL || PCM.pcm_c_get_cycles == NULL || PCM.pcm_c_get_instr == NULL || PCM.pcm_c_build_core_event == NULL || PCM.pcm_c_get_core_event == NULL) return -1; if (numEvents > 4) { printf("Number of arguments are too many! exit...\n"); return -2; } for (i = 0; i < numEvents; ++i) { PCM.pcm_c_build_core_event(i, argv[i+1]); } printf("[c_example] Initializing PCM measurements:\n"); PCM.pcm_c_init(); printf("[c_example] Calling PCM start()\n"); PCM.pcm_c_start(); for(i=0;i<10000;i++) c[i%100] = 4 * a[i%100] + b[i%100]; for(i=0;i<100;i++) total += c[i]; PCM.pcm_c_stop(); printf("[c_example] PCM measurement stopped, compute result %u\n", total); lcore_id = pcm_getcpu(); printf("C:%llu I:%llu, IPC:%3.2f\n", (unsigned long long)PCM.pcm_c_get_cycles(lcore_id), (unsigned long long)PCM.pcm_c_get_instr(lcore_id), (double)PCM.pcm_c_get_instr(lcore_id)/PCM.pcm_c_get_cycles(lcore_id)); printf("CPU%d E0: %llu, E1: %llu, E2: %llu, E3: %llu\n", lcore_id, (unsigned long long)PCM.pcm_c_get_core_event(lcore_id,0), (unsigned long long)PCM.pcm_c_get_core_event(lcore_id,1), (unsigned long long)PCM.pcm_c_get_core_event(lcore_id,2), (unsigned long long)PCM.pcm_c_get_core_event(lcore_id,3)); return 0; } pcm-202502/pcm-kubernetes.yaml.experimental000066400000000000000000000104461475730356400207140ustar00rootroot00000000000000--- apiVersion: v1 kind: Namespace metadata: name: intel-pcm labels: # uses host features by design privileges required pod-security.kubernetes.io/enforce: privileged pod-security.kubernetes.io/enforce-version: latest pod-security.kubernetes.io/audit: privileged pod-security.kubernetes.io/audit-version: latest pod-security.kubernetes.io/warn: privileged pod-security.kubernetes.io/warn-version: latest --- apiVersion: apps/v1 kind: DaemonSet metadata: labels: app.kubernetes.io/instance: pcm app.kubernetes.io/name: intel-pcm app.kubernetes.io/part-of: intel-pcm name: intel-pcm namespace: intel-pcm spec: selector: matchLabels: app.kubernetes.io/component: pcm-sensor-server app.kubernetes.io/instance: pcm app.kubernetes.io/name: intel-pcm template: metadata: labels: app.kubernetes.io/component: pcm-sensor-server app.kubernetes.io/instance: pcm app.kubernetes.io/name: intel-pcm app.kubernetes.io/part-of: intel-pcm jobLabel: pcm spec: automountServiceAccountToken: false containers: - image: ghcr.io/intel/pcm:latest env: - name: PCM_NO_MSR value: "1" - name: PCM_IGNORE_ARCH_PERFMON value: "0" - name: PCM_NO_PERF value: "0" livenessProbe: failureThreshold: 3 httpGet: path: / port: 9738 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 name: intel-pcm ports: - containerPort: 9738 hostPort: 9738 name: pcm-metrics protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: / port: 9738 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 securityContext: privileged: false runAsNonRoot: false runAsUser: 0 readOnlyRootFilesystem: true capabilities: add: - SYS_ADMIN - SYS_RAWIO seccompProfile: type: RuntimeDefault volumeMounts: - mountPath: /dev/cpu name: dev-cpu readOnly: true - mountPath: /dev/mem name: dev-mem readOnly: true - mountPath: /pcm/proc/bus/pci name: proc-pci readOnly: true - mountPath: /pcm/sys/firmware/acpi/tables/MCFG name: sys-acpi readOnly: true - mountPath: /pcm/proc/sys/kernel/nmi_watchdog name: nmi-watchdog readOnly: true - mountPath: /sys name: sysfs readOnly: false nodeSelector: kubernetes.io/os: linux feature.node.kubernetes.io/cpu-model.vendor_id: Intel # node feature discovery populates this volumes: - hostPath: path: /dev/cpu name: dev-cpu - hostPath: path: /dev/mem name: dev-mem - hostPath: path: /sys name: sysfs - hostPath: path: /sys/firmware/acpi/tables/MCFG name: sys-acpi - hostPath: path: /proc/bus/pci name: proc-pci - hostPath: path: /proc/sys/kernel/nmi_watchdog name: nmi-watchdog --- # prometheus operator defines this CRD apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: labels: app.kubernetes.io/instance: pcm app.kubernetes.io/name: intel-pcm app.kubernetes.io/part-of: intel-pcm app.kubernetes.io/component: metrics jobLabel: pcm prometheus.io/podmonitor: system-metrics release: prometheus name: pcm namespace: intel-pcm spec: attachMetadata: node: true jobLabel: jobLabel namespaceSelector: matchNames: - intel-pcm podMetricsEndpoints: - enableHttp2: false filterRunning: true followRedirects: false honorLabels: true honorTimestamps: true path: /metrics port: pcm-metrics interval: 1s relabelings: - sourceLabels: - __meta_kubernetes_pod_node_name targetLabel: nodename scheme: http selector: matchLabels: app.kubernetes.io/component: pcm-sensor-server app.kubernetes.io/instance: pcm app.kubernetes.io/name: intel-pcm pcm-202502/pcm.spec000066400000000000000000000057071475730356400140470ustar00rootroot00000000000000 Name: pcm Version: master Release: 0 Summary: Intel(r) Performance Counter Monitor Group: System/Monitoring License: BSD-3-Clause Url: https://github.com/intel/pcm Source: %{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-build AutoReqProv: on BuildRequires: unzip BuildRequires: gcc BuildRequires: make BuildRequires: gcc-c++ BuildRequires: cmake %if 0%{?suse_version} BuildRequires: libopenssl-devel %else BuildRequires: openssl-devel BuildRequires: libasan %endif %description Intel(r) Performance Counter Monitor (Intel(r) PCM) is an application programming interface (API) and a set of tools based on the API to monitor performance and energy metrics of Intel(r) Core(tm), Xeon(r), Atom(tm) and Xeon Phi(tm) processors. PCM works on Linux, Windows, Mac OS X, FreeBSD and DragonFlyBSD operating systems. %prep %setup -n pcm-master %build mkdir build cd build cmake -DPCM_NO_STATIC_LIBASAN=ON -DCMAKE_INSTALL_PREFIX=/usr/ -DCMAKE_BUILD_TYPE=RelWithDebInfo .. make -j %install rm -rf $RPM_BUILD_ROOT cd build make install DESTDIR=$RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %post %postun %files %defattr(-,root,root,0755) %doc LICENSE doc/LINUX_HOWTO.txt /usr/share/doc/PCM /usr/share/licenses/pcm /usr/share/doc/PCM/CUSTOM-COMPILE-OPTIONS.md /usr/share/doc/PCM/DOCKER_README.md /usr/share/doc/PCM/ENVVAR_README.md /usr/share/doc/PCM/FAQ.md /usr/share/doc/PCM/FREEBSD_HOWTO.txt /usr/share/doc/PCM/LINUX_HOWTO.txt /usr/share/doc/PCM/MAC_HOWTO.txt /usr/share/doc/PCM/PCM-EXPORTER.md /usr/share/doc/PCM/PCM-SENSOR-SERVER-README.md /usr/share/doc/PCM/PCM_RAW_README.md /usr/share/doc/PCM/README.md /usr/share/doc/PCM/WINDOWS_HOWTO.md /usr/share/doc/PCM/license.txt /usr/share/licenses/pcm/LICENSE %{_sbindir}/pcm-core %{_sbindir}/pcm-iio %{_sbindir}/pcm-latency %{_sbindir}/pcm-lspci %{_sbindir}/pcm-memory %{_sbindir}/pcm-msr %{_sbindir}/pcm-mmio %{_sbindir}/pcm-tpmi %{_sbindir}/pcm-numa %{_sbindir}/pcm-pcicfg %{_sbindir}/pcm-accel %{_sbindir}/pcm-pcie %{_sbindir}/pcm-power %{_sbindir}/pcm-sensor %{_sbindir}/pcm-sensor-server %{_sbindir}/pcm-tsx %{_sbindir}/pcm-raw %{_sbindir}/pcm %{_bindir}/pcm-client %{_sbindir}/pcm-daemon %{_sbindir}/pcm-bw-histogram %{_datadir}/pcm/ %changelog * Mon Feb 28 2022 - roman.dementiev@intel.com add addition doc files * Tue Jan 04 2022 - maria.markova@intel.com Add cmake adaptation * Fri Dec 17 2021 - maria.markova@intel.com Move licence.txt Linix_HOWTO.txt to doc folder * Tue Aug 25 2020 - roman.dementiev@intel.com Add pcm-raw under %files * Wed Apr 01 2020 - otto.g.bruggeman@intel.com Add pcm-sensor-server under %files * Mon Nov 25 2019 - roman.dementiev@intel.com call make install and use _sbindir or _bindir * Mon Oct 21 2019 - roman.dementiev@intel.com add opCode file to /usr/share/pcm use "install" to copy pcm-bw-histogram.sh * Fri Oct 18 2019 - roman.dementiev@intel.com created spec file pcm-202502/perfmon/000077500000000000000000000000001475730356400140515ustar00rootroot00000000000000pcm-202502/scripts/000077500000000000000000000000001475730356400140725ustar00rootroot00000000000000pcm-202502/scripts/bhs-die-stat.sh000066400000000000000000000024511475730356400167140ustar00rootroot00000000000000#!/bin/bash echo "Intel(r) Performance Counter Monitor" echo "Birch Stream Die Statistics Utility" echo # Run the pcm-tpmi command and store the output output=$(pcm-tpmi 2 0x10 -d) # Use a while loop to read each line of the output echo "$output" | while read -r line; do # Check if the line contains "Read value" if [[ $line =~ Read\ value\ ([0-9]+)\ from\ TPMI\ ID\ 2@16\ for\ entry\ ([0-9]+)\ in\ instance\ ([0-9]+) ]]; then # Extract the value using BASH_REMATCH value=${BASH_REMATCH[1]} die=${BASH_REMATCH[2]} socket=${BASH_REMATCH[3]} freq=$(( (value & 0x7F) * 100 )) compute=$(( (value >> 23) & 1 )) llc=$(( (value >> 24) & 1 )) memory=$(( (value >> 25) & 1 )) io=$(( (value >> 26) & 1 )) die_type="" if [ "$compute" -ne 0 ]; then die_type="compute/" fi if [ "$llc" -ne 0 ]; then die_type="${die_type}LLC/" fi if [ "$memory" -ne 0 ]; then die_type="${die_type}memory/" fi if [ "$io" -ne 0 ]; then die_type="${die_type}IO" fi die_type="${die_type%"${die_type##*[!\/]}"}" str="Socket $socket die $die ($die_type) uncore frequency" printf "%-60s: %d MHz\n" "$str" "$freq" fi done pcm-202502/scripts/bhs-power-mode.ps1000066400000000000000000000107001475730356400173450ustar00rootroot00000000000000Write-Output "Intel(r) Performance Counter Monitor" Write-Output "Birch Stream Power Mode Utility" Write-Output "" Write-Output " Options:" Write-Output " --default : set default power mode" Write-Output " --latency-optimized-mode : set latency optimized mode" Write-Output "" # Run the pcm-tpmi command to determine I/O and compute dies $output = pcm-tpmi 2 0x10 -d -b 26:26 # Parse the output to build lists of I/O and compute dies $io_dies = @() $compute_dies = @() $die_types = @{} $output -split "`n" | ForEach-Object { $line = $_ if ($line -match "instance 0") { $die = $line -match 'entry (\d+)' | Out-Null; $matches[1] if ($line -match "value 1") { $die_types[$die] = "IO" $io_dies += $die } elseif ($line -match "value 0") { $die_types[$die] = "Compute" $compute_dies += $die } } } if ($args[0] -eq "--default") { Write-Output "Setting default mode..." foreach ($die in $io_dies) { # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 8 # EFFICIENCY_LATENCY_CTRL_LOW_THRESHOLD (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 38:32 -w 13 # EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 46:40 -w 120 # EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD_ENABLE (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 39:39 -w 1 } foreach ($die in $compute_dies) { # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore Compute) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 12 } } if ($args[0] -eq "--latency-optimized-mode") { Write-Output "Setting latency optimized mode..." foreach ($die in $io_dies) { # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 0 # EFFICIENCY_LATENCY_CTRL_LOW_THRESHOLD (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 38:32 -w 0 # EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 46:40 -w 0 # EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD_ENABLE (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 39:39 -w 1 } foreach ($die in $compute_dies) { # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore Compute) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 0 } } Write-Output "Dumping TPMI Power control register states..." Write-Output "" # Function to extract and calculate metrics from the value function ExtractAndPrintMetrics { param ( [int]$value, [int]$socket_id, [int]$die ) $die_type = $die_types[$die] # Extract bits and calculate metrics $min_ratio = ($value -shr 15) -band 0x7F $max_ratio = ($value -shr 8) -band 0x7F $eff_latency_ctrl_ratio = ($value -shr 22) -band 0x7F $eff_latency_ctrl_low_threshold = ($value -shr 32) -band 0x7F $eff_latency_ctrl_high_threshold = ($value -shr 40) -band 0x7F $eff_latency_ctrl_high_threshold_enable = ($value -shr 39) -band 0x1 # Convert to MHz or percentage $min_ratio = $min_ratio * 100 $max_ratio = $max_ratio * 100 $eff_latency_ctrl_ratio = $eff_latency_ctrl_ratio * 100 $eff_latency_ctrl_low_threshold = ($eff_latency_ctrl_low_threshold * 100) / 127 $eff_latency_ctrl_high_threshold = ($eff_latency_ctrl_high_threshold * 100) / 127 # Print metrics Write-Output "Socket ID: $socket_id, Die: $die, Type: $die_type" Write-Output "MIN_RATIO: $min_ratio MHz" Write-Output "MAX_RATIO: $max_ratio MHz" Write-Output "EFFICIENCY_LATENCY_CTRL_RATIO: $eff_latency_ctrl_ratio MHz" if ($die_type -eq "IO") { Write-Output "EFFICIENCY_LATENCY_CTRL_LOW_THRESHOLD: $eff_latency_ctrl_low_threshold%" Write-Output "EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD: $eff_latency_ctrl_high_threshold%" Write-Output "EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD_ENABLE: $eff_latency_ctrl_high_threshold_enable" } Write-Output "" } # Iterate over all dies and run pcm-tpmi for each to get the metrics foreach ($die in $die_types.Keys) { $output = pcm-tpmi 2 0x18 -d -e $die # Parse the output and extract metrics for each socket $output -split "`n" | ForEach-Object { $line = $_ if ($line -match "Read value") { $value = $line -match 'value (\d+)' | Out-Null; $matches[1] $socket_id = $line -match 'instance (\d+)' | Out-Null; $matches[1] ExtractAndPrintMetrics -value $value -socket_id $socket_id -die $die } } }pcm-202502/scripts/bhs-power-mode.sh000066400000000000000000000103771475730356400172660ustar00rootroot00000000000000#!/bin/bash echo "Intel(r) Performance Counter Monitor" echo "Birch Stream Power Mode Utility" echo "" echo " Options:" echo " --default : set default power mode" echo " --latency-optimized-mode : set latency optimized mode" echo # Run the pcm-tpmi command to determine I/O and compute dies output=$(pcm-tpmi 2 0x10 -d -b 26:26) # Parse the output to build lists of I/O and compute dies io_dies=() compute_dies=() declare -A die_types while read -r line; do if [[ $line == *"instance 0"* ]]; then die=$(echo "$line" | grep -oP 'entry \K[0-9]+') if [[ $line == *"value 1"* ]]; then die_types[$die]="IO" io_dies+=("$die") elif [[ $line == *"value 0"* ]]; then die_types[$die]="Compute" compute_dies+=("$die") fi fi done <<< "$output" if [ "$1" == "--default" ]; then echo "Setting default mode..." for die in "${io_dies[@]}"; do # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 8 #EFFICIENCY_LATENCY_CTRL_LOW_THRESHOLD (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 38:32 -w 13 #EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD(Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 46:40 -w 120 #EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD_ENABLE (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 39:39 -w 1 done for die in "${compute_dies[@]}"; do # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore Compute) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 12 done fi if [ "$1" == "--latency-optimized-mode" ]; then echo "Setting latency optimized mode..." for die in "${io_dies[@]}"; do # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 0 #EFFICIENCY_LATENCY_CTRL_LOW_THRESHOLD (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 38:32 -w 0 #EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD(Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 46:40 -w 0 #EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD_ENABLE (Uncore IO) pcm-tpmi 2 0x18 -d -e $die -b 39:39 -w 1 done for die in "${compute_dies[@]}"; do # EFFICIENCY_LATENCY_CTRL_RATIO (Uncore Compute) pcm-tpmi 2 0x18 -d -e $die -b 28:22 -w 0 done fi echo "Dumping TPMI Power control register states..." echo "" # Function to extract and calculate metrics from the value extract_and_print_metrics() { local value=$1 local socket_id=$2 local die=$3 local die_type=${die_types[$die]} # Extract bits and calculate metrics local min_ratio=$(( (value >> 15) & 0x7F )) local max_ratio=$(( (value >> 8) & 0x7F )) local eff_latency_ctrl_ratio=$(( (value >> 22) & 0x7F )) local eff_latency_ctrl_low_threshold=$(( (value >> 32) & 0x7F )) local eff_latency_ctrl_high_threshold=$(( (value >> 40) & 0x7F )) local eff_latency_ctrl_high_threshold_enable=$(( (value >> 39) & 0x1 )) # Convert to MHz or percentage min_ratio=$(( min_ratio * 100 )) max_ratio=$(( max_ratio * 100 )) eff_latency_ctrl_ratio=$(( eff_latency_ctrl_ratio * 100 )) eff_latency_ctrl_low_threshold=$(( (eff_latency_ctrl_low_threshold * 100) / 127 )) eff_latency_ctrl_high_threshold=$(( (eff_latency_ctrl_high_threshold * 100) / 127 )) # Print metrics echo "Socket ID: $socket_id, Die: $die, Type: $die_type" echo "MIN_RATIO: $min_ratio MHz" echo "MAX_RATIO: $max_ratio MHz" echo "EFFICIENCY_LATENCY_CTRL_RATIO: $eff_latency_ctrl_ratio MHz" if [ $die_type == "IO" ] ; then echo "EFFICIENCY_LATENCY_CTRL_LOW_THRESHOLD: $eff_latency_ctrl_low_threshold%" echo "EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD: $eff_latency_ctrl_high_threshold%" echo "EFFICIENCY_LATENCY_CTRL_HIGH_THRESHOLD_ENABLE: $eff_latency_ctrl_high_threshold_enable" fi echo } # Iterate over all dies and run pcm-tpmi for each to get the metrics for die in "${!die_types[@]}"; do output=$(pcm-tpmi 2 0x18 -d -e "$die") # Parse the output and extract metrics for each socket while read -r line; do if [[ $line == *"Read value"* ]]; then value=$(echo "$line" | grep -oP 'value \K[0-9]+') socket_id=$(echo "$line" | grep -oP 'instance \K[0-9]+') extract_and_print_metrics "$value" "$socket_id" "$die" fi done <<< "$output" done pcm-202502/scripts/build.sh000066400000000000000000000000511475730356400155210ustar00rootroot00000000000000 mkdir build cd build cmake .. make -j pcm-202502/scripts/busy.sh000066400000000000000000000002461475730356400154120ustar00rootroot00000000000000 # simple script to keep all cores busy for some time time -p bash -c 'for i in $(seq 1 $(nproc)); do perl -e '\''$c=0; for(0..99999999){$c++;}'\'' & done; wait' pcm-202502/scripts/convert_edp_list.sh000066400000000000000000000001151475730356400177660ustar00rootroot00000000000000 cat "$1" | sed '0,/^#.*[gG][rR][oO][uU][pP]/d' | head -n -1 | dos2unix pcm-202502/scripts/cppcheck.sh000077500000000000000000000003721475730356400162130ustar00rootroot00000000000000 cppcheck $1 --force --enable=warning --inline-suppr -iPCMService.cpp -isimdjson -ipugixml -iPcmMsrDriver_info.c -igoogletest -DTEXT -j $2 2> cppcheck.out if [ -s cppcheck.out ] then cat cppcheck.out exit 1 fi echo No issues found pcm-202502/scripts/debug-build.sh000066400000000000000000000001161475730356400166070ustar00rootroot00000000000000 mkdir debug-build cd debug-build cmake -DCMAKE_BUILD_TYPE=Debug .. make -j pcm-202502/scripts/filter.sh000066400000000000000000000000721475730356400157120ustar00rootroot00000000000000 cat $1| rs -c, -C, -T | grep "$2" | rs -c, -C, -T > $3 pcm-202502/scripts/find_field.awk000066400000000000000000000001461475730356400166620ustar00rootroot00000000000000#!/bin/awk -f { for(i=1; i<=NF; i++) { if (index($i, term) > 0) print (i)":"$i; } }pcm-202502/scripts/find_field.sh000066400000000000000000000000731475730356400165110ustar00rootroot00000000000000 head -1 $1 | awk -F ',' -v term="$2" -f find_field.awk pcm-202502/scripts/generate_summary.py000066400000000000000000000200211475730356400200060ustar00rootroot00000000000000#SPDX-License-Identifier: BSD-3-Clause #Copyright (c) 2023, Intel Corporation import pandas as pd import io import matplotlib from matplotlib import pyplot as plt import numpy as np from xlsxwriter import Workbook import xlsxwriter from datetime import datetime import sys if len(sys.argv) < 2: print("Error: Please input a valid csv filename in the command arguments") exit() try: df = pd.read_csv(sys.argv[1],header=[0,1]) except: arg = sys.argv[1] if arg == "--help" or arg == "-h": print("python3 -{arguments}") print("Following is the accepted list of arguments:") print("sys_exec_time : System Instructions per nominal CPU cycle vs System Time") print("cpu_util_time : Socket Instructions per nominal CPU cycle vs System Time") print("mem_traffic_time : Memory Bandwidth per socket vs System Time") print("cpu_util_socket : Instructions per nominal CPU cycle vs Socket") print("Mem_traffic_time : Average Memory Bandwidth per socket vs Socket") print("Note: If no arguments are passed, the default is to include everything above.") else: print("Error: Please input a avalid csv filename in the command arguments") exit() n = len(sys.argv) arguments=[] for i in range(2, n): arg = sys.argv[i] arg = arg.split('-') arguments.append(arg[1]) n = len(arguments) headers = df.columns columns=[] for title in headers: columns.append(title[0]+' '+title[1]) df.columns = columns flag = True count = 0 columns_added = [] while flag == True: column_name = 'Socket ' + str(count) + ' WRITE' if column_name in df.columns: insert_at_index = df.columns.get_loc(column_name) + 1 columns_added.append(insert_at_index) df.insert (insert_at_index, 'Socket ' + str(count) + ' mem traffic', (df['Socket ' + str(count) + ' READ'] * df['Socket ' + str(count) + ' WRITE'])/950) count = count + 1 else: count = count - 1 break try: system_time = df['System Time'] updated_time_format = [] for i in system_time: format = '%H:%M:%S' time_strip = i.split('.')[0] my_date = datetime.strptime(time_strip, format) updated_time_format.append(my_date.strftime("%H:%M:%S %p")) df['System Time'] = updated_time_format except: print("Generating consolidated report...") # 'System Exec' data={'System Time': df['System Time'], 'System Exec': df['System EXEC']} df1 = pd.DataFrame(data) # 'CPU utilization per socket (NUMA region)' data={} data['System Time'] = df['System Time'] for i in range(0,count+1): key = 'Socket ' + str(i) + ' EXEC' data[key] = df[key] df2 = pd.DataFrame(data) # 'Memory Bandwidth per socket (NUMA region)' data={} data['System Time'] = df['System Time'] for i in range(0,count+1): key = 'Socket ' + str(i) + ' mem traffic' data[key] = df[key] df3 = pd.DataFrame(data) # 'Average CPU utilization (extended instructions)' data={} data['System'] = df['System EXEC'].mean()*100 for i in range(0,count+1): key = 'Socket ' + str(i) + ' EXEC' data['Socket ' + str(i)] = df[key].mean()*100 df4 = pd.DataFrame(data,index=[1]) df4=df4.T # 'Average Memory Bandwidth per socket (NUMA region)' data={} for i in range(0,count+1): key = 'Socket ' + str(i) + ' mem traffic' data['Socket ' + str(i)] = df[key].mean()*100 df5 = pd.DataFrame(data,index=[1]) df5=df5.T writer = pd.ExcelWriter('consolidated_summary.xlsx', engine='xlsxwriter') sheet_name = 'consolidated' df.to_excel(writer, sheet_name=sheet_name) workbook = writer.book worksheet = writer.sheets[sheet_name] cell_format = workbook.add_format() cell_format.set_bg_color('yellow') cell_format.set_bold() for index in columns_added: excel_column = '' + xlsxwriter.utility.xl_col_to_name(index+1)+':'+xlsxwriter.utility.xl_col_to_name(index+1) worksheet.set_column(excel_column, None, cell_format) # ---------------------------------------------------------------------------------------- if "sys_exec_time" in arguments or n == 0: sheet_name = 'System EXEC' df1.to_excel(writer, sheet_name=sheet_name) workbook = writer.book worksheet = writer.sheets[sheet_name] (max_row, max_col) = df1.shape chart1 = workbook.add_chart({'type': 'line'}) chart1.add_series({ 'name': [sheet_name, 0, 2], 'categories': [sheet_name, 1, 1, max_row, 1], 'values': [sheet_name, 1, 2, max_row, 2], }) chart1.set_title ({'name': 'Instructions per nominal CPU cycle'}) chart1.set_x_axis({'name': 'System Time'}) chart1.set_y_axis({'name': 'System Instructions per nominal CPU cycle', 'major_gridlines': {'visible': False}}) worksheet.insert_chart(1, 6, chart1) # ---------------------------------------------------------------------------------------- if "cpu_util_time" in arguments or n == 0: sheet_name = 'CPU utilization per socket' df2.to_excel(writer, sheet_name=sheet_name) workbook = writer.book worksheet = writer.sheets[sheet_name] (max_row, max_col) = df2.shape chart2 = workbook.add_chart({'type': 'line'}) for i in range(0,max_col-1): col = i + 2 chart2.add_series({ 'name': [sheet_name, 0, col], 'categories': [sheet_name, 1, 1, max_row, 1], 'values': [sheet_name, 1, col, max_row, col], }) chart2.set_title ({'name': 'Instructions per nominal CPU cycle'}) chart2.set_x_axis({'name': 'System Time'}) chart2.set_y_axis({'name': 'Socket Instructions per nominal CPU cycle', 'major_gridlines': {'visible': False}}) worksheet.insert_chart(1, 6, chart2) # ---------------------------------------------------------------------------------------- if "mem_traffic_time" in arguments or n == 0: sheet_name = 'Memory Bandwidth per socket' df3.to_excel(writer, sheet_name=sheet_name) workbook = writer.book worksheet = writer.sheets[sheet_name] (max_row, max_col) = df3.shape chart3 = workbook.add_chart({'type': 'line'}) for i in range(0,max_col-1): col = i + 2 chart3.add_series({ 'name': [sheet_name, 0, col], 'categories': [sheet_name, 1, 1, max_row, 1], 'values': [sheet_name, 1, col, max_row, col], }) chart3.set_title ({'name': 'Memory Bandwidth per socket'}) chart3.set_x_axis({'name': 'System Time'}) chart3.set_y_axis({'name': 'Memory Traffic (GB)', 'major_gridlines': {'visible': False}}) worksheet.insert_chart(1, 6, chart3) # ---------------------------------------------------------------------------------------- if "cpu_util_socket" in arguments or n == 0: sheet_name = 'Average CPU Util' df4.to_excel(writer, sheet_name=sheet_name) workbook = writer.book worksheet = writer.sheets[sheet_name] (max_row, max_col) = df4.shape chart4 = workbook.add_chart({'type': 'column'}) chart4.add_series({ 'name': [sheet_name, 1, 0], 'categories': [sheet_name, 1, 0, max_row, 0], 'values': [sheet_name, 1, 1, max_row, 1], }) chart4.set_title ({'name': 'Instructions per nominal CPU cycle'}) chart4.set_x_axis({'name': 'Socket'}) chart4.set_y_axis({'name': 'Socket Instructions per nominal CPU cycle', 'major_gridlines': {'visible': False}}) worksheet.insert_chart(1, 6, chart4) # ---------------------------------------------------------------------------------------- if "Mem_traffic_time" in arguments or n == 0: sheet_name = 'Avg Memory Bandwidth per socket' df5.to_excel(writer, sheet_name=sheet_name) workbook = writer.book worksheet = writer.sheets[sheet_name] (max_row, max_col) = df4.shape chart5 = workbook.add_chart({'type': 'column'}) chart5.add_series({ 'name': [sheet_name, 1, 0], 'categories': [sheet_name, 1, 0, max_row, 0], 'values': [sheet_name, 1, 1, max_row, 1], }) chart5.set_title ({'name': 'Average Memory Bandwidth per socket'}) chart5.set_x_axis({'name': 'Socket'}) chart5.set_y_axis({'name': 'Memory Traffic (GB)', 'major_gridlines': {'visible': False}}) worksheet.insert_chart(1, 6, chart5) writer.close() pcm-202502/scripts/get_sles_bins.sh000066400000000000000000000004361475730356400172510ustar00rootroot00000000000000 filename=`curl https://download.opensuse.org/repositories/home:/opcm/SLE_15_SP1/x86_64/ -s | sed -n 's/.*\(pcm-0-[0-9]*\.1\.x86_64.rpm\).*/\1/p'` curl -L https://download.opensuse.org/repositories/home:/opcm/SLE_15_SP1/x86_64/$filename -o $filename rpm2cpio $filename | cpio -idmv pcm-202502/scripts/grafana/000077500000000000000000000000001475730356400154715ustar00rootroot00000000000000pcm-202502/scripts/grafana/README.md000066400000000000000000000044631475730356400167570ustar00rootroot00000000000000-------------------------------------------------------------------------------- Instructions on How To Run PCM Grafana Dashboard -------------------------------------------------------------------------------- Installation on target system to be analyzed: 1. Build from the root PCM directory: ``` cd ../.. # if the current location is 'scripts/grafana/' mkdir build && cd build cmake .. && make -j$(nproc) pcm-sensor-server ``` 2. As root start pcm-sensor-server: `cd bin && sudo ./pcm-sensor-server` Alternatively one can start [pcm-sensor-server as a container from docker hub](../../doc/DOCKER_README.md). Installation of the grafana front-end (can be on any *host* system with connectivity to the target system): 1. Make sure curl and docker are installed on the *host* 2. In PCM source directory on the *host*: `cd scripts/grafana` 3. (Download once and) start docker containers on the *host*: `sudo bash start.sh http://target_system_address:9738` - `start.sh` script starts telegraf/influxdb/grafana containers - `start-prometheus.sh` is an alternative script which starts prometheus + grafana containers: `sudo bash start-prometheus.sh target_system_address:9738` - `start.sh` and `start-prometheus.sh` can also be used to monitor multiple hosts running pcm-sensor-server containers:`sudo bash start.sh targets.txt` or `sudo bash start-prometheus.sh targets.txt`. Here `targets.txt` should be of the following format: ```properties host1_ipaddress:pcmport host2_ipaddress:pcmport . . hostn_ipaddress:pcmport ``` - Don't use `localhost` to specify the `target_system_address` if the *host* and the target are the same machine because `localhost` resolves to the own private IP address of the docker container when accessed inside the container. The external IP address or hostname should be used instead. 4. Start your browser at http://*host*:3000/ and then login with admin user, password admin . Change the password and then click on "**Home**" (left top corner) -> "**Dashboards**" -> "Intel® Performance Counter Monitor (Intel® PCM) Dashboard" 5. You can also stop and delete the containers when needed: `sudo bash stop.sh` ![pcm grafana output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-dashboard-full.png) pcm-202502/scripts/grafana/automatic_influxdb.yml000066400000000000000000000006651475730356400221040ustar00rootroot00000000000000apiVersion: 1 datasources: - name: PCM type: influxdb access: proxy url: "http://influxdb:8086" password: user: database: telegraf basicAuth: false basicAuthUser: basicAuthPassword: withCredentials: isDefault: true jsonData: tlsAuth: false tlsAuthWithCACert: false timeInterval: "2s" secureJsonData: tlsCACert: "" tlsClientCert: "" tlsClientKey: "" version: 1 editable: true pcm-202502/scripts/grafana/automatic_prometheus.yml000066400000000000000000000003541475730356400224570ustar00rootroot00000000000000apiVersion: 1 datasources: - name: PCM type: prometheus access: proxy url: "http://prometheus:9090" password: user: database: basicAuth: false isDefault: true editable: true jsonData: readOnly: false version: 1 pcm-202502/scripts/grafana/clean.sh000066400000000000000000000000761475730356400171120ustar00rootroot00000000000000 bash stop.sh rm -rf provisioning/datasources rm -rf *_volume pcm-202502/scripts/grafana/prometheus.yml.template000066400000000000000000000016261475730356400222260ustar00rootroot00000000000000# my global config global: scrape_interval: 2s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 2s # Evaluate rules every 15 seconds. The default is every 1 minute. # scrape_timeout is set to the global default (10s). # Alertmanager configuration alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. rule_files: # - "first_rules.yml" # - "second_rules.yml" # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=` to any timeseries scraped from this config. - job_name: 'pcm' # metrics_path defaults to '/metrics' # scheme defaults to 'http'. static_configs: - targets: ['PCMSENSORSERVER'] pcm-202502/scripts/grafana/provisioning/000077500000000000000000000000001475730356400202175ustar00rootroot00000000000000pcm-202502/scripts/grafana/provisioning/dashboards/000077500000000000000000000000001475730356400223315ustar00rootroot00000000000000pcm-202502/scripts/grafana/provisioning/dashboards/pcm-provider.yml000066400000000000000000000014441475730356400254660ustar00rootroot00000000000000apiVersion: 1 providers: # an unique provider name - name: 'default PCM dashboard provider' # org id. will default to orgId 1 if not specified orgId: 1 # name of the dashboard folder. Required folder: '' # folder UID. will be automatically generated if not specified folderUid: '' # provider type. Required type: file # disable dashboard deletion disableDeletion: false # enable dashboard editing editable: true # how often Grafana will scan for changed dashboards updateIntervalSeconds: 1 # allow updating provisioned dashboards from the UI allowUiUpdates: false options: # path to dashboard files on disk. Required path: /var/lib/grafana/dashboards pcm-202502/scripts/grafana/start-prometheus.sh000077500000000000000000000061201475730356400213550ustar00rootroot00000000000000#!/bin/bash set -e usage() { echo echo "Usage: $0 target_address:port" echo echo "target_address is the hostname or IP address of the system that runs pcm-sensor-server" echo echo "Alternative usage: $0 filename" echo echo "Specify filename containing target_address:port in each line" exit 1 } # Validate the URL format and reject localhost or 127.0.0.1 validate_url() { local url=$1 local regex='^([a-zA-Z0-9.-]+):[0-9]+$' local localhost_regex='^(localhost|127\.0\.0\.1):[0-9]+$' if ! [[ $url =~ $regex ]]; then echo "Error: The target_address ($url) provided is not in the correct format." usage fi if [[ $url =~ $localhost_regex ]]; then echo "Error: The target_address cannot be localhost or 127.0.0.1." usage fi } if [ "$#" -ne 1 ]; then usage fi CTR_RUN=${CTR_RUN:-docker} mkdir -p grafana_volume/dashboards || { echo "Error creating grafana_volume/dashboards directory"; exit 1; } mkdir -p prometheus_volume || { echo "Error creating prometheus_volume directory"; exit 1; } chmod -R 777 *_volume || { echo "Error setting permissions on volume directories"; exit 1; } mkdir -p provisioning/datasources || { echo "Error creating provisioning/datasources directory"; exit 1; } cp automatic_prometheus.yml provisioning/datasources/automatic.yml || { echo "Error copying automatic_prometheus.yml"; exit 1; } # check if argument is file, create the prometheus.yml accordingly if [ -f "$1" ]; then echo "creating prometheus.yml for hosts in targets file"; head -n -1 "prometheus.yml.template" > prometheus.yml || { echo "Error creating prometheus.yml"; exit 1; } while read -r line; do validate_url "$line" echo " - targets: ['$line']" >> "prometheus.yml" done < "$1" echo Downloading PCM dashboard curl -o grafana_volume/dashboards/pcm-dashboard.json $(head -1 "$1")/dashboard/prometheus || { echo "Error downloading PCM dashboard"; exit 1; } else validate_url "$1" echo "creating prometheus.yml for $1 "; sed "s#PCMSENSORSERVER#$1#g" prometheus.yml.template > prometheus.yml || { echo "Error creating prometheus.yml"; exit 1; } echo Downloading PCM dashboard curl -o grafana_volume/dashboards/pcm-dashboard.json "$1"/dashboard/prometheus || { echo "Error downloading PCM dashboard"; exit 1; } fi echo "Starting prometheus network" ${CTR_RUN} network create prometheus-network || { echo "Error creating prometheus network"; exit 1; } echo Starting prometheus ${CTR_RUN} run --name prometheus --network=prometheus-network -d -p 9090:9090 -v "$PWD"/prometheus.yml:/etc/prometheus/prometheus.yml:Z -v "$PWD"/prometheus_volume:/prometheus:Z quay.io/prometheus/prometheus:latest || { echo "Error starting prometheus"; exit 1; } echo Starting grafana ${CTR_RUN} run -d --network=prometheus-network --name=grafana -p 3000:3000 -v "$PWD"/grafana_volume:/var/lib/grafana:Z -v "$PWD"/provisioning:/etc/grafana/provisioning:Z -e GF_DASHBOARDS_MIN_REFRESH_INTERVAL=1s docker.io/grafana/grafana:latest || { echo "Error starting grafana"; exit 1; } echo "Start browser at http://"`hostname`":3000/ or http://localhost:3000/ and login with admin user, password admin" pcm-202502/scripts/grafana/start.sh000077500000000000000000000067111475730356400171720ustar00rootroot00000000000000#!/bin/bash set -e usage() { echo echo "Usage: $0 http(s)://target_address:port" echo echo "target_address is the hostname or IP address of the system that runs pcm-sensor-server" exit 1 } # Validate the URL format and reject localhost or 127.0.0.1 validate_url() { local url=$1 local regex='^https?://([a-zA-Z0-9.-]+):[0-9]+$' local localhost_regex='^(https?://)?(localhost|127\.0\.0\.1):[0-9]+$' if ! [[ $url =~ $regex ]]; then echo "Error: The URL provided is not in the correct format." usage fi if [[ $url =~ $localhost_regex ]]; then echo "Error: The target_address cannot be localhost or 127.0.0.1." usage fi } if [ "$#" -ne 1 ]; then usage fi validate_url "$1" mkdir -p grafana_volume/dashboards || { echo "Error creating grafana_volume/dashboards directory"; exit 1; } mkdir -p influxdb_volume || { echo "Error creating influxdb_volume directory"; exit 1; } chmod -R 777 *_volume || { echo "Error setting permissions on volume directories"; exit 1; } mkdir -p provisioning/datasources || { echo "Error creating provisioning/datasources directory"; exit 1; } cp automatic_influxdb.yml provisioning/datasources/automatic.yml || { echo "Error copying automatic_influxdb.yml"; exit 1; } CTR_RUN=${CTR_RUN:-docker} # check if argument is file, create the telegraf.conf accordingly if [ -f "$1" ]; then echo "creating telegraf.conf for hosts in targets file"; head -n -7 "telegraf.conf.template" > telegraf.conf || { echo "Error creating telegraf.conf"; exit 1; } while IFS='' read -r line || [[ -n "$line" ]]; do # Split the line at the : character to get the IP and port ip=$(echo "$line" | cut -d ':' -f 1) port=$(echo "$line" | cut -d ':' -f 2) # Append the transformed line to the output file, separated by a comma echo -n "\"http://$ip:$port/persecond/\"," >> telegraf.conf done < "$1" sed -i '$ s/,$//' telegraf.conf || { echo "Error editing telegraf.conf"; exit 1; } tail -n -6 "telegraf.conf.template" >> telegraf.conf || { echo "Error appending to telegraf.conf"; exit 1; } echo Downloading PCM dashboard curl -o grafana_volume/dashboards/pcm-dashboard.json $(head -1 "$1")/dashboard || { echo "Error downloading PCM dashboard"; exit 1; } else echo "creating telegraf.conf for $1 "; sed "s#PCMSENSORSERVER#$1#g" telegraf.conf.template > telegraf.conf || { echo "Error creating telegraf.conf"; exit 1; } echo Downloading PCM dashboard curl -o grafana_volume/dashboards/pcm-dashboard.json "$1"/dashboard || { echo "Error downloading PCM dashboard"; exit 1; } fi echo "Creating influxdb network" ${CTR_RUN} network create influxdb-network || { echo "Error creating influxdb network"; exit 1; } echo Starting influxdb ${CTR_RUN} run -d --name influxdb -p 8083:8083 -p 8086:8086 --network=influxdb-network -v "$PWD"/influxdb_volume:/var/lib/influxdb influxdb:1.8.0-alpine || { echo "Error starting influxdb"; exit 1; } echo Starting telegraf ${CTR_RUN} run -d --name telegraf --network=influxdb-network -v "$PWD"/telegraf.conf:/etc/telegraf/telegraf.conf:ro telegraf || { echo "Error starting telegraf"; exit 1; } echo Starting grafana ${CTR_RUN} run -d --network=influxdb-network --name grafana -p 3000:3000 -v "$PWD"/provisioning:/etc/grafana/provisioning -v "$PWD"/grafana_volume:/var/lib/grafana -e GF_DASHBOARDS_MIN_REFRESH_INTERVAL=1s grafana/grafana || { echo "Error starting grafana"; exit 1; } echo "Start browser at http://"`hostname`":3000/ or http://localhost:3000/ and login with admin user, password admin" pcm-202502/scripts/grafana/stop.sh000077500000000000000000000004701475730356400170160ustar00rootroot00000000000000 CTR_RUN=${CTR_RUN:-docker} for c in grafana telegraf influxdb prometheus; do id=`${CTR_RUN} ps -a -q --filter="name=$c" --format="{{.ID}}"` if [ ! -z "$id" ] then echo Stopping and deleting $c ${CTR_RUN} rm $(${CTR_RUN} stop $id) fi done ${CTR_RUN} network rm prometheus-network influxdb-network pcm-202502/scripts/grafana/telegraf.conf.template000066400000000000000000000106711475730356400217500ustar00rootroot00000000000000# # Telegraf is entirely plugin driven. All metrics are gathered from the # declared inputs, and sent to the declared outputs. # # Plugins must be declared in here to be active. # To deactivate a plugin, comment out the name and any variables. # # Use 'telegraf -config telegraf.conf -test' to see what metrics a config # file would generate. # # Environment variables can be used anywhere in this config file, simply prepend # them with $. For strings the variable must be within quotes (ie, "$STR_VAR"), # for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR) # Global tags can be specified here in key="value" format. [global_tags] # dc = "us-east-1" # will tag all metrics with dc=us-east-1 # rack = "1a" ## Environment variables can be used as tags, and throughout the config file # user = "$USER" # Configuration for telegraf agent [agent] ## Default data collection interval for all inputs # interval = "2s" ## Rounds collection interval to 'interval' ## ie, if interval="10s" then always collect on :00, :10, :20, etc. round_interval = false ## Telegraf will send metrics to outputs in batches of at ## most metric_batch_size metrics. metric_batch_size = 1000 ## For failed writes, telegraf will cache metric_buffer_limit metrics for each ## output, and will flush this buffer on a successful write. Oldest metrics ## are dropped first when this buffer fills. metric_buffer_limit = 10000 ## Collection jitter is used to jitter the collection by a random amount. ## Each plugin will sleep for a random time within jitter before collecting. ## This can be used to avoid many plugins querying things like sysfs at the ## same time, which can have a measurable effect on the system. collection_jitter = "0s" ## Default flushing interval for all outputs. You shouldn't set this below ## interval. Maximum flush_interval will be flush_interval + flush_jitter flush_interval = "1s" ## Jitter the flush interval by a random amount. This is primarily to avoid ## large write spikes for users running a large number of telegraf instances. ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s flush_jitter = "0s" ## By default, precision will be set to the same timestamp order as the ## collection interval, with the maximum being 1s. ## Precision will NOT be used for service inputs, such as logparser and statsd. ## Valid values are "ns", "us" (or "Âĩs"), "ms", "s". precision = "" ## Run telegraf in debug mode debug = false ## Run telegraf in quiet mode quiet = false ## Override default hostname, if empty use os.Hostname() ## hostname = "" ## If set to true, do no set the "host" tag in the telegraf agent. omit_hostname = false ############################################################################### # OUTPUT PLUGINS # ############################################################################### # Configuration for influxdb server to send metrics to [[outputs.influxdb]] ## The full HTTP or UDP endpoint URL for your InfluxDB instance. ## Multiple urls can be specified as part of the same cluster, ## this means that only ONE of the urls will be written to each interval. # urls = ["udp://localhost:8089"] # UDP endpoint example urls = ["http://influxdb:8086"] # required ## The target database for metrics (telegraf will create it if not exists). database = "telegraf" # required ## Retention policy to write to. Empty string writes to the default rp. retention_policy = "" ## Write consistency (clusters only), can be: "any", "one", "quorum", "all" write_consistency = "any" ## Write timeout (for the InfluxDB client), formatted as a string. ## If not provided, will default to 5s. 0s means no timeout (not recommended). timeout = "5s" # username = "telegraf" # password = "metricsmetricsmetricsmetrics" ## Set the user agent for HTTP POSTs (can be useful for log differentiation) # user_agent = "telegraf" ## Set UDP payload size, defaults to InfluxDB UDP Client default (512 bytes) # udp_payload = 512 ## Optional SSL Config # ssl_ca = "/etc/telegraf/ca.pem" # ssl_cert = "/etc/telegraf/cert.pem" # ssl_key = "/etc/telegraf/key.pem" ## Use SSL but skip chain & host verification # insecure_skip_verify = false [[inputs.http]] urls = [ "PCMSENSORSERVER/persecond/" ] interval = "2s" data_format = "json" headers = {"Accept" = "application/json"} pcm-202502/scripts/pcm-background.sh000066400000000000000000000000411475730356400173150ustar00rootroot00000000000000 pcm "$@" & echo "$!" > pcm.pid pcm-202502/scripts/pcm-exporter000066400000000000000000000000601475730356400164360ustar00rootroot00000000000000#!/bin/sh ../build/bin/pcm-sensor-server "$@" pcm-202502/scripts/pcm-stop.sh000066400000000000000000000000241475730356400161640ustar00rootroot00000000000000 kill `cat pcm.pid` pcm-202502/scripts/pcm-win-power-tray.py000066400000000000000000000075261475730356400201370ustar00rootroot00000000000000import pystray from PIL import Image, ImageDraw, ImageFont import threading import time import subprocess import csv import io def create_image(text, background_color): width = 12 height = 12 image = Image.new('RGB', (width, height), background_color) draw = ImageDraw.Draw(image) # Use the default font and scale it font = ImageFont.load_default() # Calculate text size and position text_bbox = draw.textbbox((0, 0), text, font=font) text_width = text_bbox[2] - text_bbox[0] text_height = text_bbox[3] - text_bbox[1] text_x = (width - text_width) // 2 text_y = -2 + (height - text_height) // 2 # Draw the text on the image draw.text((text_x, text_y), text, fill="white", font=font) # Scale the image down to fit the system tray icon size image = image.resize((64, 64), Image.LANCZOS) return image # global process variable to kill the pcm.exe process when the icon is clicked process = None def update_icon(icon): # Start the pcm.exe process with -csv 3 parameters # store process into the global process variable global process process = subprocess.Popen( # change the path to pcm.exe as needed ["..\\windows_build22\\bin\\Release\\pcm.exe", "-r", "-csv", "3"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) count = 0 while True: # find the header line to find the index of "SYSTEM Energy (Joules)" if (count > 1000): print (f"Can't find SYSTEM Energy (Joules) metric") process.kill() exit(0) count = count + 1 line = process.stdout.readline() csv_reader = csv.reader(io.StringIO(line)) header = next(csv_reader) try: system_energy_index = header.index("SYSTEM Energy (Joules)") print (f"SYSTEM Energy (Joules) found at index {system_energy_index}") break except ValueError: #print("SYSTEM Energy (Joules) not found in the header") continue # Skip the second header line process.stdout.readline() while True: # Read the output line by line line = process.stdout.readline() # print (line) if not line: break system_energy_joules = -1 # Parse the CSV output csv_reader = csv.reader(io.StringIO(line)) for row in csv_reader: # Extract the system power consumption in Watts try: system_energy_joules = float(row[system_energy_index]) print (f"SYSTEM Energy (Joules): {system_energy_joules}") # Convert Joules to Watts power_consumption_watts = system_energy_joules / 3.0 if (power_consumption_watts > 30) : background_color = "red" elif (power_consumption_watts > 20) : background_color = "darkblue" else: background_color = "darkgreen" # Update the icon with the current power consumption in Watts icon.icon = create_image(f"{power_consumption_watts:.0f}", background_color) except (IndexError, ValueError): continue if (system_energy_joules == -1): continue print (f"pcm.exe exited with code {process.returncode}") def on_quit(icon, item): icon.stop() process.kill() def main(): # Create the system tray icon icon = pystray.Icon("Intel PCM: System Watts") icon.icon = create_image("P", "darkblue") icon.title = "Intel PCM: System Watts" icon.menu = pystray.Menu( pystray.MenuItem("Quit", on_quit) ) # Start a thread to update the icon threading.Thread(target=update_icon, args=(icon,), daemon=True).start() # Run the icon icon.run() if __name__ == "__main__": main()pcm-202502/scripts/pcm.plot000066400000000000000000000006251475730356400155540ustar00rootroot00000000000000 set key autotitle columnhead set datafile separator "," # change as needed # set xlabel 'sample # (each is 1000ms)' set ylabel 'metric value' set terminal pdf set output "pcm.pdf" # change below as needed # plot metrics 3 .. 37 do for [m=3:37] { plot "single_header.pcm.csv" using m with dots } # plot metrics 84 .. 107 do for [m=84:107] { plot "single_header.pcm.csv" using m with dots } pcm-202502/scripts/pmu-query.py000066400000000000000000000116551475730356400164200ustar00rootroot00000000000000#!/usr/bin/env python3 import io import urllib.request import urllib.parse import json import csv # subprocess is used as multiplatform approach, usage is verified (20-07-2022) import subprocess # nosec import sys import platform import getopt import re import shutil all_flag = False download_flag = False filename = None offcore_events = [] core_events = [] try: opts, args = getopt.getopt(sys.argv[1:], "a,f:,d", ["all", "file=", "download"]) for o, a in opts: if o in ("-a", "--all"): all_flag = True if o in ("-f", "--file"): filename = a if o in ("-d", "--download"): download_flag = True except getopt.GetoptError as err: print("parse error: %s\n" % (str(err))) sys.exit(-2) if filename is None: # vefified that link to mapfile.csv is safe and correct (20-07-2022) map_file_raw = urllib.request.urlopen("https://raw.githubusercontent.com/intel/perfmon/main/mapfile.csv").read().decode('utf-8') # nosec map_dict = csv.DictReader(io.StringIO(map_file_raw), delimiter=',') map_file = [] core_path = "" offcore_path = "" while True: try: map_file.append(next(map_dict)) except StopIteration: break if platform.system() == "CYGWIN_NT-6.1": p = subprocess.Popen(["./pcm-core.exe -c"], stdout=subprocess.PIPE, shell=True) elif platform.system() == "Windows": p = subprocess.Popen(["pcm-core.exe", "-c"], stdout=subprocess.PIPE, shell=True) elif platform.system() == "Linux": pcm_core = shutil.which("pcm-core") if not pcm_core: print("Could not find pcm-core executable!") sys.exit(-1) p = subprocess.Popen([pcm_core, "-c"], stdout=subprocess.PIPE, shell=True) else: p = subprocess.Popen(["../build/bin/pcm-core -c"], stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() p_status = p.wait() for model in map_file: if re.search(model["Family-model"], output.decode("utf-8")): if model["EventType"] == "core": core_path = model["Filename"] elif model["EventType"] == "offcore": offcore_path = model["Filename"] print(model) if core_path: # vefified that links, created on base of map_file are correct (20-07-2022) json_core_data = urllib.request.urlopen( # nosec "https://raw.githubusercontent.com/intel/perfmon/main" + core_path ) core_events = json.load(json_core_data) if download_flag: with open(core_path.split("/")[-1], "w") as outfile: json.dump(core_events, outfile, sort_keys=True, indent=4) else: print("no core event found for %s CPU, program abort..." % output.decode("utf-8")) sys.exit(-1) if offcore_path: # vefified that links, created on base of map_file are correct (20-07-2022) json_offcore_data = urllib.request.urlopen( # nosec "https://raw.githubusercontent.com/intel/perfmon/main" + offcore_path ) offcore_events = json.load(json_offcore_data) if download_flag: with open(offcore_path.split("/")[-1], "w") as outfile: json.dump(offcore_events, outfile, sort_keys=True, indent=4) else: for f in filename.split(","): print(f) core_events.extend(json.load(open(f))) if all_flag: for event in core_events + offcore_events: if "EventName" in event and "BriefDescription" in event: print(event["EventName"] + ":" + event["BriefDescription"]) sys.exit(0) name = input("Event to query (empty enter to quit):") while name: for event in core_events + offcore_events: if "EventName" in event and name.lower() in event["EventName"].lower(): print(event["EventName"] + ":" + event["BriefDescription"]) for ev_code in event["EventCode"].split(", "): print( "cpu/umask=%s,event=%s,name=%s%s%s%s%s%s/" % ( event["UMask"], ev_code, event["EventName"], (",offcore_rsp=%s" % (event["MSRValue"])) if event["MSRValue"] != "0" else "", (",inv=%s" % (event["Invert"])) if event["Invert"] != "0" else "", (",any=%s" % (event["AnyThread"])) if event["AnyThread"] != "0" else "", (",edge=%s" % (event["EdgeDetect"])) if event["EdgeDetect"] != "0" else "", (",cmask=%s" % (event["CounterMask"])) if event["CounterMask"] != "0" else "", ) ) name = input("Event to query (empty enter to quit):") pcm-202502/scripts/readmem.sh000066400000000000000000000011471475730356400160430ustar00rootroot00000000000000 numactl --cpunodebind=0 --membind=0 ./readmem 10 & numactl --cpunodebind=1 --membind=1 ./readmem 10 & numactl --cpunodebind=0 --membind=0 ./readmem 10 & numactl --cpunodebind=1 --membind=1 ./readmem 10 & numactl --cpunodebind=0 --membind=0 ./readmem 10 & numactl --cpunodebind=1 --membind=1 ./readmem 10 & numactl --cpunodebind=0 --membind=0 ./readmem 10 & numactl --cpunodebind=1 --membind=1 ./readmem 10 & numactl --cpunodebind=0 --membind=0 ./readmem 10 & numactl --cpunodebind=1 --membind=1 ./readmem 10 & numactl --cpunodebind=0 --membind=0 ./readmem 10 & numactl --cpunodebind=1 --membind=1 ./readmem 10 & pcm-202502/scripts/single_header.awk000066400000000000000000000004631475730356400173720ustar00rootroot00000000000000BEGIN { line = 0; } { if (line == 0) { # print $0; for(i=1; i<=NF; i++) { first[i] = $i; } } else if (line == 1) { for(i=1; i<=NF; i++) { if ($i != "") printf first[i]" "$i"," } print "" } else { print $0 } line = line + 1; } pcm-202502/scripts/single_header.sh000066400000000000000000000000771475730356400172230ustar00rootroot00000000000000 cat $1 | awk -F ',' -f single_header.awk > single_header.$1 pcm-202502/src/000077500000000000000000000000001475730356400131725ustar00rootroot00000000000000pcm-202502/src/CMakeLists.txt000066400000000000000000000342741475730356400157440ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2022, Intel Corporation include(FindOpenSSL) # All pcm-* executables set(PROJECT_NAMES pcm pcm-numa pcm-latency pcm-power pcm-msr pcm-memory pcm-tsx pcm-pcie pcm-core pcm-iio pcm-lspci pcm-pcicfg pcm-mmio pcm-tpmi pcm-raw pcm-accel) set(MINIMUM_OPENSSL_VERSION 1.1.1) file(GLOB COMMON_SOURCES pcm-accel-common.cpp msr.cpp cpucounters.cpp pci.cpp mmio.cpp tpmi.cpp pmt.cpp bw.cpp utils.cpp topology.cpp debug.cpp threadpool.cpp uncore_pmu_discovery.cpp ${PCM_PUGIXML_CPP}) if (APPLE) file(GLOB UNUX_SOURCES dashboard.cpp) else() file(GLOB UNUX_SOURCES dashboard.cpp resctrl.cpp) endif() if (LINUX) if(EXISTS "/etc/os-release") # AND IS_READABLE "/etc/os-release" (3.29 cmake required :-( ) file(STRINGS "/etc/os-release" OS_RELEASE_CONTENTS) foreach(LINE ${OS_RELEASE_CONTENTS}) if(LINE MATCHES "^ID=") string(REGEX REPLACE "^ID=\"?\([a-zA-Z]+\)\"?" "\\1" OS_ID ${LINE}) endif() endforeach() message(STATUS "Detected Linux distribution: ${OS_ID}") else() message(STATUS "Unable to read /etc/os-release") endif() endif() if(NOT PCM_NO_ASAN) if(OS_ID STREQUAL "centos") set(PCM_NO_STATIC_LIBASAN ON) message(STATUS "CentOS detected, using dynamic libasan") endif() if(OS_ID STREQUAL "arch") set(PCM_NO_STATIC_LIBASAN ON) message(STATUS "arch Linux detected, using dynamic libasan") endif() endif() if(UNIX) # LINUX, FREE_BSD, APPLE if (NOT APPLE) set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS} -s") # --strip-unneeded for packaging endif() list(APPEND PROJECT_NAMES pcm-sensor-server) list(APPEND PROJECT_NAMES pcm-sensor) # libpcm.a add_library(PCM_STATIC STATIC ${COMMON_SOURCES} ${UNUX_SOURCES}) set_target_properties(PCM_STATIC PROPERTIES OUTPUT_NAME pcm) # libpcm.a with -DPCM_SILENT for Release* add_library(PCM_STATIC_SILENT STATIC ${COMMON_SOURCES} ${UNUX_SOURCES}) target_compile_definitions(PCM_STATIC_SILENT PRIVATE $<$:PCM_SILENT> $<$:PCM_SILENT> $<$:PCM_SILENT> ) set_target_properties(PCM_STATIC_SILENT PROPERTIES POSITION_INDEPENDENT_CODE ON) # libpcm.so add_library(PCM_SHARED SHARED pcm-core.cpp) target_compile_options(PCM_SHARED PRIVATE -DPCM_SHARED_LIBRARY=1) # PCM_SILENT in Release* for pcm-core.cpp target_compile_definitions(PCM_SHARED PRIVATE $<$:PCM_SILENT> $<$:PCM_SILENT> $<$:PCM_SILENT> ) if(PCM_NO_ASAN) set(PCM_DYNAMIC_ASAN "") set(PCM_STATIC_ASAN "") else() if(PCM_NO_STATIC_LIBASAN) message(STATUS "Using dynamic libasan") set(PCM_DYNAMIC_ASAN "asan") set(PCM_STATIC_ASAN "") else() set(PCM_DYNAMIC_ASAN "") set(PCM_STATIC_ASAN "-static-libasan") message(STATUS "Using static libasan") message(STATUS "To use dynamic libasan, use -DPCM_NO_STATIC_LIBASAN=1 option (required for CentOS and arch Linux)") endif() endif() if(APPLE) add_subdirectory(MacMSRDriver) include_directories("${CMAKE_SOURCE_DIR}/src/MacMSRDriver") # target_include_directories doesn't work target_link_libraries(PCM_SHARED PRIVATE PCM_STATIC_SILENT PcmMsr Threads::Threads) elseif(LINUX) target_link_libraries(PCM_SHARED PRIVATE PCM_STATIC_SILENT Threads::Threads "${PCM_DYNAMIC_ASAN}") else() target_link_libraries(PCM_SHARED PRIVATE PCM_STATIC_SILENT Threads::Threads) endif() set_target_properties(PCM_SHARED PROPERTIES OUTPUT_NAME pcm) endif() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2 /wd4251 /wd4273 /EHa /Zi") add_definitions(/W3) # windows/* files -> PCM_STATIC file(GLOB WINDOWS_SOURCES winpmem/winpmem.cpp windows/stdafx.cpp freegetopt/getopt.cpp) add_library(PCM_STATIC STATIC ${COMMON_SOURCES} ${WINDOWS_SOURCES}) target_compile_definitions(PCM_STATIC PRIVATE UNICODE _UNICODE _CONSOLE) if(PCM_NO_STATIC_MSVC_RUNTIME_LIBRARY) set(PCM_MSVC_RUNTIME_LIBRARY_OPTIONS "") else() set(PCM_MSVC_RUNTIME_LIBRARY_OPTIONS "/MT$<$:d>") message(STATUS "Using static MSVC runtime library") message(STATUS "To use default/dynamic MSVC runtime library, use -DNO_STATIC_MSVC_RUNTIME_LIBRARY=1 option") endif() target_compile_options(PCM_STATIC PRIVATE "${PCM_MSVC_RUNTIME_LIBRARY_OPTIONS}") # Graphical perfmon front-end: pcm-lib, pcm-service # Files: COMMON_FILES() + pcm-lib.cpp winpmem\winpmem.cpp dllmain.cpp file(GLOB PCM_LIB_SOURCES winpmem/winpmem.cpp dllmain.cpp pcm-lib.cpp ) add_library(pcm-lib SHARED ${COMMON_SOURCES} ${PCM_LIB_SOURCES}) target_compile_definitions(pcm-lib PRIVATE _WINDOWS _USRDLL PCM_EXPORTS _WINDLL _UNICODE UNICODE) target_compile_options(pcm-lib PRIVATE "${PCM_MSVC_RUNTIME_LIBRARY_OPTIONS}") # Pcm-service files: PCM_SHARED + AssemblyInfo.cpp PCMInstaller.cpp PCMService.cpp file(GLOB PCM_SERVICE_SOURCES windows/PCMInstaller.cpp windows/PCMService.cpp windows/AssemblyInfo.cpp winddows/utils.cpp) add_executable(pcm-service ${PCM_SERVICE_SOURCES}) target_compile_definitions(pcm-service PRIVATE _UNICODE UNICODE _CONSOLE) set_target_properties(pcm-service PROPERTIES LINK_FLAGS "/INCREMENTAL:NO" COMMON_LANGUAGE_RUNTIME "") set_property(TARGET pcm-service PROPERTY VS_DOTNET_REFERENCES "System;System.Configuration.Install;System.Data;System.Management;System.ServiceProcess;System.Xml") target_link_libraries(pcm-service pcm-lib) endif(MSVC) ####################### # SIMDJSON dependency ####################### add_library(PCM_SIMDJSON INTERFACE) # interface library for simdjson set(SIMDJSON_IS_APPLICABLE TRUE) # true if simdjson can be used, default - TRUE # check simdjson support matrix - https://github.com/simdjson/simdjson/blob/master/doc/basics.md # > GCC 7.4, > Clang 6.0 , > MSVC 2017 if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.4) OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6) OR (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_TOOLSET_VERSION VERSION_LESS 141)) # corresponds to VS2017 message(WARNING " ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_VERSION} is incompartible with simdjson features' requirements.\n" " Refer to simdjson support matrix - https://github.com/simdjson/simdjson/blob/master/doc/basics.md .\n" " Parsing events from https://github.com/intel/perfmon won't be supported.") set(SIMDJSON_IS_APPLICABLE FALSE) endif() if(SIMDJSON_IS_APPLICABLE) find_package(simdjson QUIET) # Working form Ubuntu 22.04 if(simdjson_FOUND) message(STATUS "System SIMDJSON is used") target_link_libraries(PCM_SIMDJSON INTERFACE simdjson::simdjson) target_compile_definitions(PCM_SIMDJSON INTERFACE SYSTEM_SIMDJSON) target_compile_definitions(PCM_SIMDJSON INTERFACE PCM_SIMDJSON_AVAILABLE) else() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/simdjson/singleheader/simdjson.h") message(STATUS "Local SIMDJSON exists: ${CMAKE_CURRENT_SOURCE_DIR}/simdjson/singleheader/simdjson.h") file(GLOB SIMDJSON_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/simdjson/singleheader/simdjson.cpp) target_sources(PCM_SIMDJSON INTERFACE ${SIMDJSON_SOURCE}) target_include_directories(PCM_SIMDJSON INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/simdjson/singleheader) target_compile_definitions(PCM_SIMDJSON INTERFACE PCM_SIMDJSON_AVAILABLE) else() message(WARNING " ${CMAKE_CURRENT_SOURCE_DIR}/simdjson/singleheader/simdjson.h doesn't exist\n" " Use `git clone --recursive` flag when cloning pcm repository to clone simdjson submodule as well or\n" " update submodule with command 'git submodule update --init --recursive' or\n" " run 'git clone https://github.com/simdjson/simdjson.git' in 'src' directory to get simdjson library") endif() endif(simdjson_FOUND) endif(SIMDJSON_IS_APPLICABLE) ####################### # End of SIMDJSON dependency section ####################### if(PCM_BUILD_EXECUTABLES) foreach(PROJECT_NAME ${PROJECT_NAMES}) file(GLOB PROJECT_FILE ${PROJECT_NAME}.cpp) set(LIBS PCM_STATIC) add_executable(${PROJECT_NAME} ${PROJECT_FILE}) if(MSVC) target_compile_options(${PROJECT_NAME} PRIVATE "${PCM_MSVC_RUNTIME_LIBRARY_OPTIONS}") endif(MSVC) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "${PCM_STATIC_ASAN}") endif() # specific file for pcm-raw project if(${PROJECT_NAME} STREQUAL pcm-raw) set(LIBS ${LIBS} PCM_SIMDJSON) endif(${PROJECT_NAME} STREQUAL pcm-raw) if(${PROJECT_NAME} STREQUAL pcm-sensor-server) if(NO_SSL) message(STATUS "SSL is disabled") else() message(STATUS "Compiling with SSL support, requires libssl-dev or openssl-devel or libopenssl-devel or libopenssl-dev package installed") message(STATUS "To disable SSL support, use -DNO_SSL=1 option") find_package(OpenSSL ${MINIMUM_OPENSSL_VERSION} QUIET) if(OPENSSL_FOUND) message(STATUS "OpenSSL version ${OPENSSL_VERSION} >= ${MINIMUM_OPENSSL_VERSION}, OpenSSL support enabled") target_compile_options(${PROJECT_NAME} PRIVATE "-DUSE_SSL") set(LIBS ${LIBS} OpenSSL::SSL OpenSSL::Crypto) else() message(STATUS "OpenSSL support has been disabled, the version is less than ${MINIMUM_OPENSSL_VERSION}") endif() endif() file(READ pcm-sensor-server.service.in SENSOR_SERVICE_IN) string(REPLACE "@@CMAKE_INSTALL_SBINDIR@@" "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SBINDIR}" SENSOR_SERVICE "${SENSOR_SERVICE_IN}") file(WRITE "${CMAKE_BINARY_DIR}/pcm-sensor-server.service" "${SENSOR_SERVICE}") file(GLOB PROJECT_FILE ${PROJECT_NAME}.cpp pcm-accel-common.h pcm-accel-common.cpp) target_include_directories(pcm-sensor-server PUBLIC ${CMAKE_SOURCE_DIR}) if(LINUX_SYSTEMD) install(FILES "${CMAKE_BINARY_DIR}/pcm-sensor-server.service" DESTINATION "${LINUX_SYSTEMD_UNITDIR}") endif(LINUX_SYSTEMD) endif(${PROJECT_NAME} STREQUAL pcm-sensor-server) if(LINUX OR FREE_BSD) set(LIBS ${LIBS} Threads::Threads) install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_SBINDIR}) endif(LINUX OR FREE_BSD) if(APPLE) set(LIBS ${LIBS} Threads::Threads PcmMsr) install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_SBINDIR}) endif(APPLE) if(MSVC) target_compile_definitions(${PROJECT_NAME} PRIVATE _UNICODE UNICODE _CONSOLE) # for all, except pcm-lib and pcm-service endif(MSVC) target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS}) endforeach(PROJECT_NAME ${PROJECT_NAMES}) endif(PCM_BUILD_EXECUTABLES) ####################### # Install ####################### if(UNIX) # APPLE, LINUX, FREE_BSD if(LINUX) # Daemon & client file(GLOB DAEMON_SOURCES "daemon/*.cpp") add_executable(daemon ${DAEMON_SOURCES}) target_link_libraries(daemon PRIVATE PCM_STATIC Threads::Threads) set_target_properties(daemon PROPERTIES OUTPUT_NAME "pcm-daemon") install(TARGETS daemon DESTINATION ${CMAKE_INSTALL_SBINDIR}) file(GLOB CLIENT_SOURCES "client/*.cpp") add_executable(client ${CLIENT_SOURCES}) target_link_libraries(client PRIVATE Threads::Threads) set_target_properties(client PROPERTIES OUTPUT_NAME "pcm-client") install(TARGETS client DESTINATION ${CMAKE_INSTALL_BINDIR}) endif(LINUX) # Install extra files install(FILES pcm-bw-histogram.sh DESTINATION ${CMAKE_INSTALL_SBINDIR} RENAME pcm-bw-histogram PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) file(GLOB OPCODE_FILES "opCode*.txt") foreach(opcode_file ${OPCODE_FILES}) get_filename_component(opcode_file_name ${opcode_file} NAME) configure_file(${opcode_file} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${opcode_file_name} COPYONLY) install(FILES ${opcode_file} DESTINATION ${CMAKE_INSTALL_DATADIR}/pcm) endforeach(opcode_file ${OPCODE_FILES}) file(COPY "PMURegisterDeclarations" DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) install(DIRECTORY "PMURegisterDeclarations" DESTINATION ${CMAKE_INSTALL_DATADIR}/pcm) # Install docs install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/pcm) install(FILES ${CMAKE_SOURCE_DIR}/README.md DESTINATION ${CMAKE_INSTALL_DOCDIR}) file(GLOB DOC_FILES ${CMAKE_SOURCE_DIR}/doc/*.txt ${CMAKE_SOURCE_DIR}/doc/*.md) foreach(doc_file ${DOC_FILES}) get_filename_component(doc_file_name ${doc_file} NAME) configure_file(${doc_file} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${doc_file_name} COPYONLY) install(FILES ${doc_file} DESTINATION ${CMAKE_INSTALL_DOCDIR}) endforeach(doc_file ${DOC_FILES}) endif(UNIX) if(MSVC) file(GLOB OPCODE_FILES "opCode*.txt") foreach(opcode_file ${OPCODE_FILES}) add_custom_command(TARGET pcm-iio POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${opcode_file} $) endforeach(opcode_file ${OPCODE_FILES}) add_custom_command(TARGET pcm-raw POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "$/PMURegisterDeclarations") add_custom_command(TARGET pcm-raw POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/src/PMURegisterDeclarations" "$/PMURegisterDeclarations") add_custom_command(TARGET pcm-service POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PROJECT_SOURCE_DIR}/src/windows/pcm-service.exe.config" $) endif(MSVC) pcm-202502/src/MacMSRDriver/000077500000000000000000000000001475730356400154305ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/CMakeLists.txt000066400000000000000000000007431475730356400201740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2022, Intel Corporation set(CMAKE_MACOSX_RPATH 1) set(CMAKE_CXX_FLAGS "-Wall") set(CMAKE_CXX_FLAGS_RELEASE "-O3") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") file(GLOB LIB_FILES PCIDriverInterface.cpp MSRAccessor.cpp) find_library(IOKIT_LIBRARY IOKit) add_library(PcmMsr SHARED ${LIB_FILES}) target_link_libraries(PcmMsr PRIVATE ${IOKIT_LIBRARY}) add_subdirectory(PcmMsr) # Installation install(TARGETS PcmMsr DESTINATION "lib") pcm-202502/src/MacMSRDriver/MSRAccessor.cpp000066400000000000000000000117611475730356400202660ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #include "MSRAccessor.h" #include #include #include using namespace std; MSRAccessor::MSRAccessor() { service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching(kPcmMsrDriverClassName)); openConnection(); } int32_t MSRAccessor::buildTopology(uint32_t num_cores, void* pTopos) { size_t topology_struct_size = sizeof(TopologyEntry)*num_cores; kern_return_t ret = IOConnectCallStructMethod(connect, kBuildTopology, NULL, 0, pTopos, &topology_struct_size); return (ret == KERN_SUCCESS) ? 0 : -1; } int32_t MSRAccessor::read(uint32_t core_num, uint64_t msr_num, uint64_t * value) { pcm_msr_data_t idatas, odatas; size_t struct_size = sizeof(pcm_msr_data_t); idatas.msr_num = (uint32_t)msr_num; idatas.cpu_num = core_num; kern_return_t ret = IOConnectCallStructMethod(connect, kReadMSR, &idatas, struct_size, &odatas, &struct_size); if(ret == KERN_SUCCESS) { *value = odatas.value; return sizeof(uint64_t); } else { return -1; } } int32_t MSRAccessor::write(uint32_t core_num, uint64_t msr_num, uint64_t value){ pcm_msr_data_t idatas; idatas.value = value; idatas.msr_num = (uint32_t)msr_num; idatas.cpu_num = core_num; kern_return_t ret = IOConnectCallStructMethod(connect, kWriteMSR, &idatas, sizeof(pcm_msr_data_t), NULL, NULL); if(ret == KERN_SUCCESS) { return sizeof(uint64_t); } else { return -1; } } uint32_t MSRAccessor::getNumInstances() { kern_return_t kernResult; uint32_t output_count = 1; uint64_t knum_insts = 0; kernResult = IOConnectCallScalarMethod(connect, kGetNumInstances, NULL, 0, &knum_insts, &output_count); if (kernResult != KERN_SUCCESS) { cerr << "IOConnectCallScalarMethod returned 0x" << hex << setw(8) << kernResult << dec << endl; } // TODO add error handling; also, number-of-instance related // functions may go away as they do not appear to be used. return knum_insts; } uint32_t MSRAccessor::incrementNumInstances() { kern_return_t kernResult; uint32_t output_count = 1; uint64_t knum_insts = 0; kernResult = IOConnectCallScalarMethod(connect, kIncrementNumInstances, NULL, 0, &knum_insts, &output_count); if (kernResult != KERN_SUCCESS) { cerr << "IOConnectCallScalarMethod returned 0x" << hex << setw(8) << kernResult << dec << endl; } // TODO add error handling; also, these functions may go away as // they do not appear to be used. return knum_insts; } uint32_t MSRAccessor::decrementNumInstances() { kern_return_t kernResult; uint32_t output_count = 1; uint64_t knum_insts = 0; kernResult = IOConnectCallScalarMethod(connect, kDecrementNumInstances, NULL, 0, &knum_insts, &output_count); if (kernResult != KERN_SUCCESS) { cerr << "IOConnectCallScalarMethod returned 0x" << hex << setw(8) << kernResult << dec << endl; } // TODO add error handling; also, these functions may go away as // they do not appear to be used. return knum_insts; } MSRAccessor::~MSRAccessor() { closeConnection(); } kern_return_t MSRAccessor::openConnection() { kern_return_t kernResult = IOServiceOpen(service, mach_task_self(), 0, &connect); if (kernResult != KERN_SUCCESS) { cerr << "IOServiceOpen returned 0x" << hex << setw(8) << kernResult << dec << endl; } else { kernResult = IOConnectCallScalarMethod(connect, kOpenDriver, NULL, 0, NULL, NULL); if (kernResult != KERN_SUCCESS) { cerr << "kOpenDriver returned 0x" << hex << setw(8) << kernResult << dec << endl; } } return kernResult; } void MSRAccessor::closeConnection() { kern_return_t kernResult = IOConnectCallScalarMethod(connect, kCloseDriver, NULL, 0, NULL, NULL); if (kernResult != KERN_SUCCESS) { cerr << "kCloseDriver returned 0x" << hex << setw(8) << kernResult << dec << endl; } kernResult = IOServiceClose(connect); if (kernResult != KERN_SUCCESS) { cerr << "IOServiceClose returned 0x" << hex << setw(8) << kernResult << dec << endl; } } pcm-202502/src/MacMSRDriver/MSRAccessor.h000066400000000000000000000012551475730356400177300ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #include #include "PcmMsr/UserKernelShared.h" class MSRAccessor { private: io_service_t service; io_connect_t connect; kern_return_t openConnection(); void closeConnection(); public: MSRAccessor(); int32_t read(uint32_t cpu_num,uint64_t msr_num, uint64_t * value); int32_t write(uint32_t cpu_num, uint64_t msr_num, uint64_t value); int32_t buildTopology(uint32_t num_cores, void*); uint32_t getNumInstances(); uint32_t incrementNumInstances(); uint32_t decrementNumInstances(); ~MSRAccessor(); }; pcm-202502/src/MacMSRDriver/MSRKernel.h000066400000000000000000000006241475730356400174050ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #define PcmMsrDriverClassName com_intel_driver_PcmMsr #define kPcmMsrDriverClassName "com_intel_driver_PcmMsr" #ifndef MSR_KERNEL_SHARED #define MSR_KERNEL_SHARED #include typedef struct { uint64_t value; uint32_t cpu_num; uint32_t msr_num; } pcm_msr_data_t; #endif pcm-202502/src/MacMSRDriver/PCIDriverInterface.cpp000066400000000000000000000132671475730356400215550ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2013, Intel Corporation // written by Patrick Konsor // #include #include #include "PCIDriverInterface.h" #include #include "PcmMsr/UserKernelShared.h" io_connect_t PCIDriver_connect = 0; std::map PCIDriver_mmap; // setupDriver #ifdef __cplusplus extern "C" #endif int PCIDriver_setupDriver() { kern_return_t kern_result; io_iterator_t iterator; bool driverFound = false; io_service_t local_driver_service; // get services kern_result = IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching(kPcmMsrDriverClassName), &iterator); if (kern_result != KERN_SUCCESS) { fprintf(stderr, "[error] IOServiceGetMatchingServices returned 0x%08x\n", kern_result); return kern_result; } // find service while ((local_driver_service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { driverFound = true; break; } if (driverFound == false) { fprintf(stderr, "[error] No matching drivers found \"%s\".\n", kPcmMsrDriverClassName); return KERN_FAILURE; } IOObjectRelease(iterator); // connect to service kern_result = IOServiceOpen(local_driver_service, mach_task_self(), 0, &PCIDriver_connect); if (kern_result != KERN_SUCCESS) { fprintf(stderr, "[error] IOServiceOpen returned 0x%08x\n", kern_result); return kern_result; } return KERN_SUCCESS; } // read32 #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_read32(uint32_t addr, uint32_t* val) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } uint64_t input[] = { (uint64_t)addr }; uint64_t val_ = 0; uint32_t outputCnt = 1; kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kRead, input, 1, &val_, &outputCnt); *val = (uint32_t)val_; return result; } // read64 #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_read64(uint32_t addr, uint64_t* val) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } kern_return_t result; uint64_t input[] = { (uint64_t)addr }; uint64_t lo = 0; uint64_t hi = 0; uint32_t outputCnt = 1; result = IOConnectCallScalarMethod(PCIDriver_connect, kRead, input, 1, &lo, &outputCnt); input[0] = (uint64_t)addr + 4; result |= IOConnectCallScalarMethod(PCIDriver_connect, kRead, input, 1, &hi, &outputCnt); *val = (hi << 32) | lo; return result; } // write32 #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_write32(uint32_t addr, uint32_t val) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } uint64_t input[] = { (uint64_t)addr, (uint64_t)val }; return IOConnectCallScalarMethod(PCIDriver_connect, kWrite, input, 2, NULL, 0); } // write64 #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_write64(uint32_t addr, uint64_t val) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } kern_return_t result; uint64_t input[] = { (uint64_t)addr, val & 0xffffffff }; result = IOConnectCallScalarMethod(PCIDriver_connect, kWrite, input, 2, NULL, 0); input[0] = (uint64_t)addr + 4; input[1] = val >> 32; result |= IOConnectCallScalarMethod(PCIDriver_connect, kWrite, input, 2, NULL, 0); return result; } // mapMemory #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_mapMemory(uint32_t address, uint8_t** virtual_address) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } uint64_t input[] = { (uint64_t)address }; uint64_t output[2]; uint32_t outputCnt = 2; kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kMapMemory, input, 1, output, &outputCnt); PCIDriver_mmap[(uint8_t*)output[1]] = (void*)output[0]; *virtual_address = (uint8_t*)output[1]; return result; } // unmapMemory #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_unmapMemory(uint8_t* virtual_address) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } void* memory_map = PCIDriver_mmap[virtual_address]; if (memory_map != NULL) { uint64_t input[] = { (uint64_t)memory_map }; kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kUnmapMemory, input, 1, NULL, 0); PCIDriver_mmap.erase(virtual_address); // remove from map return result; } else { return KERN_INVALID_ADDRESS; } } // readMemory32 #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_readMemory32(uint8_t* address, uint32_t* val) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } uint64_t input[] = { (uint64_t)address }; uint64_t val_ = 0; uint32_t outputCnt = 1; kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kReadMemory, input, 1, &val_, &outputCnt); *val = (uint32_t)val_; return result; } // readMemory64 #ifdef __cplusplus extern "C" #endif uint32_t PCIDriver_readMemory64(uint8_t* address, uint64_t* val) { if (!PCIDriver_connect) { if (PCIDriver_setupDriver() != KERN_SUCCESS) { return KERN_FAILURE; } } kern_return_t result; uint64_t input[] = { (uint64_t)address }; uint64_t lo = 0; uint64_t hi = 0; uint32_t outputCnt = 1; result = IOConnectCallScalarMethod(PCIDriver_connect, kReadMemory, input, 1, &lo, &outputCnt); input[0] = (uint64_t)address + 4; result |= IOConnectCallScalarMethod(PCIDriver_connect, kReadMemory, input, 1, &hi, &outputCnt); *val = (hi << 32) | lo; return result; } pcm-202502/src/MacMSRDriver/PCIDriverInterface.h000066400000000000000000000022321475730356400212100ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2013, Intel Corporation // written by Patrick Konsor // #ifndef pci_driver_driverinterface_h #define pci_driver_driverinterface_h #ifdef __cplusplus extern "C" { #endif #define PCI_ENABLE 0x80000000 #define FORM_PCI_ADDR(bus,dev,fun,off) (((PCI_ENABLE)) | \ ((bus & 0xFF) << 16) | \ ((dev & 0x1F) << 11) | \ ((fun & 0x07) << 8) | \ ((off & 0xFF) << 0)) uint32_t PCIDriver_read32(uint32_t addr, uint32_t* val); uint32_t PCIDriver_read64(uint32_t addr, uint64_t* val); uint32_t PCIDriver_write32(uint32_t addr, uint32_t val); uint32_t PCIDriver_write64(uint32_t addr, uint64_t val); uint32_t PCIDriver_mapMemory(uint32_t address, uint8_t** virtual_address); uint32_t PCIDriver_unmapMemory(uint8_t* virtual_address); uint32_t PCIDriver_readMemory32(uint8_t* address, uint32_t* val); uint32_t PCIDriver_readMemory64(uint8_t* address, uint64_t* val); #ifdef __cplusplus } #endif #endif pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/000077500000000000000000000000001475730356400206255ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.pbxproj000066400000000000000000000426471475730356400237160ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 81ADBF0A156EBD73006D9B47 /* PcmMsrClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81ADBF09156EBD73006D9B47 /* PcmMsrClient.cpp */; }; 81ADBF1A156EEDB9006D9B47 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81ADBF0B156EDBA1006D9B47 /* IOKit.framework */; }; 81DEAF6315703531005E8EC6 /* MSRAccessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81ADBF1C156EFF69006D9B47 /* MSRAccessor.cpp */; }; 81DEAF6615703946005E8EC6 /* DriverInterface.c in Sources */ = {isa = PBXBuildFile; fileRef = 81DEAF6515703946005E8EC6 /* DriverInterface.c */; }; 81DEAF67157039F6005E8EC6 /* DriverInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 81ADBF17156EECDA006D9B47 /* DriverInterface.h */; }; 81DEAF68157039FB005E8EC6 /* MSRAccessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 81ADBF1B156EFF56006D9B47 /* MSRAccessor.h */; }; 81F91BC6156D9BF8007DD788 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 81F91BC4156D9BF8007DD788 /* InfoPlist.strings */; }; 81F91BC9156D9BF8007DD788 /* PcmMsr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81F91BC8156D9BF8007DD788 /* PcmMsr.cpp */; }; 895805FC1760E6E5006ED117 /* PCIDriverInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 895805FA1760E6E5006ED117 /* PCIDriverInterface.cpp */; }; 895805FD1760E6E5006ED117 /* PCIDriverInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 895805FB1760E6E5006ED117 /* PCIDriverInterface.h */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ 816FC6A5158296D200D9DEB4 /* PBXBuildRule */ = { isa = PBXBuildRule; compilerSpec = com.apple.compilers.proxy.script; fileType = pattern.proxy; isEditable = 1; outputFiles = ( ); }; /* End PBXBuildRule section */ /* Begin PBXCopyFilesBuildPhase section */ 816FC6A31582965F00D9DEB4 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/local/lib; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 81ADBF08156EBD65006D9B47 /* PcmMsrClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PcmMsrClient.h; sourceTree = ""; }; 81ADBF09156EBD73006D9B47 /* PcmMsrClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PcmMsrClient.cpp; sourceTree = ""; }; 81ADBF0B156EDBA1006D9B47 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 81ADBF0D156EDD11006D9B47 /* UserKernelShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = UserKernelShared.h; path = PcmMsr/UserKernelShared.h; sourceTree = ""; }; 81ADBF12156EEB93006D9B47 /* libPcmMsr.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libPcmMsr.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 81ADBF17156EECDA006D9B47 /* DriverInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DriverInterface.h; sourceTree = ""; }; 81ADBF1B156EFF56006D9B47 /* MSRAccessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSRAccessor.h; sourceTree = ""; }; 81ADBF1C156EFF69006D9B47 /* MSRAccessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MSRAccessor.cpp; sourceTree = ""; }; 81DEAF6515703946005E8EC6 /* DriverInterface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DriverInterface.c; sourceTree = ""; }; 81F91BBC156D9BF8007DD788 /* PcmMsrDriver.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PcmMsrDriver.kext; sourceTree = BUILT_PRODUCTS_DIR; }; 81F91BC3156D9BF8007DD788 /* PcmMsr-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PcmMsr-Info.plist"; sourceTree = ""; }; 81F91BC5156D9BF8007DD788 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 81F91BC7156D9BF8007DD788 /* PcmMsr.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PcmMsr.h; sourceTree = ""; }; 81F91BC8156D9BF8007DD788 /* PcmMsr.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PcmMsr.cpp; sourceTree = ""; }; 81F91BCA156D9BF8007DD788 /* PcmMsr-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PcmMsr-Prefix.pch"; sourceTree = ""; }; 895805FA1760E6E5006ED117 /* PCIDriverInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCIDriverInterface.cpp; sourceTree = ""; }; 895805FB1760E6E5006ED117 /* PCIDriverInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PCIDriverInterface.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 81ADBF0F156EEB93006D9B47 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 81ADBF1A156EEDB9006D9B47 /* IOKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 81F91BB7156D9BF8007DD788 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 81ADBF16156EECC6006D9B47 /* PcmMsrLibrary */ = { isa = PBXGroup; children = ( 81ADBF17156EECDA006D9B47 /* DriverInterface.h */, 81DEAF6515703946005E8EC6 /* DriverInterface.c */, 81ADBF1B156EFF56006D9B47 /* MSRAccessor.h */, 81ADBF1C156EFF69006D9B47 /* MSRAccessor.cpp */, 895805FA1760E6E5006ED117 /* PCIDriverInterface.cpp */, 895805FB1760E6E5006ED117 /* PCIDriverInterface.h */, 81F91BC1156D9BF8007DD788 /* PcmMsr */, ); name = PcmMsrLibrary; sourceTree = ""; }; 81F91BAF156D9BF8007DD788 = { isa = PBXGroup; children = ( 81ADBF0D156EDD11006D9B47 /* UserKernelShared.h */, 81ADBF16156EECC6006D9B47 /* PcmMsrLibrary */, 81F91BBE156D9BF8007DD788 /* Frameworks */, 81F91BBD156D9BF8007DD788 /* Products */, ); sourceTree = ""; }; 81F91BBD156D9BF8007DD788 /* Products */ = { isa = PBXGroup; children = ( 81F91BBC156D9BF8007DD788 /* PcmMsrDriver.kext */, 81ADBF12156EEB93006D9B47 /* libPcmMsr.dylib */, ); name = Products; sourceTree = ""; }; 81F91BBE156D9BF8007DD788 /* Frameworks */ = { isa = PBXGroup; children = ( 81ADBF0B156EDBA1006D9B47 /* IOKit.framework */, ); name = Frameworks; sourceTree = ""; }; 81F91BC1156D9BF8007DD788 /* PcmMsr */ = { isa = PBXGroup; children = ( 81F91BC7156D9BF8007DD788 /* PcmMsr.h */, 81F91BC8156D9BF8007DD788 /* PcmMsr.cpp */, 81F91BC2156D9BF8007DD788 /* Supporting Files */, 81ADBF08156EBD65006D9B47 /* PcmMsrClient.h */, 81ADBF09156EBD73006D9B47 /* PcmMsrClient.cpp */, ); path = PcmMsr; sourceTree = ""; }; 81F91BC2156D9BF8007DD788 /* Supporting Files */ = { isa = PBXGroup; children = ( 81F91BC3156D9BF8007DD788 /* PcmMsr-Info.plist */, 81F91BC4156D9BF8007DD788 /* InfoPlist.strings */, 81F91BCA156D9BF8007DD788 /* PcmMsr-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 81ADBF10156EEB93006D9B47 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 81DEAF67157039F6005E8EC6 /* DriverInterface.h in Headers */, 81DEAF68157039FB005E8EC6 /* MSRAccessor.h in Headers */, 895805FD1760E6E5006ED117 /* PCIDriverInterface.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 81F91BB8156D9BF8007DD788 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 81ADBF11156EEB93006D9B47 /* PcmMsrLibrary */ = { isa = PBXNativeTarget; buildConfigurationList = 81ADBF13156EEB93006D9B47 /* Build configuration list for PBXNativeTarget "PcmMsrLibrary" */; buildPhases = ( 81ADBF0E156EEB93006D9B47 /* Sources */, 81ADBF0F156EEB93006D9B47 /* Frameworks */, 81ADBF10156EEB93006D9B47 /* Headers */, 816FC6A31582965F00D9DEB4 /* CopyFiles */, ); buildRules = ( 816FC6A5158296D200D9DEB4 /* PBXBuildRule */, ); dependencies = ( ); name = PcmMsrLibrary; productName = PcmMsrLibrary; productReference = 81ADBF12156EEB93006D9B47 /* libPcmMsr.dylib */; productType = "com.apple.product-type.library.dynamic"; }; 81F91BBB156D9BF8007DD788 /* PcmMsrDriver */ = { isa = PBXNativeTarget; buildConfigurationList = 81F91BCD156D9BF8007DD788 /* Build configuration list for PBXNativeTarget "PcmMsrDriver" */; buildPhases = ( 81F91BB6156D9BF8007DD788 /* Sources */, 81F91BB7156D9BF8007DD788 /* Frameworks */, 81F91BB8156D9BF8007DD788 /* Headers */, 81F91BB9156D9BF8007DD788 /* Resources */, 81F91BBA156D9BF8007DD788 /* Rez */, ); buildRules = ( ); dependencies = ( ); name = PcmMsrDriver; productName = PcmMsr; productReference = 81F91BBC156D9BF8007DD788 /* PcmMsrDriver.kext */; productType = "com.apple.product-type.kernel-extension"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 81F91BB1156D9BF8007DD788 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0710; }; buildConfigurationList = 81F91BB4156D9BF8007DD788 /* Build configuration list for PBXProject "PcmMsr" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, ); mainGroup = 81F91BAF156D9BF8007DD788; productRefGroup = 81F91BBD156D9BF8007DD788 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 81F91BBB156D9BF8007DD788 /* PcmMsrDriver */, 81ADBF11156EEB93006D9B47 /* PcmMsrLibrary */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 81F91BB9156D9BF8007DD788 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 81F91BC6156D9BF8007DD788 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXRezBuildPhase section */ 81F91BBA156D9BF8007DD788 /* Rez */ = { isa = PBXRezBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXRezBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 81ADBF0E156EEB93006D9B47 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 81DEAF6315703531005E8EC6 /* MSRAccessor.cpp in Sources */, 81DEAF6615703946005E8EC6 /* DriverInterface.c in Sources */, 895805FC1760E6E5006ED117 /* PCIDriverInterface.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 81F91BB6156D9BF8007DD788 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 81F91BC9156D9BF8007DD788 /* PcmMsr.cpp in Sources */, 81ADBF0A156EBD73006D9B47 /* PcmMsrClient.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 81F91BC4156D9BF8007DD788 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 81F91BC5156D9BF8007DD788 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 81ADBF14156EEB93006D9B47 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_LOCATION = NO; EXECUTABLE_PREFIX = lib; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; INSTALL_PATH = /usr/lib; PRODUCT_NAME = PcmMsr; }; name = Debug; }; 81ADBF15156EEB93006D9B47 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_LOCATION = NO; EXECUTABLE_PREFIX = lib; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; INSTALL_PATH = /usr/lib; PRODUCT_NAME = PcmMsr; }; name = Release; }; 81F91BCB156D9BF8007DD788 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 13.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 81F91BCC156D9BF8007DD788 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 13.0; SDKROOT = macosx; }; name = Release; }; 81F91BCE156D9BF8007DD788 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1.0.0d1; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "PcmMsr/PcmMsr-Prefix.pch"; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; INFOPLIST_FILE = "PcmMsr/PcmMsr-Info.plist"; MODULE_NAME = com.intel.driver.PcmMsrDriver; MODULE_VERSION = 1.0.0d1; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.intel.driver.PcmMsr; PRODUCT_NAME = PcmMsrDriver; SDKROOT = macosx; VALID_ARCHS = x86_64; WRAPPER_EXTENSION = kext; }; name = Debug; }; 81F91BCF156D9BF8007DD788 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1.0.0d1; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "PcmMsr/PcmMsr-Prefix.pch"; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; INFOPLIST_FILE = "PcmMsr/PcmMsr-Info.plist"; MODULE_NAME = com.intel.driver.PcmMsrDriver; MODULE_VERSION = 1.0.0d1; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.intel.driver.PcmMsr; PRODUCT_NAME = PcmMsrDriver; SDKROOT = macosx; VALID_ARCHS = x86_64; WRAPPER_EXTENSION = kext; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 81ADBF13156EEB93006D9B47 /* Build configuration list for PBXNativeTarget "PcmMsrLibrary" */ = { isa = XCConfigurationList; buildConfigurations = ( 81ADBF14156EEB93006D9B47 /* Debug */, 81ADBF15156EEB93006D9B47 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 81F91BB4156D9BF8007DD788 /* Build configuration list for PBXProject "PcmMsr" */ = { isa = XCConfigurationList; buildConfigurations = ( 81F91BCB156D9BF8007DD788 /* Debug */, 81F91BCC156D9BF8007DD788 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 81F91BCD156D9BF8007DD788 /* Build configuration list for PBXNativeTarget "PcmMsrDriver" */ = { isa = XCConfigurationList; buildConfigurations = ( 81F91BCE156D9BF8007DD788 /* Debug */, 81F91BCF156D9BF8007DD788 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 81F91BB1156D9BF8007DD788 /* Project object */; } pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/000077500000000000000000000000001475730356400246235ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/contents.xcworkspacedata000066400000000000000000000002271475730356400315660ustar00rootroot00000000000000 pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/000077500000000000000000000000001475730356400267665ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/aiott.xcuserdatad/000077500000000000000000000000001475730356400324145ustar00rootroot00000000000000UserInterfaceState.xcuserstate000066400000000000000000002651511475730356400404030ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/aiott.xcuserdatadbplist00ÔX$versionX$objectsY$archiverT$top† ¯)*+,-./01EFGHIJKLMNOefghijklmnvwxy‚ƒŽ”ϧ¨ŠĒĢŦ­Ž¸ŊÃÄÅĖŌĶÔרŪßãįķôõö÷øųúûũū˙ !34:;AEFJMNQRVWcdefjkopvw}~ˆ‰Š‹ĄĸŖ¤Ĩϧ¨Š¯°¸ģŧŋĀÃÄĮČËĖĐÔØäåæįčéęëėíņõöū  #&*/37;IJKLMNTU[\bchpqrst|}~„‰‘’“›œĄ§Ŧ´ĩļžŋŅŌĶÔÕÖרęëėíîīđņųúûüũū˙$(*/0FGHIJKLMNOPSW[ghijko|}‚ƒ„‰•™Ÿ ĄĸĨąĩŧŊžÁÄĮĘÔÕÖרęëėíîīđņōøų˙ )*+,45;?IJKLRSYZ[agopqyz{ƒ„…†Š¤Ĩϧ¨ŠĒĢŦ­Ž¯ŋĀÁÂÃÄÅĶ×ÚŪáåčėīķöüũ  &'+0128<ABCIJNSTU]adhkuvwx}~€†‹”žŸ ĄĨϧ̝šŊĀÄĮËÎÜŨŪßāáâčéîö÷øų !"#$%&'()*+,-456789=CDFJPRVZ^bfjntuv€‚ƒ„…‰’“ĄĸŖ¤Ĩϧ­ŽŗģŧŊžÆĮČĖŅŌĶŨŪßāáåõö÷øųúûžÁÄĮĘÍĐĶÖŲÜßâåčëîņô÷úũ  !$'*-0369<?BEHKNQTWZ]`cfilorux{~„‡Š“–™œŸĸĨ¨ĢŽą´ˇēŊĀÃÆÉĖĪŌÕØÛŪáäįęíđķöųü˙          # & ) , / 2 5 8 ; > A D G J M P S V Y \ _ b e h k n q t w z } € ƒ † ‰ Œ  ’ • ˜ › ž Ą ¤ § Ē ­ ° ŗ ļ š ŧ ŋ Â Å Č Ë Î Ņ Ô × Ú Ũ ā ã æ é ė ī ō õ ø û ū          # $ ) 1 2 3 4 < = > B G H I S T U V W [ k l m n o p q u x : = @ C F I L O R U X [ ^ a d g j m p s v y |  ‚ … ˆ ‹ Ž ‘ ” — š    Ŗ Ļ Š Ŧ ¯ ˛ ĩ ¸ ģ ž Á Ä Į Ę Í Đ Ķ Ö Ų Ü ß â å č ë î ņ ô ÷ ú ũ         ! $ ' * - 0 3 6 9 < ? B E H K N Q T W Z ] ` c f i l o r u x { ~  „ ‡ Š   “ – ™ œ Ÿ ĸ Ĩ ¨ Ģ Ž ą ´ ˇ ē Ŋ Ā Ã Æ É Ė Ī Ō Õ Ø Û Ū á ä į ę í đ ķ ö ų ü ˙          # & ) , / 2 5 8 ; > A D G J M P S V Y \ _ b e h k n q t w { á å č ė ī ķ ö ú ũ  $'+.259<@CGJNQUX\_cfjmqtx{‚†‰”—›žĸĨŠŦ°ŗˇēžÁÅČĖĪĶÖÚŨáäčëīōöųũ  #'*6789:;<=IJKLMNO[\]ijkwxyz†‡ˆ”•–—˜™š›§¨ŠĩÄÅÆĮČÉĘÖרäåæįķôõö $%&'345ABCDPQRS_`abnopq}~‹ŒŽš›œŠĒĢŦ¸šēģĮČÉĘÖרŲåæįķôõö !"./0<=>?KLMNZ[\]ijklxyz{‡ˆ‰•–—˜™š›œ¨ŠĒšÅÆĮČÔÕÖ×ãäåæōķôõbfgklpquvz{€„…‰ŠŽ“”˜™žĸŖ§¨Ŧ­ą˛ļˇģŧĀÁÅÆĘËĪĐÔÕŲÚŪßãäčéíîōķ÷øüũ   &'*017=CDJPVW]cijpv|€ˆ‘•™ž¤ĒĢąˇ¸žÄĘĐÔÚŪäęđöü  "(.28>DEIORX^djpv€‚ƒ‡ˆ‰‘™š›œŖ¤Ĩ¯°ą˛šÃÄÅÆĮČÉĶÔÕÖāáâåõö÷øųúū!%&*+/0459:>?CDHIMNRSWX`abcdlmnowxyz‚ƒ„…Ž˜™šĸŖ¤Ĩ­Ž¯ˇ¸šÁÂÃËĖÍÎÛßâæéíđô÷ûū  U$nullĶ V$classWNS.keysZNS.objects€Oĸ€€ĸ€\_$44D36AD8-513B-4CD9-A6CA-53DE19A36F0F_IDEWorkspaceDocumentĶ  €<¨€€€€€ € € € ¨!"#$'$€ Z[€M€€€€M_>IDEWorkspaceTabController_2FB767B5-A143-4E05-88C9-42AED9476D06^IDEWindowFrame_!IDEOrderedWorkspaceTabControllers_IDEWindowInFullscreenMode_,IDEWorkspaceWindowControllerUniqueIdentifier_IDEActiveWorkspaceTabController_IDEWindowToolbarIsVisible_IDEWindowTabBarIsVisibleĶ 3<€<¨456789:;€€€€€€€€¨='?@AB$D€€€€Í€ÔO€M€A[IDETabLabel_IDEShowNavigator]IDEEditorArea_-IDEWorkspaceTabControllerUtilityAreaSplitView_IDENavigatorArea_,IDEWorkspaceTabControllerDesignAreaSplitView_IDEShowUtilities_AssistantEditorsLayout^IOUserClient.h Ķ Q[€<ŠRSTUVWXYZ€€€€€€€€ €!Š\]^'Dabc$€"€q€ €€A€Ž€Ä€Ė€M_IDEEditorMode_Genius_IDEEditorMode_StandardZlayoutTree]IDEShowEditorZEditorMode_IDEDefaultDebugArea_DebuggerSplitView_ DefaultPersistentRepresentations_ShowDebuggerAreaĶ ps€<ĸqr€#€$ĸtu€%€&]SplitPosition_%EditorLayout_PersistentRepresentation">˙…öĶ {~€<ĸ|}€'€(ĸ€€)€YYAlternateTMainĶ …‰€OŖ†‡ˆ€*€+€,ŖŠDŒ€-€A€W_)EditorLayout_StateSavingStateDictionaries_EditorLayout_Selected_EditorLayout_GeometryŌ ‘’€VĄ“€.Ķ –ž€<§—˜™š›œ€/€0€1€2€3€4€5§Ÿ ĄĸŖ¤Ĩ€6€7€F€P€Q€R€S\FileDataType_ArchivableRepresentation[EditorState_NavigableItemName_DocumentNavigableItemName_DocumentExtensionIdentifier[DocumentURL_public.c-headerÕ¯°ą ˛ŗ´ĩļD_DocumentLocation^IdentifierPath_DomainIdentifier_IndexOfDocumentIdentifier€B€8€€E€AŌ šē€@ĸģŧ€9€=Ķ ŋÁ€<ĄĀ€:ĄÂ€;_navigableItem_nameXPcmMsr.hŌÆĮČÉZ$classnameX$classes_NSMutableDictionaryŖČĘË\NSDictionaryXNSObjectĶ ÎЀ<ĄĪ€>ĄŅ€?Zidentifier_(Xcode.IDEKit.GeniusCategory.CounterpartsŌÆĮÕÖWNSArrayĸÕËĶŲ ÚĩÜŨYtimestamp[documentURL€€D€C_efile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.hŌÆĮāá_DVTDocumentLocationĸâË_DVTDocumentLocationŌÆĮäå_(IDENavigableItemArchivableRepresentationĸæË_(IDENavigableItemArchivableRepresentationĶ éî€O¤ęëėí€G€H€I€J¤īđ$ō€K€L€M€N_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#Aĩ´ŠųáqY{0, 1166}X{547, 0}ŌÆĮĘüĸĘËXPcmMsr.hXPcmMsr.h_&Xcode.IDEKit.EditorDocument.SourceCodeĶ ĩ[NS.relativeWNS.base€U€T€_efile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.hŌÆĮ UNSURLĸ ËUNSURLŌÆĮ  ^NSMutableArrayŖ ÕËŌ ‘€VĄ€X_{{0, 0}, {537, 861}}Ķ €OŖ†‡ˆ€*€+€,ŖD€Z€A€oŌ š€@Ą €[Ķ #+€<§—˜™š›œ€/€0€1€2€3€4€5§,-.//¤2€\€]€i€a€a€R€m_public.c-plus-plus-sourceÕ¯°ą ˛567ļD€g€_€^€E€A_/Xcode.IDENavigableItemDomain.WorkspaceStructureŌ š=€@Ŗ>?@€`€c€eŌ BC/ZIdentifier€b€aZPcmMsr.cppŌÆĮGH_IDEArchivableStringIndexPairĸIË_IDEArchivableStringIndexPairŌ BCL€b€dVPcmMsrŌ BCP€b€fVPcmMsrĶŲ ÚĩÜU€€D€h_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.cppĶ Y^€O¤ęëėí€G€H€I€J¤_`$b€j€k€M€l#Aĩ´Šųá€ÔY{0, 1702}W{35, 6}Ķ hĩ€U€n€_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.cppŌ šm€@Ąn€p_{{0, 0}, {1074, 861}}Ķ rt€<Ąs€rĄu€s_%EditorLayout_PersistentRepresentationĶ y{€<Ąz€tĄ|€uTMainĶ €„€OŖ‚ƒ€v€w€xŖ…D‡€y€A€ž_)EditorLayout_StateSavingStateDictionaries_EditorLayout_Selected_EditorLayout_GeometryŌ š€@ĄŽ€zĶ ‘™€<§’“”•–—˜€{€|€}€~€€€€§š›œžŸ €‚€ƒ€“€›€‡€œ€\FileDataType_ArchivableRepresentation[EditorState_NavigableItemName_DocumentNavigableItemName_DocumentExtensionIdentifier[DocumentURL_public.c-headerÕ¯°ą ˛ĒĢŦļD€€…€„€E€A_.Xcode.IDENavigableItemDomain.FrameworkFilePathŌ š˛€@Ĩŗ´ĩ†€ˆ€Š€Œ€ŽŌ BCž€b€‡^IOUserClient.hŌ BCž€b€‰UIOKitŌ BC€b€‹_Kernel.frameworkŌ BCƀb€ZFrameworksŌ BCʀb€ZMacOSX10.7ĶŲ ÚĩÜĪ€€D€‘Ō ŅŌĶYNS.string€’_Åfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOUserClient.hŌÆĮÕÖ_NSMutableStringŖÕ×ËXNSStringĶ Ú߀O¤ÛÜŨŪ€”€•€–€—¤āá$〘€™€M€š_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#AĩČ2z‹ûj\{5637, 2449}Z{5280, 45}_class IOUserClient_&Xcode.IDEKit.EditorDocument.SourceCodeĶ Īĩ€U€‘€Ō šķ€@Ąô€Ÿ_{{0, 0}, {1334, 861}}Ô÷ø ųúĩüũ_primaryEditorContextNode_geniusEditorContextNode_rootLayoutTreeNode€Ą€€­€ĒÖ˙ ×ũĩ_ documentArchivableRepresentation[orientationVparent[contentTypeXchildren€ĸ€Ē€Ŧ€Õ¯°ą ˛  ŦļD€Š€Ŗ€„€E€AŌ š€@Ĩ€¤€Ĩ€Ļ€§€¨Ō BCž€b€‡Ō BCž€b€‰Ō BC€b€‹Ō BCƀb€Ō BCʀb€ĶŲ ÚĩÜĪ€€D€‘Ö˙ ĩ×ĩ×.€€€Ŧ€ĢŌ š1€@Ąú€ĄŌÆĮ45_'IDEWorkspaceTabControllerLayoutTreeNodeĸ6Ë_'IDEWorkspaceTabControllerLayoutTreeNodeŌÆĮ89_#IDEWorkspaceTabControllerLayoutTreeĸ:Ë_#IDEWorkspaceTabControllerLayoutTreeĶ =C€<Ĩ>?@AB€¯€°€ą€˛€ŗĨDEFDH€´€ĩ€ˇ€´€š_LayoutFocusModeYVariablesWConsoleZLayoutMode_IDEDebugArea_SplitViewĶ PR€<ĄQ€ļĄD€A_VariablesViewSelectedScopeĶ WY€<ĄX€¸ĄD€A_ConsoleFilterModeĶ ^`€<Ą_€ēĄa€ģ_DVTSplitViewItemsŌ ‘e€Vĸfg€ŧ€ÁĶ jm€Oĸkl€Ŋ€žĸno€ŋ€Ā]DVTIdentifier_DVTViewMagnitude]VariablesView#@„ĀĶ vy€Oĸkl€Ŋ€žĸz{€Â€Ã[ConsoleArea#@„čĶ €‚€<Ą_€ēĄƒ€ÅŌ ‘†€Vĸ‡ˆ€Æ€ÉĶ ‹Ž€Oĸkl€Ŋ€žĸ€Į€ČYIDEEditor#@‰HĶ •˜€Oĸkl€Ŋ€žĸ™š€Ę€Ë_IDEDebuggerArea#@spĶ Ÿ €<  Ķ ŖĨ€<Ą_€ēĄĻ€ÎŌ ‘Š€VĸḔ΀ŌĶ Žą€Oĸkl€Ŋ€žĸ˛ŗ€Đ€ŅP#@ƒ Ķ ¸ģ€Oĸkl€Ŋ€žĸ˛Ŋ€Đ€Ķ#@o Ķ Áɀ<§ÂÃÄÅÆĮČ€Õ€Ö€×€Ø€Ų€Ú€Û§ĘËĖÍÎĪЀ܀í€ú€û%*F_Xcode.IDEKit.Navigator.Symbol_ Xcode.IDEKit.Navigator.BatchFind_SelectedNavigator_Xcode.IDEKit.Navigator.Issues_Xcode.IDEKit.Navigator.Debug_ Xcode.IDEKit.Navigator.Structure_Xcode.IDEKit.Navigator.LogsĶ Úâ€<§ÛÜŨŪßāá€Ũ€Ū€ß€ā€á€â€ã§ã'$'įč'€ä€€M€€ę€ë€_IDEExpandedItems_IDESymbolNavigatorShowHierarchy_$IDESymbolNavigatorShowContainersOnly_!IDESymbolNavigatorShowClassesOnly_IDESymbolNamePatternString_!IDESymbolNavigatorSelectedSymbols_#IDESymbolNavigatorShowWorkspaceOnlyŌ ‘ķ€VĨôõö÷ø€å€æ€į€č€é_c:@C@MSRAccessor_(c:@C@com_intel_driver_PcmMsr@C@MetaClass_.c:@C@com_intel_driver_PcmMsrClient@C@MetaClass_"c:@C@com_intel_driver_PcmMsrClient_c:@C@com_intel_driver_PcmMsrPŌ ‘€VĄ€ė_"c:@C@MSRAccessor@F@openConnection#Ķ €<§     €î€ī€đ€ņ€ō€ķ€ô§D$įD€A€M€ę€õ€ö€A€ø_#IDEBatchFindNavigatorScrollPosition_!IDEBatchFindNavigatorShowsOptions_"IDEBatchFindNavigatorReplaceString_IDEBatchFindNavigatorFindString_'IDEBatchFindNavigatorSelectedRowIndexes_IDEBatchFindNavigatorFindMode_$IDEBatchFindNavigatorCollapsedGroupsVmallocÔ ! "#XNSLength\NSRangeCountZNSLocation€÷ŌÆĮ%&ZNSIndexSetĸ'ËZNSIndexSetŌ ×)€ųŌÆĮ+,_NSMutableIndexSetŖ-.Ë_NSMutableIndexSetZNSIndexSet_ Xcode.IDEKit.Navigator.StructureĶ 2<€<Š3456789:;€ü€ũ€ū€˙Š$>?@$BC$E€M€M"#€M$_IDEErrorFilteringEnabled^IDEVisibleRect_IDECollapsedFiles_IDEExpandedIssues^IDEShowsByType_IDESelectedNavigables_IDECollapsedTypes_IDERecentFilteringEnabled_IDECollapsedGroups_{{0, 0}, {345, 945}}Ō QR ŌÆĮTU\NSMutableSetŖTVËUNSSetŌ QYĄZ Ķ ]b€<¤^_`a    ¤cdef_documentLocations[fullMessageTtypeYsubissuesŌ šm€@ĄnŲpqŲrs tÚuv×ĩxxyz{z_characterRangeLoc_characterRangeLen_endingColumnNumber_startingColumnNumber_startingLineNumber_endingLineNumberc€߁_‡file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/usr/include/stdlib.hŌÆĮ~_DVTTextDocumentLocationŖ€Ë_DVTTextDocumentLocation_DVTDocumentLocation_DFunctions that differ only in their return type cannot be overloaded^Semantic IssueŌ ‘†€Vĸ‡ˆĶ ‹€<¤^_`a    ¤‘’“”Ō š—€@Ą˜ŲpqŲrs tÚuš×ĩœœyž"€ _gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.cpp_pIn file included from /Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.cpp:2:_Clang LLVM 1.0 NoticeŌ ‘¤€V Ķ §Ŧ€<¤^_`a    ¤­Ž“° !Ō šŗ€@Ą´ŲpqŲrs tÚuļ×ĩ¸¸yēģē €_ˇfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/libkern/libkern.h_Previous declaration is hereŌ ‘Ā€V Ō ‘ÀV Ō QƁ Ō QɁ Ķ ĖЀ<ŖÍÎ΁&'(ŖŅD$)€A€M_IDEStackCompressionValue_IDEThreadOrQueueMode_IDEShowOnlyInterestingContentĶ Úâ€<§ÛÜŨŪßāá+,-./01§ã$åæ$$é2€M3<€M€MD^IDEVisibleRect_"IDEUnsavedDocumentFilteringEnabled_IDESelectedTree_IDEExpandedItemsTree_IDESCMStatusFilteringEnabled_!IDERecentDocumentFilteringEnabled_,IDENavigatorExpandedItemsBeforeFilteringTree_{{0, 0}, {345, 817}}Ķ ôö€OĄõ4Ą÷5_IDEValuesAsTreeĶ ûũ€<Ąü6Ąū7VPcmMsrĶ €<Ą8Ą9VPcmMsrĶ   €<Ą :Ą ;_PcmMsrClient.cppPĶ €OĄõ4Ą=Ķ €<Ąü6Ą>Ķ #€<Ĩ  !";8?@AĨ %   ;B;;;]PcmMsrLibraryXProductsZFrameworksĶ .1€<ĸ 0;Cĸ  ;;_Supporting FilesĶ 79€OĄõ4Ą:EĶ =>€<  Ķ AE€<ŖBCDGHIŖ$GH€MJN_#IDELogNavigatorRecentFilterStateKey_&IDELogNavigatorSelectedObjectsStateKey_"IDELogNavigatorVisibleRectStateKeyĶ NP€OĄOKĄQL_IDEValuesAsTreeĶ UW€<ĄVMĄį€ę_ Build PcmMsrLibrary : 1:22:27 PM_{{0, 0}, {259, 825}}Ķ ]_€<Ą_€ēĄ`PŌ ‘c€VŖdefQTWĶ il€Oĸkl€Ŋ€žĸmnRS_IDENavigatorArea#@u Ķ sv€Oĸkl€Ŋ€žĸwxUV]IDEEditorArea#@”ØĶ }€€Oĸkl€Ŋ€žĸ‚XY_IDEUtilitiesArea#@p@_{{0, 91}, {1680, 937}}Ō šˆ€@Ą€Ķ Œ˜€<ĢŽ‘’“”•–—]^_`abcdefgĢ'šDœžŸ Ą$$€h€A‹“ą˛ō€M€M_BreakpointsActivated_DefaultEditorStatesForURLs_DebuggingWindowBehavior_ActiveRunDestination\ActiveScheme_0LastCompletedPersistentSchemeBasedActivityReport_DocumentWindows_DefaultEditorFrameSizeForURLs_RecentEditorDocumentURLs_AppFocusInMiniDebugging_MiniDebuggingConsoleĶ ą¸€<Ϟŗ´ĩijklmnĻšēģŧŊžo˜ŗāđ_IDEQuickLookEditor.Editor_'Xcode.IDEKit.EditorDocument.PlistEditor_7Xcode.Xcode3ProjectSupport.EditorDocument.Xcode3Project_&Xcode.IDEKit.EditorDocument.SourceCode_'Xcode.IDEKit.EditorDocument.LogDocument_,Xcode.IDEKit.EditorDocument.ASCIIPlistEditorĶ Į̀<ĨČÉĘËˁprtvxĨÎĪĐŅԁz‡’Ķ Õĩ€Uq€Ō ŅŌŲ€’_file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-ccpdocwrovhbjackmzaqtolttprt/Build/Products/Debug/libPcmMsrLibrary.dylibĶ Üĩ€Us€Ō ŅŌ‒_file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-cwyrljtdvuljlketlkxmiygymebv/Build/Products/Debug/libPcmMsrLibrary.dylibĶ ãĩ€Uu€Ō ŅŌဒ_Œfile://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-cwyrljtdvuljlketlkxmiygymebv/Build/Products/Debug/liblibPcmMsr.dylibĶ ęĩ€Uw€Ō ŅŌ_‰file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-cwyrljtdvuljlketlkxmiygymebv/Build/Products/Debug/libPcmMsr.dylibĶ ņĩ€Uy€Ō ŅŌõ€’_‰file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-admpducbfnneumeyynvfwmnhhodt/Build/Products/Debug/libPcmMsr.dylibĶ øú€<Ąų{Ąû|_SelectedDocumentLocationsŌ š˙€@Ą}ÔŲ ÚD_IDEQuickLookPageNumber€~€A_file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-ccpdocwrovhbjackmzaqtolttprt/Build/Products/Debug/libPcmMsrLibrary.dylib#Aĩy_—ŧŌÆĮ  _IDEQuickLookDocumentLocationŖ  Ë_IDEQuickLookDocumentLocation_DVTDocumentLocationĶ €<Ą‚Ąƒ_SelectedDocumentLocationsŌ š€@Ą„ÔŲ ÚD†€…€A_file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-cwyrljtdvuljlketlkxmiygymebv/Build/Products/Debug/libPcmMsrLibrary.dylib#AĩŖjHT3čĶ "$€<Ą#ˆĄ%‰_SelectedDocumentLocationsŌ š)€@Ą*ŠÔŲ Ú,.DŒ€‹€A_Œfile://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-cwyrljtdvuljlketlkxmiygymebv/Build/Products/Debug/liblibPcmMsr.dylib#AĩĢN2lū7Ķ 46€<Ą#ˆĄ7ŽŌ š:€@Ą;ÔŲ Ú=?D‘€€A_‰file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-cwyrljtdvuljlketlkxmiygymebv/Build/Products/Debug/libPcmMsr.dylib#AĩĢOî 'Ķ EG€<ĄF“ĄH”_SelectedDocumentLocationsŌ šL€@ĄM•ÔŲ ÚOQD—€–€A_‰file://localhost/Users/aiott/Library/Developer/Xcode/DerivedData/PcmMsr-admpducbfnneumeyynvfwmnhhodt/Build/Products/Debug/libPcmMsr.dylib#Aĩ¯—Æéu°Ķ WZ€<ĸXY™›ĸ[\ĒĶ _ĩ€Uš€Ō ŅŌc€’_Dfile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsr-Info.plistĶ fĩ€Uœ€Ō ŅŌj€’_Sfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsr-Info.plistĶ mq€<ŖnopžŸ ŖrstĄ¤Ĩ_IDE_PLIST_EDITOR_SELECTION_KEY_ IDE_PLIST_EDITOR_VISIBLERECT_KEY_IDE_PLIST_EDITOR_EXPANSION_KEYŌ šz€@ĸ{|ĸŖ_IOKitPersonalities\PcmMsrClient_{{0, 0}, {1090, 832}}Ō Q‚Ŗƒ„…ρ§ŠŌ ‘ˆ€Vĸ{|ĸŖŌ ‘€VĄށ¨_OSBundleLibrariesŌ ‘’€VĄ{ĸĶ –š€<Ŗ—˜™́Ŧ­Ŗ›œށ°ą_IDE_PLIST_EDITOR_SELECTION_KEY_ IDE_PLIST_EDITOR_VISIBLERECT_KEY_IDE_PLIST_EDITOR_EXPANSION_KEYŌ šŖ€@Ą¤¯_OSBundleLibraries_{{0, 0}, {963, 806}}Ō QЁĄǁ˛Ō ‘­€VĄ¤¯Ķ ąĩ€<Ŗ˛ŗ´´ļ¸Ŗļˇ¸ēųßĶ ģĩ€Uĩ€Ō ŅŌŋ€’_?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩϧ¨ŠĒĢŦ­Ž¯°ą˛ŗ´ĩšēģŧŊ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Ё‹Œށ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩρ§¨Ёǁ́Ŧ­ށ¯°ą˛ŗ´ĩļˇ¸šēģŧŊžŋÁÁāŁƁĮȁɁʁˁˁ́΁΁Ёҁԁ́ԁՁցׁ؁؁ځÛŌ ŅŌĀ€’_Architectures||ADDITIONAL_SDKSŌ ŅŌÀ’_Architectures||ARCHSŌ ŅŌƀ’_Architectures||SDKROOTŌ ŅŌɀ’_#Architectures||SUPPORTED_PLATFORMSŌ ŅŌĖ€’_Architectures||VALID_ARCHSŌ ŅŌĪ€’_Build Locations||SYMROOTŌ ŅŌŌ€’_Build Locations||OBJROOTŌ ŅŌՀ’_%Build Locations||SHARED_PRECOMPS_DIRŌ ŅŌ؀’_Build Options||BUILD_VARIANTSŌ ŅŌۀ’_Build Options||GCC_VERSIONŌ ŅŌŪ€’_(Build Options||DEBUG_INFORMATION_FORMATŌ ŅŌဒ_'Build Options||GENERATE_PROFILING_CODEŌ ŅŌ䀒_@Build Options||PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIRŌ ŅŌဒ_)Build Options||RUN_CLANG_STATIC_ANALYZERŌ ŅŌꀒ_2Build Options||SCAN_ALL_SOURCE_FILES_FOR_INCLUDESŌ ŅŌ퀒_ Build Options||VALIDATE_PRODUCTŌ ŅŌđ€’_%Code Signing||CODE_SIGN_ENTITLEMENTSŌ ŅŌ퀒_!Code Signing||CODE_SIGN_IDENTITYŌ ŅŌö€’_,Code Signing||CODE_SIGN_RESOURCE_RULES_PATHŌ ŅŌų€’_$Code Signing||OTHER_CODE_SIGN_FLAGSŌ ŅŌü€’_Deployment||STRIPFLAGSŌ ŅŌ˙€’_Deployment||ALTERNATE_GROUPŌ ŅŌ€’_Deployment||ALTERNATE_OWNERŌ ŅŌ€’_Deployment||ALTERNATE_MODEŌ ŅŌ€’_(Deployment||ALTERNATE_PERMISSIONS_FILESŌ ŅŌ €’_!Deployment||COMBINE_HIDPI_IMAGESŌ ŅŌ€’_ Deployment||DEPLOYMENT_LOCATIONŌ ŅŌ€’_&Deployment||DEPLOYMENT_POSTPROCESSINGŌ ŅŌ€’_Deployment||INSTALL_GROUPŌ ŅŌ€’_Deployment||INSTALL_OWNERŌ ŅŌ€’_Deployment||INSTALL_MODE_FLAGŌ ŅŌ€’_Deployment||DSTROOTŌ ŅŌ €’_Deployment||INSTALL_PATHŌ ŅŌ#€’_%Deployment||MACOSX_DEPLOYMENT_TARGETŌ ŅŌ&€’_%Deployment||PRODUCT_DEFINITION_PLISTŌ ŅŌ)€’_Deployment||SKIP_INSTALLŌ ŅŌ,€’_$Deployment||STRIP_INSTALLED_PRODUCTŌ ŅŌ/€’_Deployment||STRIP_STYLEŌ ŅŌ2€’_Deployment||SEPARATE_STRIPŌ ŅŌ5€’_Kernel Module||MODULE_NAMEŌ ŅŌ8€’_Kernel Module||MODULE_STARTŌ ŅŌ;€’_Kernel Module||MODULE_STOPŌ ŅŌ>€’_Kernel Module||MODULE_VERSIONŌ ŅŌA€’_Linking||BUNDLE_LOADERŌ ŅŌD€’_%Linking||DYLIB_COMPATIBILITY_VERSIONŌ ŅŌG€’_Linking||DYLIB_CURRENT_VERSIONŌ ŅŌJ€’_Linking||DEAD_CODE_STRIPPINGŌ ŅŌM€’_'Linking||LINKER_DISPLAYS_MANGLED_NAMESŌ ŅŌP€’_Linking||LD_NO_PIEŌ ŅŌS€’_,Linking||PRESERVE_DEAD_CODE_INITS_AND_TERMSŌ ŅŌV€’_Linking||LD_DYLIB_INSTALL_NAMEŌ ŅŌY€’_Linking||EXPORTED_SYMBOLS_FILEŌ ŅŌ\€’_Linking||INIT_ROUTINEŌ ŅŌ_€’_&Linking||LINK_WITH_STANDARD_LIBRARIESŌ ŅŌb€’_Linking||MACH_O_TYPEŌ ŅŌe€’_Linking||ORDER_FILEŌ ŅŌh€’_Linking||OTHER_LDFLAGSŌ ŅŌk€’_%Linking||GENERATE_MASTER_OBJECT_FILEŌ ŅŌn€’_Linking||PRELINK_LIBSŌ ŅŌq€’_Linking||KEEP_PRIVATE_EXTERNSŌ ŅŌt€’_!Linking||LD_RUNPATH_SEARCH_PATHSŌ ŅŌw€’_Linking||SEPARATE_SYMBOL_EDITŌ ŅŌz€’_Linking||PRELINK_FLAGSŌ ŅŌ}€’_Linking||SECTORDER_FLAGSŌ ŅŌ€€’_!Linking||UNEXPORTED_SYMBOLS_FILEŌ ŅŌƒ€’_Linking||WARNING_LDFLAGSŌ ŅŌ†€’_Linking||LD_GENERATE_MAP_FILEŌ ŅŌ‰€’_%Packaging||APPLY_RULES_IN_COPY_FILESŌ ŅŌŒ€’_ Packaging||EXECUTABLE_EXTENSIONŌ ŅŌ€’_Packaging||EXECUTABLE_PREFIXŌ ŅŌ’€’_+Packaging||INFOPLIST_EXPAND_BUILD_SETTINGSŌ ŅŌ•€’_!Packaging||GENERATE_PKGINFO_FILEŌ ŅŌ˜€’_Packaging||FRAMEWORK_VERSIONŌ ŅŌ›€’_Packaging||INFOPLIST_FILEŌ ŅŌž€’_.Packaging||INFOPLIST_OTHER_PREPROCESSOR_FLAGSŌ ŅŌĄ€’_#Packaging||INFOPLIST_OUTPUT_FORMATŌ ŅŌ¤€’_.Packaging||INFOPLIST_PREPROCESSOR_DEFINITIONSŌ ŅŌ§€’_#Packaging||INFOPLIST_PREFIX_HEADERŌ ŅŌĒ€’_ Packaging||INFOPLIST_PREPROCESSŌ ŅŌ­€’_&Packaging||COPYING_PRESERVES_HFS_DATAŌ ŅŌ°€’_'Packaging||PRIVATE_HEADERS_FOLDER_PATHŌ ŅŌŗ€’_Packaging||PRODUCT_NAMEŌ ŅŌ_$Packaging||PLIST_FILE_OUTPUT_FORMATŌ ŅŌš€’_&Packaging||PUBLIC_HEADERS_FOLDER_PATHŌ ŅŌŧ€’_(Packaging||STRINGS_FILE_OUTPUT_ENCODINGŌ ŅŌŋ€’_Packaging||WRAPPER_EXTENSIONŌ ŅŌ€’_'Search Paths||ALWAYS_SEARCH_USER_PATHSŌ ŅŌŀ’_%Search Paths||FRAMEWORK_SEARCH_PATHSŌ ŅŌȀ’_"Search Paths||HEADER_SEARCH_PATHSŌ ŅŌˀ’_#Search Paths||LIBRARY_SEARCH_PATHSŌ ŅŌ΀’_Search Paths||REZ_SEARCH_PATHSŌ ŅŌŅ€’_<Search Paths||EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ŅŌԀ’_<Search Paths||INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ŅŌ׀’_'Search Paths||USER_HEADER_SEARCH_PATHSŌ ŅŌڀ’_Unit Testing||OTHER_TEST_FLAGSŌ ŅŌŨ€’_Unit Testing||TEST_AFTER_BUILDŌ ŅŌ‒_Unit Testing||TEST_HOSTŌ ŅŌ〒_Unit Testing||TEST_RIGŌ ŅŌ怒_$Versioning||CURRENT_PROJECT_VERSIONŌ ŅŌ递_Versioning||VERSION_INFO_FILEŌ ŅŌ뀒_%Versioning||VERSION_INFO_EXPORT_DECLŌ ŅŌ_ Versioning||VERSION_INFO_PREFIXŌ ŅŌō€’_ Versioning||VERSION_INFO_SUFFIXŌ ŅŌõ€’_Versioning||VERSIONING_SYSTEMŌ ŅŌø€’_!Versioning||VERSION_INFO_BUILDERŌ ŅŌû€’_AApple LLVM compiler 3.1 - Code Generation||GCC_FAST_OBJC_DISPATCHŌ ŅŌū€’_HApple LLVM compiler 3.1 - Code Generation||CLANG_X86_VECTOR_INSTRUCTIONSŌ ŅŌ €’_>Apple LLVM compiler 3.1 - Code Generation||GCC_STRICT_ALIASINGŌ ŅŌ €’_IApple LLVM compiler 3.1 - Code Generation||GCC_GENERATE_DEBUGGING_SYMBOLSŌ ŅŌ €’_=Apple LLVM compiler 3.1 - Code Generation||GCC_DYNAMIC_NO_PICŌ ŅŌ €’_KApple LLVM compiler 3.1 - Code Generation||GCC_GENERATE_TEST_COVERAGE_FILESŌ ŅŌ €’_IApple LLVM compiler 3.1 - Code Generation||GCC_INLINES_ARE_PRIVATE_EXTERNŌ ŅŌ €’_KApple LLVM compiler 3.1 - Code Generation||GCC_INSTRUMENT_PROGRAM_FLOW_ARCSŌ ŅŌ €’_HApple LLVM compiler 3.1 - Code Generation||GCC_ENABLE_KERNEL_DEVELOPMENTŌ ŅŌ €’_3Apple LLVM compiler 3.1 - Code Generation||LLVM_LTOŌ ŅŌ €’_Apple LLVM compiler 3.1 - Language||GCC_ENABLE_OBJC_EXCEPTIONSŌ ŅŌ L€’_8Apple LLVM compiler 3.1 - Language||GCC_ENABLE_TRIGRAPHSŌ ŅŌ O€’_KApple LLVM compiler 3.1 - Language||GCC_ENABLE_FLOATING_POINT_LIBRARY_CALLSŌ ŅŌ R€’_CApple LLVM compiler 3.1 - Language||GCC_USE_INDIRECT_FUNCTION_CALLSŌ ŅŌ U€’_CApple LLVM compiler 3.1 - Language||GCC_USE_REGISTER_FUNCTION_CALLSŌ ŅŌ X€’_KApple LLVM compiler 3.1 - Language||GCC_INCREASE_PRECOMPILED_HEADER_SHARINGŌ ŅŌ [€’_9Apple LLVM compiler 3.1 - Language||CLANG_ENABLE_OBJC_ARCŌ ŅŌ ^€’_6Apple LLVM compiler 3.1 - Language||GCC_ENABLE_OBJC_GCŌ ŅŌ a€’_0Apple LLVM compiler 3.1 - Language||OTHER_CFLAGSŌ ŅŌ d€’_8Apple LLVM compiler 3.1 - Language||OTHER_CPLUSPLUSFLAGSŌ ŅŌ g€’_@Apple LLVM compiler 3.1 - Language||GCC_PRECOMPILE_PREFIX_HEADERŌ ŅŌ j€’_5Apple LLVM compiler 3.1 - Language||GCC_PREFIX_HEADERŌ ŅŌ m€’_@Apple LLVM compiler 3.1 - Language||GCC_ENABLE_BUILTIN_FUNCTIONSŌ ŅŌ p€’_=Apple LLVM compiler 3.1 - Language||GCC_ENABLE_PASCAL_STRINGSŌ ŅŌ s€’_=Apple LLVM compiler 3.1 - Language||GCC_FORCE_CPU_SUBTYPE_ALLŌ ŅŌ v€’_3Apple LLVM compiler 3.1 - Language||GCC_SHORT_ENUMSŌ ŅŌ y€’_FApple LLVM compiler 3.1 - Language||GCC_USE_STANDARD_INCLUDE_SEARCHINGŌ ŅŌ |€’_ZApple LLVM compiler 3.1 - Preprocessing||GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPSŌ ŅŌ €’_DApple LLVM compiler 3.1 - Warnings||GCC_WARN_CHECK_SWITCH_STATEMENTSŌ ŅŌ ‚€’_EApple LLVM compiler 3.1 - Warnings||CLANG_WARN__EXIT_TIME_DESTRUCTORSŌ ŅŌ …€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_FOUR_CHARACTER_CONSTANTSŌ ŅŌ ˆ€’_3Apple LLVM compiler 3.1 - Warnings||GCC_WARN_SHADOWŌ ŅŌ ‹€’_NApple LLVM compiler 3.1 - Warnings||CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIESŌ ŅŌ Ž€’_DApple LLVM compiler 3.1 - Warnings||GCC_WARN_64_TO_32_BIT_CONVERSIONŌ ŅŌ ‘€’_GApple LLVM compiler 3.1 - Warnings||CLANG_WARN_IMPLICIT_SIGN_CONVERSIONŌ ŅŌ ”€’_FApple LLVM compiler 3.1 - Warnings||GCC_WARN_ALLOW_INCOMPLETE_PROTOCOLŌ ŅŌ —€’_AApple LLVM compiler 3.1 - Warnings||GCC_WARN_INHIBIT_ALL_WARNINGSŌ ŅŌ š€’_LApple LLVM compiler 3.1 - Warnings||GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETEDŌ ŅŌ €’_>Apple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_RETURN_TYPEŌ ŅŌ  €’_@Apple LLVM compiler 3.1 - Warnings||GCC_WARN_MISSING_PARENTHESESŌ ŅŌ Ŗ€’_MApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERSŌ ŅŌ Ļ€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_MISSING_PROTOTYPESŌ ŅŌ Š€’_BApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_MISSING_NEWLINEŌ ŅŌ Ŧ€’_SApple LLVM compiler 3.1 - Warnings||GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTORŌ ŅŌ ¯€’_CApple LLVM compiler 3.1 - Warnings||GCC_WARN_NON_VIRTUAL_DESTRUCTORŌ ŅŌ ˛€’_=Apple LLVM compiler 3.1 - Warnings||CLANG_WARN_OBJCPP_ARC_ABIŌ ŅŌ ĩ€’_2Apple LLVM compiler 3.1 - Warnings||WARNING_CFLAGSŌ ŅŌ ¸€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONSŌ ŅŌ ģ€’_NApple LLVM compiler 3.1 - Warnings||CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONSŌ ŅŌ ž€’_5Apple LLVM compiler 3.1 - Warnings||GCC_WARN_PEDANTICŌ ŅŌ Á€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_POINTER_SIGNEDNESSŌ ŅŌ Ä€’_9Apple LLVM compiler 3.1 - Warnings||GCC_WARN_SIGN_COMPAREŌ ŅŌ Į€’_BApple LLVM compiler 3.1 - Warnings||GCC_WARN_STRICT_SELECTOR_MATCHŌ ŅŌ Ę€’_MApple LLVM compiler 3.1 - Warnings||CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSIONŌ ŅŌ Í€’_ZApple LLVM compiler 3.1 - Warnings||GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORSŌ ŅŌ Đ€’_VApple LLVM compiler 3.1 - Warnings||GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORSŌ ŅŌ Ķ€’_@Apple LLVM compiler 3.1 - Warnings||GCC_TREAT_WARNINGS_AS_ERRORSŌ ŅŌ Ö€’_FApple LLVM compiler 3.1 - Warnings||GCC_WARN_TYPECHECK_CALLS_TO_PRINTFŌ ŅŌ Ų€’_@Apple LLVM compiler 3.1 - Warnings||GCC_WARN_UNDECLARED_SELECTORŌ ŅŌ Ü€’_@Apple LLVM compiler 3.1 - Warnings||GCC_WARN_UNINITIALIZED_AUTOSŌ ŅŌ ß€’_Static Analyzer - Checkers||CLANG_ANALYZER_DEADCODE_DEADSTORESŌ ŅŌ ũ€’_9Static Analyzer - Checkers||CLANG_ANALYZER_OBJC_SELF_INITŌ ‘ €VĄ ŨŌ ŅŌ €’_Linking||LD_DYLIB_INSTALL_NAMEĶ   €<  Ķ  €<Ĩ   āáâãäĨ     åæīđÃ_-Xcode3ProjectEditorPreviousProjectEditorClass_(Xcode3ProjectEditor.sourceList.splitview_,Xcode3ProjectEditorPreviousTargetEditorClass_,Xcode3ProjectEditorSelectedDocumentLocations_-Xcode3ProjectEditor_Xcode3BuildSettingsEditor_Xcode3BuildSettingsEditorĶ   !€<Ą įĄ "č_DVTSplitViewItemsŌ ‘ &€Vĸ ' (éíĶ + .€Oĸ , -ęëĸį 0€ęė]DVTIdentifier_DVTViewMagnitude#@e@Ķ 6 9€Oĸ , -ęëĸį ;€ęî#@Œ@_Xcode3BuildSettingsEditorŌ š @€@Ą AņÔŲ oÚ Cq E Fķ÷ôō_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr.xcodeproj/#Aĩ´Ū`ã…/Ķ  K O€<Ŗy M NđõöŖ P Q R÷øųVTarget_"Xcode3BuildSettingsEditorLocations_Xcode3BuildSettingsEditor\PcmMsrDriverŌ š Y€@Ą ZúĶ  ] d€<Ļ ^ _ ` a b cûüũū˙ĻD fDD iD€´€A€A€A_"Xcode3BuildPropertyNameDisplayMode_Selected Build Properties_$Xcode3BuildSettingsEditorDisplayMode_#Xcode3BuildPropertyValueDisplayMode_#Collapsed Build Property Categories_Xcode3BuildSettingsEditorModeŌ ‘ s€VĄ tŌ ŅŌ w€’_Kernel Module||MODULE_NAMEŌ ‘ z€V¯ŋ { | } ~  €  ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ  Ž   ‘ ’ “ ” • – — ˜ ™ š › œ  ž Ÿ   Ą ĸ Ŗ ¤ Ĩ Ļ § ¨ Š Ē Ģ Ŧ ­ Ž ¯ ° ą ˛ ŗ ´ ĩ ļ ˇ ¸ š ē ģ ŧ Ŋ ž ŋ Ā Á Â Ã Ä Å Æ Į Č É Ę Ë Ė Í Î Ī Đ Ņ Ō Ķ Ô Õ Ö × Ø Ų Ú Û Ü Ũ Ū ß ā á â ã ä å æ į č é ę ë ė í î ī đ ņ ō ķ ô õ ö ÷ ø ų ú û ü ũ ū ˙                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Ё‹Œށ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩρ§¨Ёǁ́Ŧ­ށ¯°ą˛ŗ´ĩļˇ¸šēģŧŊžŋÁÂŌ ŅŌ <€’_Architectures||ADDITIONAL_SDKSŌ ŅŌ ?€’_Architectures||ARCHSŌ ŅŌ B€’_Architectures||SDKROOTŌ ŅŌ E€’_ Architectures||ONLY_ACTIVE_ARCHŌ ŅŌ H€’_#Architectures||SUPPORTED_PLATFORMSŌ ŅŌ K€’_Architectures||VALID_ARCHSŌ ŅŌ N€’_Build Locations||SYMROOTŌ ŅŌ Q€’_Build Locations||OBJROOTŌ ŅŌ T€’_%Build Locations||SHARED_PRECOMPS_DIRŌ ŅŌ W€’_Build Options||BUILD_VARIANTSŌ ŅŌ Z€’_Build Options||GCC_VERSIONŌ ŅŌ ]€’_'Build Options||GENERATE_PROFILING_CODEŌ ŅŌ `€’_@Build Options||PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIRŌ ŅŌ c€’_)Build Options||RUN_CLANG_STATIC_ANALYZERŌ ŅŌ f€’_2Build Options||SCAN_ALL_SOURCE_FILES_FOR_INCLUDESŌ ŅŌ i€’_ Build Options||VALIDATE_PRODUCTŌ ŅŌ l€’_%Code Signing||CODE_SIGN_ENTITLEMENTSŌ ŅŌ o€’_!Code Signing||CODE_SIGN_IDENTITYŌ ŅŌ r€’_,Code Signing||CODE_SIGN_RESOURCE_RULES_PATHŌ ŅŌ u€’_$Code Signing||OTHER_CODE_SIGN_FLAGSŌ ŅŌ x€’_Deployment||STRIPFLAGSŌ ŅŌ {€’_Deployment||ALTERNATE_GROUPŌ ŅŌ ~€’_Deployment||ALTERNATE_OWNERŌ ŅŌ €’_Deployment||ALTERNATE_MODEŌ ŅŌ „€’_(Deployment||ALTERNATE_PERMISSIONS_FILESŌ ŅŌ ‡€’_!Deployment||COMBINE_HIDPI_IMAGESŌ ŅŌ Š€’_ Deployment||DEPLOYMENT_LOCATIONŌ ŅŌ €’_&Deployment||DEPLOYMENT_POSTPROCESSINGŌ ŅŌ €’_Deployment||INSTALL_GROUPŌ ŅŌ “€’_Deployment||INSTALL_OWNERŌ ŅŌ –€’_Deployment||INSTALL_MODE_FLAGŌ ŅŌ ™€’_Deployment||DSTROOTŌ ŅŌ œ€’_Deployment||INSTALL_PATHŌ ŅŌ Ÿ€’_%Deployment||MACOSX_DEPLOYMENT_TARGETŌ ŅŌ ĸ€’_%Deployment||PRODUCT_DEFINITION_PLISTŌ ŅŌ Ĩ€’_Deployment||SKIP_INSTALLŌ ŅŌ ¨€’_$Deployment||STRIP_INSTALLED_PRODUCTŌ ŅŌ Ģ€’_Deployment||STRIP_STYLEŌ ŅŌ Ž€’_Deployment||SEPARATE_STRIPŌ ŅŌ ą€’_Kernel Module||MODULE_NAMEŌ ŅŌ ´€’_Kernel Module||MODULE_STARTŌ ŅŌ ˇ€’_Kernel Module||MODULE_STOPŌ ŅŌ ē€’_Kernel Module||MODULE_VERSIONŌ ŅŌ Ŋ€’_Linking||BUNDLE_LOADERŌ ŅŌ Ā€’_%Linking||DYLIB_COMPATIBILITY_VERSIONŌ ŅŌ Ã€’_Linking||DYLIB_CURRENT_VERSIONŌ ŅŌ Æ€’_Linking||DEAD_CODE_STRIPPINGŌ ŅŌ É€’_'Linking||LINKER_DISPLAYS_MANGLED_NAMESŌ ŅŌ Ė€’_Linking||LD_NO_PIEŌ ŅŌ Ī€’_,Linking||PRESERVE_DEAD_CODE_INITS_AND_TERMSŌ ŅŌ Ō€’_Linking||LD_DYLIB_INSTALL_NAMEŌ ŅŌ Õ€’_Linking||EXPORTED_SYMBOLS_FILEŌ ŅŌ Ø€’_Linking||INIT_ROUTINEŌ ŅŌ Û€’_&Linking||LINK_WITH_STANDARD_LIBRARIESŌ ŅŌ Ū€’_Linking||MACH_O_TYPEŌ ŅŌ á€’_Linking||ORDER_FILEŌ ŅŌ ä€’_Linking||OTHER_LDFLAGSŌ ŅŌ į€’_%Linking||GENERATE_MASTER_OBJECT_FILEŌ ŅŌ ę€’_Linking||PRELINK_LIBSŌ ŅŌ í€’_Linking||KEEP_PRIVATE_EXTERNSŌ ŅŌ đ€’_!Linking||LD_RUNPATH_SEARCH_PATHSŌ ŅŌ ķ€’_Linking||SEPARATE_SYMBOL_EDITŌ ŅŌ ö€’_Linking||PRELINK_FLAGSŌ ŅŌ ų€’_Linking||SECTORDER_FLAGSŌ ŅŌ ü€’_!Linking||UNEXPORTED_SYMBOLS_FILEŌ ŅŌ ˙€’_Linking||WARNING_LDFLAGSŌ ŅŌ €’_Linking||LD_GENERATE_MAP_FILEŌ ŅŌ €’_%Packaging||APPLY_RULES_IN_COPY_FILESŌ ŅŌ €’_ Packaging||EXECUTABLE_EXTENSIONŌ ŅŌ €’_Packaging||EXECUTABLE_PREFIXŌ ŅŌ €’_+Packaging||INFOPLIST_EXPAND_BUILD_SETTINGSŌ ŅŌ €’_!Packaging||GENERATE_PKGINFO_FILEŌ ŅŌ €’_Packaging||FRAMEWORK_VERSIONŌ ŅŌ €’_Packaging||INFOPLIST_FILEŌ ŅŌ €’_.Packaging||INFOPLIST_OTHER_PREPROCESSOR_FLAGSŌ ŅŌ €’_#Packaging||INFOPLIST_OUTPUT_FORMATŌ ŅŌ €’_.Packaging||INFOPLIST_PREPROCESSOR_DEFINITIONSŌ ŅŌ #€’_#Packaging||INFOPLIST_PREFIX_HEADERŌ ŅŌ &€’_ Packaging||INFOPLIST_PREPROCESSŌ ŅŌ )€’_&Packaging||COPYING_PRESERVES_HFS_DATAŌ ŅŌ ,€’_'Packaging||PRIVATE_HEADERS_FOLDER_PATHŌ ŅŌ /€’_Packaging||PRODUCT_NAMEŌ ŅŌ 2€’_$Packaging||PLIST_FILE_OUTPUT_FORMATŌ ŅŌ 5€’_&Packaging||PUBLIC_HEADERS_FOLDER_PATHŌ ŅŌ 8€’_(Packaging||STRINGS_FILE_OUTPUT_ENCODINGŌ ŅŌ ;€’_Packaging||WRAPPER_EXTENSIONŌ ŅŌ >€’_'Search Paths||ALWAYS_SEARCH_USER_PATHSŌ ŅŌ A€’_%Search Paths||FRAMEWORK_SEARCH_PATHSŌ ŅŌ D€’_#Search Paths||LIBRARY_SEARCH_PATHSŌ ŅŌ G€’_Search Paths||REZ_SEARCH_PATHSŌ ŅŌ J€’_<Search Paths||EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ŅŌ M€’_<Search Paths||INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ŅŌ P€’_'Search Paths||USER_HEADER_SEARCH_PATHSŌ ŅŌ S€’_Unit Testing||OTHER_TEST_FLAGSŌ ŅŌ V€’_Unit Testing||TEST_AFTER_BUILDŌ ŅŌ Y€’_Unit Testing||TEST_HOSTŌ ŅŌ \€’_Unit Testing||TEST_RIGŌ ŅŌ _€’_$Versioning||CURRENT_PROJECT_VERSIONŌ ŅŌ b€’_Versioning||VERSION_INFO_FILEŌ ŅŌ e€’_%Versioning||VERSION_INFO_EXPORT_DECLŌ ŅŌ h€’_ Versioning||VERSION_INFO_PREFIXŌ ŅŌ k€’_ Versioning||VERSION_INFO_SUFFIXŌ ŅŌ n€’_Versioning||VERSIONING_SYSTEMŌ ŅŌ q€’_!Versioning||VERSION_INFO_BUILDERŌ ŅŌ t€’_AApple LLVM compiler 3.1 - Code Generation||GCC_FAST_OBJC_DISPATCHŌ ŅŌ w€’_HApple LLVM compiler 3.1 - Code Generation||CLANG_X86_VECTOR_INSTRUCTIONSŌ ŅŌ z€’_>Apple LLVM compiler 3.1 - Code Generation||GCC_STRICT_ALIASINGŌ ŅŌ }€’_IApple LLVM compiler 3.1 - Code Generation||GCC_GENERATE_DEBUGGING_SYMBOLSŌ ŅŌ €€’_=Apple LLVM compiler 3.1 - Code Generation||GCC_DYNAMIC_NO_PICŌ ŅŌ ƒ€’_KApple LLVM compiler 3.1 - Code Generation||GCC_GENERATE_TEST_COVERAGE_FILESŌ ŅŌ †€’_IApple LLVM compiler 3.1 - Code Generation||GCC_INLINES_ARE_PRIVATE_EXTERNŌ ŅŌ ‰€’_KApple LLVM compiler 3.1 - Code Generation||GCC_INSTRUMENT_PROGRAM_FLOW_ARCSŌ ŅŌ Œ€’_HApple LLVM compiler 3.1 - Code Generation||GCC_ENABLE_KERNEL_DEVELOPMENTŌ ŅŌ €’_3Apple LLVM compiler 3.1 - Code Generation||LLVM_LTOŌ ŅŌ ’€’_Apple LLVM compiler 3.1 - Language||GCC_ENABLE_OBJC_EXCEPTIONSŌ ŅŌ Å€’_8Apple LLVM compiler 3.1 - Language||GCC_ENABLE_TRIGRAPHSŌ ŅŌ Č€’_KApple LLVM compiler 3.1 - Language||GCC_ENABLE_FLOATING_POINT_LIBRARY_CALLSŌ ŅŌ Ë€’_CApple LLVM compiler 3.1 - Language||GCC_USE_INDIRECT_FUNCTION_CALLSŌ ŅŌ Î€’_CApple LLVM compiler 3.1 - Language||GCC_USE_REGISTER_FUNCTION_CALLSŌ ŅŌ Ņ€’_KApple LLVM compiler 3.1 - Language||GCC_INCREASE_PRECOMPILED_HEADER_SHARINGŌ ŅŌ Ô€’_9Apple LLVM compiler 3.1 - Language||CLANG_ENABLE_OBJC_ARCŌ ŅŌ ×€’_6Apple LLVM compiler 3.1 - Language||GCC_ENABLE_OBJC_GCŌ ŅŌ Ú€’_0Apple LLVM compiler 3.1 - Language||OTHER_CFLAGSŌ ŅŌ Ũ€’_8Apple LLVM compiler 3.1 - Language||OTHER_CPLUSPLUSFLAGSŌ ŅŌ ā€’_@Apple LLVM compiler 3.1 - Language||GCC_PRECOMPILE_PREFIX_HEADERŌ ŅŌ ã€’_5Apple LLVM compiler 3.1 - Language||GCC_PREFIX_HEADERŌ ŅŌ æ€’_@Apple LLVM compiler 3.1 - Language||GCC_ENABLE_BUILTIN_FUNCTIONSŌ ŅŌ é€’_=Apple LLVM compiler 3.1 - Language||GCC_ENABLE_PASCAL_STRINGSŌ ŅŌ ė€’_=Apple LLVM compiler 3.1 - Language||GCC_FORCE_CPU_SUBTYPE_ALLŌ ŅŌ ī€’_3Apple LLVM compiler 3.1 - Language||GCC_SHORT_ENUMSŌ ŅŌ ō€’_FApple LLVM compiler 3.1 - Language||GCC_USE_STANDARD_INCLUDE_SEARCHINGŌ ŅŌ õ€’_ZApple LLVM compiler 3.1 - Preprocessing||GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPSŌ ŅŌ ø€’_DApple LLVM compiler 3.1 - Warnings||GCC_WARN_CHECK_SWITCH_STATEMENTSŌ ŅŌ û€’_EApple LLVM compiler 3.1 - Warnings||CLANG_WARN__EXIT_TIME_DESTRUCTORSŌ ŅŌ ū€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_FOUR_CHARACTER_CONSTANTSŌ ŅŌ €’_3Apple LLVM compiler 3.1 - Warnings||GCC_WARN_SHADOWŌ ŅŌ €’_NApple LLVM compiler 3.1 - Warnings||CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIESŌ ŅŌ €’_DApple LLVM compiler 3.1 - Warnings||GCC_WARN_64_TO_32_BIT_CONVERSIONŌ ŅŌ €’_GApple LLVM compiler 3.1 - Warnings||CLANG_WARN_IMPLICIT_SIGN_CONVERSIONŌ ŅŌ €’_FApple LLVM compiler 3.1 - Warnings||GCC_WARN_ALLOW_INCOMPLETE_PROTOCOLŌ ŅŌ €’_AApple LLVM compiler 3.1 - Warnings||GCC_WARN_INHIBIT_ALL_WARNINGSŌ ŅŌ €’_LApple LLVM compiler 3.1 - Warnings||GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETEDŌ ŅŌ €’_>Apple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_RETURN_TYPEŌ ŅŌ €’_@Apple LLVM compiler 3.1 - Warnings||GCC_WARN_MISSING_PARENTHESESŌ ŅŌ €’_MApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERSŌ ŅŌ €’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_MISSING_PROTOTYPESŌ ŅŌ "€’_BApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_MISSING_NEWLINEŌ ŅŌ %€’_SApple LLVM compiler 3.1 - Warnings||GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTORŌ ŅŌ (€’_CApple LLVM compiler 3.1 - Warnings||GCC_WARN_NON_VIRTUAL_DESTRUCTORŌ ŅŌ +€’_=Apple LLVM compiler 3.1 - Warnings||CLANG_WARN_OBJCPP_ARC_ABIŌ ŅŌ .€’_2Apple LLVM compiler 3.1 - Warnings||WARNING_CFLAGSŌ ŅŌ 1€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONSŌ ŅŌ 4€’_NApple LLVM compiler 3.1 - Warnings||CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONSŌ ŅŌ 7€’_5Apple LLVM compiler 3.1 - Warnings||GCC_WARN_PEDANTICŌ ŅŌ :€’_EApple LLVM compiler 3.1 - Warnings||GCC_WARN_ABOUT_POINTER_SIGNEDNESSŌ ŅŌ =€’_9Apple LLVM compiler 3.1 - Warnings||GCC_WARN_SIGN_COMPAREŌ ŅŌ @€’_BApple LLVM compiler 3.1 - Warnings||GCC_WARN_STRICT_SELECTOR_MATCHŌ ŅŌ C€’_MApple LLVM compiler 3.1 - Warnings||CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSIONŌ ŅŌ F€’_ZApple LLVM compiler 3.1 - Warnings||GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORSŌ ŅŌ I€’_VApple LLVM compiler 3.1 - Warnings||GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORSŌ ŅŌ L€’_@Apple LLVM compiler 3.1 - Warnings||GCC_TREAT_WARNINGS_AS_ERRORSŌ ŅŌ O€’_FApple LLVM compiler 3.1 - Warnings||GCC_WARN_TYPECHECK_CALLS_TO_PRINTFŌ ŅŌ R€’_@Apple LLVM compiler 3.1 - Warnings||GCC_WARN_UNDECLARED_SELECTORŌ ŅŌ U€’_@Apple LLVM compiler 3.1 - Warnings||GCC_WARN_UNINITIALIZED_AUTOSŌ ŅŌ X€’_Static Analyzer - Checkers||CLANG_ANALYZER_DEADCODE_DEADSTORESŌ ŅŌ v€’_9Static Analyzer - Checkers||CLANG_ANALYZER_OBJC_SELF_INITĶ  y z€<  Ķ  } ¯€<¯1 ~  €  ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ  Ž   ‘ ’ “ ” • – — ˜ ™ š › œ  ž Ÿ   Ą ĸ Ŗ ¤ ĨX § ¨  Ē Ģ Ŧ ­ ށŁĮɁˁ́΁ҁ́Ձׁ؁ہŨ߁áãåįéëíīņķõ÷ųûũ˙   ™€!¯1 ° ą ˛ ŗ ´ ĩ ļ ˇ ¸ š ē ģ ŧ Ŋ ž ŋ Ā Á Â Ã Ä Å Æ Į Č É Ę Ë Ė Í Î Ī Đ Ņ Ō Ķ Ô Õ Ö × Ø Ų Ú Û Ü Ũ Ū ß ā#+258<?GJNUX\`hlpsw{ƒ†Ёށ’–šžĄĨЁ­°ŗˇģŋÁĮʁԁՁ؁ŨáåéėĶ  ãĩ€UÆ€Ō ŅŌ į€’_?file://localhost/Users/aiott/Desktop/PcmMsr/DriverInterface.cppĶ  ęĩ€UČ€Ō ŅŌ î€’_¸file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/sys/appleapiopts.hĶ  ņĩ€UĘ€Ō ŅŌ õ€’_ēfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/TargetConditionals.hĶ  øĩ€UĖ€Ō ŅŌ ü€’_˛file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/sys/kernel.hĶ  ˙ĩ€UÎ€Ō ŅŌ€’_;file://localhost/Users/aiott/Desktop/PcmMsr/MSRAccessor.cppĶ ĩ€UĐ€Ō ŅŌ €’_ģfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/mach/processor_info.hĶ  ĩ€UŌ€Ō ŅŌ€’_Jfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/MSRAccessor.cppĶ ĩ€UÔ€Ō ŅŌ€’_ļfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/libkern/sysctl.hĶ ĩ€UÖ€Ō ŅŌ€’_efile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/MSRAccessor.cppĶ "ĩ€UØ€Ō ŅŌ&€’_\file://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/en.lproj/InfoPlist.stringsĶ )ĩ€UÚ€Ō ŅŌ-€’_Mfile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/en.lproj/InfoPlist.stringsĶ 0ĩ€UÜ€Ō ŅŌ4€’_>file://localhost/Users/aiott/Desktop/PcmMsr/UserKernelShared.hĶ 7ĩ€UŪ€Ō ŅŌ;€’_Efile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/UserKernelShared.hĶ >ĩ€Uā€Ō ŅŌB€’_Tfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/UserKernelShared.hĶ Eĩ€Uâ€Ō ŅŌI€’_ofile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/UserKernelShared.hĶ Lĩ€Uä€Ō ŅŌP€’_Ŋfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/i386/machine_routines.hĶ Sĩ€Uæ€Ō ŅŌW€’_ŗfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/i386/eflags.hĶ Zĩ€Uč€Ō ŅŌ^€’_Lfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/DriverInterface.cĶ aĩ€Uę€Ō ŅŌe€’_=file://localhost/Users/aiott/Desktop/PcmMsr/DriverInterface.cĶ hĩ€Uė€Ō ŅŌl€’_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/DriverInterface.cĶ oĩ€Uî€Ō ŅŌs€’_Lfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsr.cppĶ vĩ€Uđ€Ō ŅŌz€’_ļfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/mach/host_info.hĶ }ĩ€Uō€Ō Ņԁ€’_=file://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsr.cppĶ „ĩ€Uô€Ō ŅŌˆ€’_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.cppĶ ‹ĩ€Uö€Ō ŅŌ€’_‡file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/usr/include/stdlib.hĶ ’ĩ€Uø€Ō ŅŌ–€’_=file://localhost/Users/aiott/Desktop/PcmMsr/DriverInterface.hĶ ™ĩ€Uú€Ō ŅŌ€’_Lfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/DriverInterface.hĶ  ĩ€Uü€Ō ŅŌ¤€’_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/DriverInterface.hĶ §ĩ€Uū€Ō ŅŌĢ€’_ŗfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/kern/thread.hĶ Žĩ€U€Ō ŅŌ˛€’_;file://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsr.hĶ ĩĩ€U€Ō ŅŌš€’_Cfile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsrClient.cppĶ ŧĩ€U€Ō ŅŌĀ€’_Rfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsrClient.cppĶ Ãĩ€U€Ō ŅŌĮ€’_´file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/mach/machine.hĶ Ęĩ€U€Ō ŅŌ΀’_˛file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/i386/cpuid.hĶ Ņĩ€U €Ō ŅŌՀ’_Jfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsr.hĶ Øĩ€U €Ō ŅŌ܀’_9file://localhost/Users/aiott/Desktop/PcmMsr/MSRAccessor.hĶ ßĩ€U€Ō ŅŌ〒_Hfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/MSRAccessor.hĶ æĩ€U€Ō ŅŌꀒ_cfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/MSRAccessor.hĶ íĩ€U€Ō ŅŌņ€’_efile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.hĶ ôĩ€U€Ō ŅŌø€’_ąfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/kern/task.hĶ ûĩ€U€Ō ŅŌ˙€’_Dfile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsr-Prefix.pchĶ ĩ€U€Ō ŅŌ€’_mfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsrClient.cppĶ  ĩ€U€Ō ŅŌ €’_Afile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsrClient.hĶ ĩ€U€Ō ŅŌ€’_Pfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsrClient.hĶ ĩ€U€Ō ŅŌ€’_kfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsrClient.hĶ ĩ€U €Ō ŅŌ"€’_ˇfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Headers/libkern/libkern.hĶ %ĩ€U"€Ō ŅŌ)€’_Âfile://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/libkern/libkern.hĶ ,1€<¤-./0$%&'¤23$5()€M*_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#Aĩp7/uÚY{0, 2535}X{225, 0}Ķ ?D€<¤@ABC,-./¤EF$01€MŨ_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#Aĩuo/Õ{Y{0, 2070}Ķ QV€<¤@ABC,-./¤WX$34€MŨ#AĩuoKÖđ\{9561, 3216}Ķ _d€<¤@ABC,-./¤ef$67€MŨ#Aĩuo&]SXY{0, 3719}Ķ mr€<¤@ABC,-./¤st$v9:€M;#Aĩy†ÅäĒšY{0, 2015}X{970, 0}Ķ |€<¤@ABC,-./¤‚ƒ$=>€MŨ#Aĩup8\\{2626, 3174}Ķ Š€<¤‹Œށ@ABC¤‘$“DE€MF_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#AĩĻ:ģ|5\{2158, 1775}Y{1446, 0}Ķ ĸ€<¤@ABC,-./¤Ŗ¤$HI€MŨ#Aĩu’Ÿtîō\{1390, 4975}Ķ Ģ°€<¤ÛÜŨŪ€”€•€–€—¤ą˛$´KL€MM#AĩŊĨŌéTĘ\{1496, 1822}Y{2867, 0}Ķ ēŋ€<¤ģŧŊžOPQR¤ĀÁ$ST€MŨ_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#AĩĢOīmū¨W{0, 45}Ķ ĖŅ€<¤@ABC,-./¤ŌĶ$VW€MŨ#Aĩumģ,ΒW{0, 45}Ķ Ú߀<¤@ABC,-./¤āá$ãYZ€M[#Aĩy†r]X{0, 390}X{271, 0}Ķ éî€<¤@ABC,-./¤īđ$ō]^€M_#Aĩy†ŗ™$JX{0, 774}X{654, 0}Ķ øũ€<¤ųúûüabcd¤ū˙$ef€Mg_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#AĩŖ‘K;{)X{0, 923}X{262, 0}Ķ  €<¤ÛÜŨŪ€”€•€–€—¤$ij€Mk#Aĩ¸ĢUa†UY{0, 2583}Y{1886, 0}Ķ €<¤@ABC,-./¤ !$#mn€Mo#AĩvŌ B5[{113, 2095}Y{1431, 0}Ķ ).€<¤@ABC,-./¤/0$qr€MŨ#AĩvԚ•hY{0, 2505}Ķ 7<€<¤‹Œށ@ABC¤=>$@tu€Mv#AĩĨúÃâĩÔY{0, 1583}Y{5438, 0}Ķ FK€<¤@ABC,-./¤LM$Oxy€Mz#Aĩy†Õáo3[{683, 2532}Y{1277, 0}Ķ UZ€<¤ÛÜŨŪ€”€•€–€—¤[\$^|}€M~#AĩŊĻĻ“ž\{8399, 2546}W{17, 0}Ķ di€<¤‹Œށ@ABC¤jk$m€€M‚#AĩĻ;F÷&\{1942, 2082}Y{1170, 0}Ķ sx€<¤@ABC,-./¤yz$„…€MŨ#AĩuomI0\{5145, 3006}Ķ †€<¤@ABC,-./¤‡ˆ$Ё‡ˆ€M‰#Aĩ}–7É\{9240, 2951}Ķ $)€<¤@ABC,-./¤*+$ą˛€MŨ#AĩvŅAšh”\{5731, 2051}Ķ 27€<¤‹Œށ@ABC¤89$;´ĩ€Mļ#AĩĨú?Čc|X{0, 950}X{950, 0}Ķ AF€<¤@ABC,-./¤GH$J¸š€Mē#Aĩy†wZˆ{X{0, 511}X{305, 0}Ķ PU€<¤ųúûüabcd¤VW$YŧŊ€Mž#AĩŖ•›iĮiX{0, 734}X{710, 0}Ķ _d€<¤ÛÜŨŪ€”€•€–€—¤ef$hÁ€MÂ#AĩŊϤŸY{0, 2104}Y{1628, 0}Ķ ns€<¤ÛÜŨŪ€”€•€–€—¤tu$wāŀMÆ#AĩŊĢr‰Š"Y{0, 2672}Y{2636, 0}Ķ }‚€<¤@ABC,-./¤ƒ„$ȁɀMŨ#Aĩunô’ā;\{1756, 2347}Ķ ‹€<¤ŒŽˁˁ́Τ‘’$”΁ЀMŅ_PrimaryDocumentTimestamp_$PrimaryDocumentVisibleCharacterRange]HideAllIssues_%PrimaryDocumentSelectedCharacterRange#AĩnëSšēY{0, 1457}Y{1371, 0}Ķ žŖ€<¤@ABC,-./¤¤Ĩ$́ԀMŨ#AĩumšŸ2ßW{0, 92}Ķ Ŧą€<¤ÛÜŨŪ€”€•€–€—¤˛ŗ$ĩց׀MØ#AĩČ2u…æŦ[{546, 3487}Z{1656, 12}Ķ ģĀ€<¤ÛÜŨŪ€”€•€–€—¤ÁÂ$āځۀMÜ#AĩČ2z‹ģĨ\{5637, 2449}Z{5280, 45}Ķ ĘĪ€<¤@ABC,-./¤ĐŅ$́ہ߀Mā#Aĩyl´čá‚Y{0, 1691}Y{1686, 0}Ķ ŲŪ€<¤ųúûüabcd¤ßā$ââã€Mä#AĩŖ6 Ų„Y{0, 2444}Y{1810, 0}Ķ čí€<¤ÛÜŨŪ€”€•€–€—¤îī$ņæį€Mč#Aĩ¸žÕsŽ~Y{0, 3991}Y{3044, 0}Ķ ÷ü€<¤@ABC,-./¤ũū$ęë€MŨ#AĩvŅĖ4ô\{5533, 1708}Ķ  €<¤ęëėí€G€H€I€J¤  $íî€Mī#Aĩ´Č˃Į“\{2469, 1926}Y{4875, 0}Ķ ;€<¯& !"#$%&'()*+,-./0123456789:ņķõ÷ųûũ˙   !#%')+-/13579;¯&<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a=@BCDFGHJKLNOPXY[\^_`aefghinopsvw|}~€Ķ dĩ€Uō€_2x-xcode-log://5A288867-C894-4F61-B588-6C2CBA58878CĶ iĩ€Uô€_2x-xcode-log://0525A4CA-06C9-4080-AF6D-0A5355DC99EDĶ nĩ€Uö€_2x-xcode-log://45E1ED50-2C99-4E9F-A621-9B48EDEAC6AAĶ sĩ€Uø€_2x-xcode-log://8D085A3A-D24F-43D7-9B2C-F53FD6A8038FĶ xĩ€Uú€_2x-xcode-log://EB047792-AD33-4C6A-99D0-54184F5CE7AEĶ }ĩ€Uü€_2x-xcode-log://C862713F-672F-46F3-B5C5-C12EC96E89FCĶ ‚ĩ€Uū€_2x-xcode-log://9D94ADAA-D3D0-4395-BCCD-D57ED4E8841DĶ ‡ĩ€U€_2x-xcode-log://451440CB-B8B4-4C96-81EE-3C6A12BB784FĶ Œĩ€U€_2x-xcode-log://EBE62B83-E49F-4D45-A94B-F4D244006DFFĶ ‘ĩ€U€_2x-xcode-log://0B106FA5-32DD-466B-A18F-AFFF5BDDFCADĶ –ĩ€U€_2x-xcode-log://543B16A3-F5F4-48F9-BF50-7EA6872DF471Ķ ›ĩ€U€_2x-xcode-log://5C1F001B-F44E-4400-BECE-29680D26A236Ķ  ĩ€U €_2x-xcode-log://FB141C82-5056-4A95-833C-F2A79AA5942DĶ Ĩĩ€U €_2x-xcode-log://54AA8DBD-B199-48C3-A1C2-5F7BB00B881BĶ Ēĩ€U€_2x-xcode-log://1FADA345-13B5-4673-930C-E64AE795D47DĶ ¯ĩ€U€_2x-xcode-log://2BED104D-FF15-485A-9A15-CADF412F26B4Ķ ´ĩ€U€_2x-xcode-log://5D3E2E80-4DE8-4A86-AFAA-6444E5FA3896Ķ šĩ€U€_2x-xcode-log://3FACC23D-8E7B-4601-B682-08B73406726AĶ žĩ€U€_2x-xcode-log://3C0A2304-614E-4753-9FAC-93027E06F5CBĶ Ãĩ€U€_2x-xcode-log://56D4903F-B532-4835-93D4-13C933B10A4EĶ Čĩ€U€_2x-xcode-log://84BE9950-103D-441D-BF3A-65D41491813EĶ Íĩ€U€_2x-xcode-log://25075FC2-D936-4BE1-94AA-12BC362B7498Ķ Ōĩ€U€_2x-xcode-log://228112D9-E0F3-4500-9CFC-07EAA0495A2AĶ ×ĩ€U €_2x-xcode-log://6570D0AE-6A8A-4190-AE44-D6278CE134D8Ķ Üĩ€U"€_2x-xcode-log://9D796B5E-9990-4B0F-AA4A-8668466D2780Ķ áĩ€U$€_2x-xcode-log://B8C0EC08-D0BF-4674-8C3C-2AED7273DAC8Ķ æĩ€U&€_2x-xcode-log://AE30E8E6-3499-49F6-BE29-C250DEE03D41Ķ ëĩ€U(€_2x-xcode-log://D026A6F9-5F65-45D2-B0BB-E65CFAF1AE7DĶ đĩ€U*€_2x-xcode-log://26F2CDD2-3262-42D5-AAC8-6727C2834758Ķ õĩ€U,€_2x-xcode-log://0B7F1F62-A3FE-4B91-AA39-E8210DAC3ACEĶ úĩ€U.€_2x-xcode-log://D8449429-94F1-4F0F-BC95-EBB2912DF67EĶ ˙ĩ€U0€_2x-xcode-log://6DDF14AA-27CD-492C-AD80-23C70C93B46DĶ ĩ€U2€_2x-xcode-log://E12DDE21-C526-45EE-B482-3E4E40E2FA3BĶ  ĩ€U4€_2x-xcode-log://4AB75C9C-325D-44EF-86DA-7232574FDB9AĶ ĩ€U6€_2x-xcode-log://04C144C7-A301-4A10-BBD4-4FFB1F7566FDĶ ĩ€U8€_2x-xcode-log://DE67AAEC-D87C-4A25-9F26-1B54600712B5Ķ ĩ€U:€_2x-xcode-log://3E9118F4-C53D-408A-B944-FEC464765ECFĶ ĩ€U<€_2x-xcode-log://4B536260-1FA1-4668-82C3-E9F8762F3FBBĶ "$€<Ą#>Ą%?_SelectedDocumentLocationsŌ š)€@ Ķ ,.€<Ą-AĄ%?_SelectedDocumentLocationsĶ 35€<Ą#>Ą%?Ķ 9;€<Ą-AĄ%?Ķ ?A€<Ą@EĄ%?_SelectedDocumentLocationsĶ FH€<Ą#>Ą%?Ķ LN€<Ą#>Ą%?Ķ RT€<ĄSIĄ%?_SelectedDocumentLocationsĶ Y[€<Ą#>Ą%?Ķ _a€<Ą#>Ą%?Ķ eg€<ĄfMĄ%?_SelectedDocumentLocationsĶ ln€<Ą-AĄ%?Ķ rt€<Ą#>Ą%?Ķ xz€<Ą#>Ą{QŌ š~€@ĄRÕ؁‚ Úĩų…†Ĩ_expandTranscriptYindexPath€SW Ķ ‰Š‹Œ×_NSIndexPathData_NSIndexPathLengthVTŌ ŽWNS.dataUBŌÆĮ’“]NSMutableDataŖ’”ËVNSDataŌÆĮ–—[NSIndexPathĸ˜Ë[NSIndexPathŌÆĮš›_IDELogDocumentLocationŖœË_IDELogDocumentLocation_DVTDocumentLocationĶ  ĸ€<Ą-AĄ%?Ķ Ļ¨€<Ą§ZĄ%?_SelectedDocumentLocationsĶ ­¯€<Ą-AĄ%?Ķ ŗĩ€<Ą´]Ą%?_SelectedDocumentLocationsĶ ēŧ€<Ą-AĄ%?Ķ Ā€<Ą-AĄ%?Ķ ÆȀ<Ą´]Ą%?Ķ Ė΀<Ą-AĄ΁bŌ šŌ€@Ą́cÕ؁‚ Úĩų׆̀dWĶ ŠÛ‹Ũ_NSIndexPathValueVĶ āâ€<Ą#>Ą%?Ķ æč€<Ą#>Ą%?Ķ ėî€<Ą#>Ą%?Ķ ōô€<Ą´]Ą%?Ķ øú€<Ą#>ĄûjŌ šū€@Ą˙kÕ؁‚ Úĩų†æ€lW&Ķ ‰Š‹ׁVmŌ Ž UBĶ €<Ą#>Ą%?Ķ €<Ą-AĄ%?Ķ €<Ą-AĄqŌ š €@Ą!rÕ؁‚ Úĩų׆õ€dW,Ķ *,€<Ą-AĄ-tŌ š0€@Ą1uÕ؁‚ Úĩų׆ú€dW.Ķ :<€<Ą#>Ą%?Ķ @B€<ĄAxĄCy_SelectedDocumentLocationsŌ šG€@ĄHzÕ؁‚ ÚĩųL†€{W2Ķ ŠÛ‹QV Ķ TV€<ĄAxĄ%?Ķ Z\€<Ą#>Ą%?Ķ `b€<Ą-AĄ%?Ķ fh€<Ą-AĄ%?Ķ ln€<Ą-AĄ%?Ķ rt€<ĄX™Ąu‚Ķ x|€<Ŗyz{ƒ„…Ŗ}~†ˆ‰_IDE_PLIST_EDITOR_SELECTION_KEY_ IDE_PLIST_EDITOR_VISIBLERECT_KEY_IDE_PLIST_EDITOR_EXPANSION_KEYŌ š…€@Ą†‡_OSBundleLibraries_{{0, 0}, {880, 828}}Ō Q‹ĄŒŠŌ ‘€VĄ†‡Ķ “–€<ĸ”•Œĸ—˜ށ_IDEDeviceLocation_IDEDeviceArchitecture_"dvtdevice-local-computer:localhostVx86_64Ķ ŸĄ€<Ą ‘Ąĸ’]IDENameString]PcmMsrLibraryĶ §Ģ€<Ŗ¨Šǁ”•–ŖŦ­ —°?_0IDEActivityReportCompletionSummaryStringSegments_IDEActivityReportOptions_IDEActivityReportTitleŌ ‘´€V¤ĩ˜ŸŖ§Ķ ģŋ€<ŖŧŊž™š›ŖĀÁœž_&IDEActivityReportStringSegmentPriority_+IDEActivityReportStringSegmentBackSeparator_)IDEActivityReportStringSegmentStringValue#@Q UBuildĶ ËĪ€<ŖŧŊž™š›ŖĐŅԁ Ąĸ#@R: ]PcmMsrLibraryĶ Ø܀<ŖŧŊž™š›ŖŨŪ߁¤ĨĻ#?đc % Ō ŽäUO\bplist00Ô;Troot€#-27EKR_foqsu†Ž™›ž ĸĨ§Š°¸ÁČĪØÚÜåčü (/<DFHKPX[`mpuŠĸ´ˇŧ?žĶ įî€<Ļŧéęžė큙¨Ё›ǁĢĻīDņōDDŦ€´­¯€´€´_"IDEActivityReportStringSegmentType_"IDEActivityReportStringSegmentDate_'IDEActivityReportStringSegmentDateStyle_'IDEActivityReportStringSegmentTimeStyle#@Ōû üũWNS.time#Aĩ¸Ÿ Ŗ3ŽŌÆĮ˙VNSDateĸ˙Ë_7/19/12 at 1:01 PMęŌ ‘€VĄ€Ķ  €<Ģ    ŗĩˇšģŊŋÁÁŁĮĢ Ɂ΁ԁցځہáåčëîĶ #ĩ€U´€_;file://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsr.hĶ (ĩ€Uļ€_Jfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsr.hĶ -ĩ€U¸€_=file://localhost/Users/aiott/Desktop/PcmMsr/DriverInterface.hĶ 2ĩ€Uē€_=file://localhost/Users/aiott/Desktop/PcmMsr/DriverInterface.cĶ 7ĩ€Uŧ€_Afile://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsrClient.hĶ <ĩ€Už€_;file://localhost/Users/aiott/Desktop/PcmMsr/MSRAccessor.cppĶ Aĩ€UĀ€_Jfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/MSRAccessor.cppĶ Fĩ€U€_=file://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr/PcmMsr.cppĶ Kĩ€UĀ_=file://localhost/Users/aiott/Desktop/PcmMsr/PcmMsr.xcodeproj/Ķ Pĩ€Uƀ_Lfile://localhost/Users/aiott/projects/pcm/MacOS_MSR_Driver/PcmMsr/PcmMsr.cppĶ Uĩ€UȀ_‡file://localhost/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/usr/include/stdlib.hĶ Z]€Oĸ[\ʁËĸ^_ˁÍUwidthVheight#@‚Ā#@‚ĀĶ fi€Oĸg\΁ËĸjkЁŅUwidth#@‚Ā#@‚ĀĶ qt€Oĸr\́ËĸuvԁÕUwidth#@‚Ā#@‚ĀĶ |€Oĸ}\ׁËĸ€؁ŲUwidth#@ŽH#@‚ĀĶ ‡Š€Oĸˆ\ہËĸ‹Œ܁ŨUwidth#@‚Ā#@‚ĀĶ ’•€Oĸ}\ׁËĸ–—߁ā#@‚Ā#@‚ĀĶ œŸ€Oĸ\âËĸ ĄãäUwidth#@‚Ā#@‚ĀĶ §Ē€Oĸˆ\ہËĸĢŦæį#@‚Ā#@‚ĀĶ ą´€Oĸ[\ʁËĸĩļéę#@‚Ā#@‚ĀĶ ģž€Oĸg\΁Ëĸŋėí#@‘°#@‚ĀĶ ÅȀOĸÆ\īËĸÉʁđņUwidth#@‚Ā#@‚ĀŌ ‘ЀVĒ ŌĶÔÕÖרŲڀķõ÷ųûũ˙Ķ Ũĩ€Uô€Ō ŅŌဒ_mfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsrClient.cppĶ äĩ€Uö€Ō ŅŌ耒_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.cppĶ ëĩ€Uø€Ō ŅŌ_efile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsr.hĶ ōĩ€Uú€Ō ŅŌö€’_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/DriverInterface.cĶ ųĩ€Uü€Ō ŅŌũ€’_cfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/MSRAccessor.hĶ ĩ€Uū€Ō ŅŌ€’_efile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/MSRAccessor.cppĶ ĩ€U€Ō ŅŌ €’_ofile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/UserKernelShared.hĶ ĩ€U€Ō ŅŌ€’_kfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/PcmMsr/PcmMsrClient.hĶ ĩ€U€Ō ŅŌ€’_gfile://localhost/Users/aiott/projects/pcm/IntelPerformanceCounterMonitor/MacMSRDriver/DriverInterface.h_NSKeyedArchiverŅUState€"+5:? M S ` g o z |  ƒ … Š Œ  ļ Í Ú Ü í ī ņ ķ õ ÷ ų û ũ         a p ” ° ß8EGXZ\^`bdfhy{}ƒ†ˆŠ–Ўįú)<Udert‡‰‹‘“•—™Ŧް˛´ļ¸ēŧžÕîų(<_r†ˆŠ‘“ĄÉÎÛŨâäæëíīųū  !#%'SkƒŒŽ‘“ ĸąŗĩˇšģŊŋÎĐŌÔÖØÚÜé$@^j|‘¤ŗÆâäæčęėõ÷üū .7@KTjq~‡”–™›ž ĢÖßįėîûˆžŖšÂíō*,579;=FHJLNižÆĪŲÚãėņú,9EMOQSģÄĘĪÕŪíôũ˙(*1357>@BDMORTacrtvxz|~€‘“•—™›šÎĐŌÔÖØ  "+68:ENmr‘šœžĨް˛šÆČĘĖ6CENPRTV_acegpz‚‘“•˙  '469;>@huwz|†“•œž ĸŠĢ­¯Ûķ (*9;=?ACEGVXZ\^`bdqŒ˜ŦČæō!#T]_jlnprt}™›ŖŦްÃĖÎĐÛäæčķãėū&(*,.79;=?ZˇĀÍØí#%')2479Qb}—Ŧް˛´Íđü       " 7 9 ; = ? A J L W Y [ ] _ a j l n w y { „ † ˆ ‘ “ • ž   ĸ ¯ ą ŗ ĩ Î Đ Ō Ô Ö ß á ä æ ī!!!H!Q!w!|!ĸ!¯!ą!ŧ!ž!Ā!Â!Ä!Æ!Ņ!Ķ!Õ!×!Ų!Û!í!÷!˙" "#"0"2"5"7":"<"Y"f"h"k"m"p"r"†"“"•"˜"š""Ÿ"ŗ"ŧ"ž"Ã"Å"Į"Ô"Ö"Û"Ũ"ß"ä"æ"č"ö# ## #-#/#4#6#8#=#?#A#M#V#c#e#h#j#m#o#x#z###ƒ##’#—#™#›# #ĸ#¤#Ž#ˇ#Ä#Æ#Ë#Í#Ī#Ô#Ö#Ø#ę#ķ$$$$$$$$$$$&$($-$/$1$>$@$E$G$I$N$P$R$S$\$i$k$p$r$t$y${$}$†$“$•$¤$Ļ$¨$Ē$Ŧ$Ž$°$˛$Á$Ã$Å$Į$É$Ė$Ī$Ō$ō%%)%I%h%‹%Š%ļ%¸%Į%É%Ë%Í%Ī%Ņ%Ķ%Õ%ä%æ%č%ę%ė%î%đ%ō&&'&N&r&&ŗ&Ų&â&ä&ī&ņ&ķ&õ&÷&ų' '7'h''Ŧ'­'ļ'¸'ģ'Ŋ'â'ī'ņ(((((( ( ((((!(#(%('()(+(Q(u(š(ŧ(æ))-)4)E)N)[)f)h)j)s)~)ƒ)Ž)—)™)ĸ)ļ)Ŋ)Ņ)Ü)˙* **!*#*%*'*)*,*/*2*5*8*K*M*P*S*V*X*[*^*`*c*~**Ą*ĩ*Ä*Ü*đ+ +!+8+A+D+E+N+[+b+h+q+t+w+z+‡+‰+’+•+˜+›+ž+§+Ē+­+°+ŗ+Į+Ķ+Ø+â+ë+í+đ+ķ,,,,@,U,l,,”,—,™,›,ž, ,Ŗ---6-P-W-q-‡-Î-Ũ-æ-č-í-đ-ķ... ..... .#.&.).,.5.7.:.=.b.d.f.h.k.n.Ø/K/c/l/n/o/|/~/‡/Š///“/œ/Ÿ/ĸ/Ĩ/¨/ą/ŗ/ļ/š/Ū/á/ã/å/č/ę/í0§0Æ0Ī0Ņ0Ō0Û0Ũ0Ū0į0ę0ë0ô0÷0ø11111111!1#1%1@1W1w1y1†1ˆ1—1š11 1Ŗ1Ļ1Š1Ŧ1ģ1ž1Ā1Ã1Æ1Č1Ę1Í1Ü222*2I2m2œ2ŗ2Ā2Â2Å2Č2Ë2Î2ā2í2ī2ō2õ2ø2û33333333$313336393<3?3R3S3`3b3e3h3k3n3{3}3€3ƒ3†3‰3–3˜3Ŗ3Ļ3Š3Ŧ3¯3˛3Ŋ3Ā3Ã3Æ3É3Ė3Ú3ã3î3û3ũ4444 444&4345484;4>4A4N4P4Q4R4_4a4h4k4n4q4x4z4}4€4Ļ4Ī4ô5555 5 55!5.505356595;5^5u5‚5„5‡5‰5Œ55˜5š5Ą5¤5§5Ē5ˇ5š5ž5Ā5Â5Į5Ę5Í5ā5é5ö5ø5ũ5˙666 6 66#606267696;6@6C6F6Y6b6{6„6†6‰6‹6˜6š6ą6´6ˇ6ē6Ŋ6Ā6Ã6Æ6É6Ė6Ī6Ō6é6ë6î6đ6ķ6ö6ų6ü6˙77777:7T7k7x7Ģ7Ŋ7Ũ7ø88)86888E8H8K8N8Q8T8W8d8g8j8m8p8s8v8’8ŧ8ö99I9x9…9‡9’9•9˜9›9ž9Ą9Ŧ9¯9˛9ĩ9¸9ģ9Č9Ę9Í9Ī9Ø9Ú:m:z:|:::Š:Œ;;,;.;1;3;<;>;Í;Ú;Ü;ß;á;ę;ė=Z=c=e=h=k=|=•=˜=›=ž= >3><>E>d>k>Š> >­>¯>˛>ĩ>¸>ģ>×>ā>â>å>č>ų>ü>˙???—? ?­?¯?˛?ĩ?¸?ģ?×?ā?â?å?č?ų?ü?˙@@@“@œ@Š@Ģ@Ž@ą@´@ˇ@Ā@Â@Å@Č@Ų@Ü@ß@â@äApAyA†AˆA‹AŽA‘A”A°AšAģAžAÁAŌAÕAØAÛAŨBiBrBBB†B‰BŒB‘B”B—B¤BĻBŠBĢB´BļBũC C CCCCCrCCCˆC‹CŽC‘C˜C›CžCĄCÂCåDDDDDDD1D>DVD_DbDiDlDoDrD{D}D‚D…DˆD‘D“D–D™D­DļD¸DģDžDËDÍDÔD×DÚDŨDäDįDęDíEE1ERE[E]E`EcEwEŽE—EšEE EŠEĢEŽEąEžEĀEĮEĘEÍEĐE×EÚEŨEāEíEīEōEôEũE˙F>FKFMFPFRF[F]FĢF¸FēFŊFŋFČFĘG4GAGCGNGQGTGWGZG]GhGkGnGqGtGwG§GŌHH/H^HzH‡H‰HŒHH’H•HŠH˛H´HšHŧHŋHĖHÎHĶHÖHŲHŪHāHãHņII III!I$I'I,I.I1I:IGIIIdIgIjImIpIsIvIyI|II‚I…IˆI‹IĻIŠIŦIŽIąI´IˇIēIŊIĀIÃIÆIÉIĖIúJ#JFJaJ|J—J˛JÍJčKKK9KTK]K`KiKlKoKrKuK’K¨KēKĮKÎKÛKŨKŪKßKėKîKņKôK÷KúL$L5L7LDLFLGLHLULWLZL]L`LcLlLnL{L}L~LLŒLŽLLLLŸL LĄLŽL°LąL˛LŋLÁLÂLÃLĐLŌLĶLÔLŨLßLâLåLöMMMM M MLMUMbMdMkMnMqMtM{M~MM„M‹M’MĩMĪMÜMåMįMęMíMúMüMũMūNN'N.NNNdN~N‹NN˜N›NžNĄN¤N§N˛NĩN¸NģNžNÁNņOOKOzOĒOÆOĶOÕOØOÛOŪOáOõOūPPPP PPPP"P%P*P,P/P=PPPYPfPhPmPpPsPxPzP}P†PĸPĢP­P°PŗPÄPĮPĘPÍPĐQQ(Q5Q7Q>QAQDQGQNQQQTQWQ^QƒQŸQĻQ¯QąQ´QˇQÄQÆQĶQÖQŲQÜQßQâQåQōQõQøQúQüQūRR&RBRiRR¯RÔRŨRßTbTeThTkTnTqTtTwTzT}T€TƒT†T‰TŒTT’T•T˜T›TžTĄT¤T§TĒT­T°TŗTļTšTŧTŋTÂTÅTČTËTÎTŅTÔT×TÚTŨTāTãTæTéTėTīTōTõTøTûTūUUUU U UUUUUUU"U%U(U+U.U1U4U7U:U=U@UCUFUIULUOURUUUXU[U^UaUdUgUjUmUpUsUvUyU|UU‚U…UˆU‹UŽU‘U”U—UšUU UŖUĻUŠUŦU¯U˛UĩU¸UģUžUÁUÄUĮUĘUÍUĐUĶUÖUŲUÜUßUâUåUčUëUîUņUôU÷UúUũVVVV V VVVVVVV!V$V'V*V-V0V3V6V9V<V?VBVEVHVKVNVQVTVWVZV]V`VcVfViVlVoVrVuVxV{V~VV„V‡VŠVVV“V–V™VœVŸVĸVĢV­VĪVØVÚVōVûVũWW W"WHWQWSWqWzW|W˜WĄWŖWŋWČWĘWōWûWũXX'X)XGXPXRX}X†XˆX˛XģXŊYY Y Y7Y@YBYwY€Y‚YĨYŽY°YØYáYãZZZZAZJZLZsZ|Z~Z˜ZĄZŖZÂZËZÍZėZõZ÷[[[ [K[T[V[z[ƒ[…[¨[ą[ŗ[Ü[å[į\\ \\,\5\7\X\a\c\z\ƒ\…\Ą\Ē\Ŧ\Ô\Ũ\ß]]]].]7]9]`]i]k]†]]‘]¯]¸]ē]Ø]á]ã^^ ^ ^+^4^6^W^`^b^|^…^‡^¯^¸^ē^Ü^å^į____<_E_G_]_f_h_—_ _ĸ_Ä_Í_Ī_ņ_ú_ü``` `I`R`T`l`u`w`Ž`—`™`ŗ`ŧ`ž`æ`ī`ņa aaa6a?aAaeanapa‘ašaœaļaŋaÁaŨaæačb bbb3b<b>b_bhbjb’b›bbĀbÉbËbëbôböc$c-c/cSc\c^c~c‡c‰cĻc¯cącâcëcíddddOdXdZd€d‰d‹dŽdˇdšdâdëdíee e"e=eFeHeoexezeŖeŦeŽeŲeâeäff ff9fBfDflfufwfœfĨf§fÍfÖfØfúgggDgMgOgŽg—g™gÃgĖgÎgđgųgûhh&h(hChLhNhhhqhshšhŖhĨhÆhĪhŅhųiii'i0i2iUi^i`iiŠiŒi°išiģi˙jj jUj^j`jĄjĒjŦjøkkkCkLkNkœkĨk§kķkükūlLlUlWlĸlĢl­lãlėlîm-m6m8mzmƒm…mĀmÉmËnnnnbnknmnĢn´nļnön˙oo>oGoIo‡oo’oÔoŨoßpp p"pZpcpepžp§pŠpépōpôq.q7q9qqˆqŠqËqÔqÖrrrrjrsrurģrÄrÆs sssesnspsŦsĩsˇsđsųsût.t7t9ttt}ttÂtËtÍuuuuSu\u^užu§uŠuéuōuôv*v3v5v~v‡v‰vævīvņw8wAwCw‹w”w–wŪwįwéxx(x*x{x„x†xÍxÖxØy"y+y-yvyyyÅyÎyĐzz(z*zkztzvzšzÂzÄ{{{{g{p{r{ˇ{Ā{Â||!|#|i|r|t|´|Ŋ|ŋ|ô|ũ|˙}G}P}R}Ŗ}Ŧ}Ž}æ}ī}ņ~9~B~D~€~‰~‹~Đ~Ų~Û+46“œž÷€€€E€N€P€™€ĸ€¤€į€đ€ō5>@ˆŠÉŌÔ‚‚‚‚[‚d‚f‚ĸ‚Ģ‚­‚ė‚õ‚÷ƒ9ƒBƒDƒŽƒ—ƒ™ƒåƒîƒđ„1„:„<„x„„ƒ„†„‰„’„”„ļ„ÄńƄĮ„Ô„Ö„á„ä„į„ę„í„đ„û„ū………… …:…e…”…Ã…ķ††††!†$†'†*†>†G†I†N†Q†T†a†c†h†k†n†s†u†x†††™†ĸ†¯†ą†ļ†š†ŧ†Á†Ã†Æ†Ī†ë†ô†ö†ų†ü‡ ‡‡‡‡‡ƒ‡Œ‡™‡›‡ĸ‡Ĩ‡¨‡Ģ‡˛‡ĩ‡¸‡ģ‡Â‡įˆˆˆˆˆˆ!ˆ.ˆ0ˆ=ˆ@ˆCˆFˆIˆLˆOˆ\ˆ^ˆaˆcˆeˆhˆjˆˆĢˆŌˆø‰‰>‰G‰I‰L‰O‰X‰Z‰x‰‰ƒ‹‹‹ ‹ ‹‹‹‹‹‹‹"‹%‹(‹+‹.‹1‹4‹7‹:‹=‹@‹C‹F‹I‹L‹O‹R‹U‹X‹[‹^‹a‹d‹g‹j‹m‹p‹s‹v‹y‹|‹‹‚‹…‹ˆ‹‹‹Ž‹‘‹”‹—‹š‹‹ ‹Ŗ‹Ļ‹Š‹Ŧ‹¯‹˛‹ĩ‹¸‹ģ‹ž‹Á‹Ä‹Į‹Ę‹Í‹Đ‹Ķ‹Ö‹Ų‹Ü‹ß‹â‹å‹č‹ë‹î‹ņ‹ô‹÷‹ú‹ũŒŒŒŒ Œ ŒŒŒŒŒŒŒ!Œ$Œ'Œ*Œ-Œ0Œ3Œ6Œ9Œ<Œ?ŒBŒEŒHŒKŒNŒQŒTŒWŒZŒ]Œ`ŒcŒfŒiŒlŒoŒrŒuŒxŒ{Œ~ŒŒ„Œ‡ŒŠŒŒŒ“Œ–Œ™ŒœŒŸŒĸŒĨŒ¨ŒĢŒŽŒąŒ´ŒˇŒēŒŊŒĀŒÃŒÆŒÉŒĖŒĪŒŌŒÕŒØŒÛŒŪŒáŒäŒįŒęŒíŒđŒķŒöŒųŒüŒ˙  #&),/258;>AJLnwy‘šœļŋÁäíīŽŽŽ Ž>ŽGŽIŽeŽnŽpŽŒŽ•Ž—ŽŋŽČŽĘŽëŽôŽöIRT— ĸÎ×Ų<EGoxzž§ŠØáã‘ ‘‘‘/‘8‘:‘Y‘b‘d‘ƒ‘Œ‘Ž‘Ŧ‘ĩ‘ˇ‘â‘ë‘í’’’’?’H’J’s’|’~’›’¤’Ļ’Ã’Ė’Î’ī’ø’ú““““8“A“C“k“t“v“ž“§“Š“Å“Î“Đ“÷””””&”(”F”O”Q”o”x”z”™”ĸ”¤””˔͔î”÷”ų••••F•O•Q•s•|•~•ž•§•Е͕ܕەô•ũ•˙–.–7–9–[–d–f–ˆ–‘–“–Ŧ–ĩ–ˇ–ā–é–ë—— ——%—.—0—J—S—U—}—†—ˆ—Ą—Ē—Ŧ—Í—Ö—Ø—ü˜˜˜(˜1˜3˜M˜V˜X˜t˜}˜˜Ŗ˜Ŧ˜Ž˜Ę˜Ķ˜Õ˜ö˜˙™™)™2™4™W™`™b™‚™‹™™ģ™Ä™Æ™ę™ķ™õššš š=šFšHšyš‚š„šĒšŗšĩšæšīšņ›› ›"›E›N›P›y›‚›„›Ž›ˇ›š›Ô›Ũ›ßœœœœ:œCœEœpœyœ{œ›œ¤œĻœĐœŲœÛ 4=?ajlĢ´ļõūžž*ž3ž5žWž`žbž„žžžĒžŗžĩžĪžØžÚŸŸ Ÿ Ÿ-Ÿ6Ÿ8Ÿ`ŸiŸkŸŽŸ—Ÿ™ŸŧŸÅŸĮŸčŸņŸķ    " f o q ŧ Å ĮĄĄĄĄ_ĄhĄjĄĒĄŗĄĩĸĸ ĸĸZĸcĸeĸŗĸŧĸžŖ ŖŖŖJŖSŖUŖ”ŖŖŸŖáŖęŖė¤'¤0¤2¤v¤¤¤É¤Ō¤ÔĨĨĨĨ]ĨfĨhĨĨĨŽĨ°ĨîĨ÷ĨųĻ;ĻDĻFĻ~χωĻÁĻĘĻ˧§§§P§Y§[§•§ž§ §æ§ī§ņ¨2¨;¨=¨x¨¨ƒ¨Ņ¨Ú¨ÜŠ"Š+Š-ŠsŠ|Š~ŠĖŠÕŠ×ĒĒĒĒWĒ`ĒbĒ•ĒžĒ ĒÛĒäĒæĢ)Ģ2Ģ4ĢlĢuĢwĢēĢÃĢÅŦŦŦŦPŦYŦ[Ŧ‘ŦšŦœŦåŦîŦđ­M­V­X­Ÿ­¨­Ē­ō­û­ũŽEŽNŽPŽ†ŽŽ‘ŽâŽëŽí¯4¯=¯?¯‰¯’¯”¯Ũ¯æ¯č°,°5°7°†°°‘°Ō°Û°Ũą ą)ą+ą{ą„ą†ąÎą×ąŲ˛˛'˛)˛˛ˆ˛Š˛Đ˛Ų˛Ûŗŗ$ŗ&ŗ[ŗdŗfŗŽŗˇŗš´ ´´´M´V´X´ ´Š´Ģ´į´đ´ōĩ7ĩ@ĩBĩ’ĩ›ĩĩúļļļ^ļgļiļŦļĩˇ ˇ ˇNˇWˇYˇœˇĨˇ§ˇæˇīˇņ¸0¸9¸;¸w¸€¸‚¸Â¸Ë¸Íš šššSš\š^š šŠšĢšõšūēēLēUēWē˜ēĄēŖēßēėēîēīēđēũē˙ģdģgģjģmģpģsģvģyģ|ģģ‚ģ…ģˆģ‹ģŽģ‘ģ”ģ—ģšģģ ģŖģĻģŠģŦģ¯ģ˛ģĩģ¸ģģģžģÁģÄģĮģĘģÍģĐģĶģÖģŲģÜģßģâģåģįģęģíģđģķģöŧ[ŧ^ŧaŧdŧgŧjŧmŧpŧsŧvŧyŧ|ŧŧ‚ŧ…ŧˆŧ‹ŧŽŧ‘ŧ”ŧ—ŧšŧŧ ŧŖŧĻŧŠŧŦŧ¯ŧ˛ŧĩŧ¸ŧģŧžŧÁŧÄŧĮŧĘŧÍŧĐŧĶŧÖŧŲŧÜŧßŧâŧåŧčŧëŧîŧûŧũŊŊŊ Ŋ ŊOŊ\Ŋ^ŊaŊcŊlŊnž)ž6ž8ž;ž=žFžHŋŋŋŋŋŋ"ŋ$ŋŲŋæŋčŋëŋíŋöŋøĀ6ĀCĀEĀHĀJĀSĀUÁÁ Á"Á%Á'Á0Á2ÁÁŒÁŽÁ‘Á“ÁœÁžÂWÂdÂfÂiÂkÂtÂvÂŪÂëÂíÂđÂōÂûÂũÃ\ÃiÃkÃnÃpÃyÃ{ÃËÃØÃÚÃŨÃßÃčÃęÄ+Ä8Ä:Ä=Ä?ÄHÄJĒğġĤÄĻÄ¯ÄąÅÅÅÅÅÅ%Å'řÅĻŨÅĢÅ­ÅļŸÆxƅƇƊƌƕƗĮMĮZĮ\Į_ĮaĮjĮlĮģĮČĮĘĮÍĮĪĮØĮÚČČ'Č)Č,Č.Č7Č9ȪȰȞČĩȡČĀČÂÉÉÉ É#É%É.É0ÉéÉöÉøÉûÉũĘĘĘHĘUĘWĘZĘ\ĘeĘgĘŅĘŪĘāĘãĘåĘîĘđËzˇˉˌˎ˗˙ËŲËæËčËëËíËöËøĖGĖTĖVĖYĖ[ĖdĖfĖĐĖŨĖßĖâĖäĖíĖīÍĨͲʹ͚͡ÍÂÍÄÎÎÎÎÎÎÎ!ÎgÎtÎvÎyÎ{΄ΆÎÛÎčÎęÎíÎīÎøÎúĪąĪžĪĀĪÃĪÅĪÎĪĐЅВДЗЙĐĸФĐņĐūŅŅŅŅŅŅLŅYŅ[Ņ^Ņ`ŅiŅkŅļŅÃŅÅŅČŅĘŅĶŅÕŌ;ŌHŌJŌMŌOŌXŌZŌÂŌĪŌŅŌÔŌÖŌßŌáĶ•ĶĸĶ¤Ķ§ĶŠĶ˛Ķ´ĶûÔÔ Ô ÔÔÔԊԗԙԜԞԧԩÔíÔúÔüÔ˙ÕÕ Õ Õ_ÕlÕnÕqÕsÕ|Õ~ÕėÕųÕûÕūÖÖ Ö ÖÅÖŌÖÔÖ×ÖŲÖâÖä׊×ļ׸×Á×Ä×Į×Ę×Í×Ö×Ų×Ü×Ū×á×üØ#Ø1ØYØbØlØu؂؄؍ؙؐؓؖØĸØĨبØĒØ­ØČØīØũŲ%Ų.Ų8ŲEŲGŲPŲSŲVŲYŲ\ŲeŲhŲkŲmŲpŲyŲ†Ų“Ų•ŲžŲĄŲ¤Ų§ŲĒŲŗŲļŲšŲģŲžŲĮŲŅŲŪŲāŲéŲėŲīŲōŲõŲūÚÚÚÚ ÚÚÚ%Ú2Ú4Ú=Ú@ÚCÚFÚIÚRÚUÚXÚZÚ]ÚfÚsÚ€Ú‚Ú‹ÚŽÚ‘Ú”Ú—Ú ÚŖÚĻÚ¨ÚĢÚÆÚíÚûÛ#Û,Û9ÛCÛPÛRÛ[Û^ÛaÛdÛgÛpÛsÛvÛxÛ{Û„Û‘ÛžÛ ÛŠÛĢÛ­Û¯ÛąÛēÛŊÛĀÛÂÛÅÛÎÛÛÛåÛōÛôÛũÜÜÜÜ ÜÜÜÜÜÜ8Ü_ÜmܕܞÜĻÜŗÜĩÜžÜÁÜÄÜĮÜĘÜĶÜÖÜŲÜÛÜŪÜįÜīÜüÜūŨŨ Ũ ŨŨŨŨŨ"Ũ$Ũ'Ũ0Ũ9ŨBŨOŨQŨZŨ]Ũ`ŨcŨfŨoŨrŨuŨwŨzŨƒŨŒŨ•ŨĸŨ¤Ũ­Ũ°ŨŗŨļŨšŨÂŨÅŨČŨĘŨÍŨčŪŪŪEŪNŪWŪ`ŪmŪoŪxŪzŪ|Ū~Ū€Ū‰ŪŒŪŪ‘Ū”ŪŪ§ŪąŪžŪĀŪÉŪĖŪĪŪŌŪÕŪŪŪáŪäŪæŪéŪōŪūßßßß ß#ß&ß)ß,ß5ß8ß;ß=ß@ßIßSß`ßbßkßnßqßtßw߀߃߆߈ߋߔߞߨßĩߡßĀßÃ߯ßÉßĖßÕߨßÛßŨßāßéßõß˙ā āāāāāāā(ā+ā.ā0ā3ā<āIāQā^ā`āiālāoārāuā~āā„ā†ā‰ā’āŸāŠāļā¸āÁāÄāĮāĘāÍāÖāŲāÜāŪāáāęā÷áááááááá$á'á*á,á/á8áBáLáYá[ádáfáhájáláuáxá{á}á€á‰á–á á­á¯á¸áēáŧážáĀáÉáĖáĪáŅáÔáŨáęáôâââ âââââ!â$â'â)â,â5â>âGâTâVâ_âbâeâhâkâtâwâzâ|ââˆâ‘âšâ§âŠâ˛â´âļâ¸âēâÃâÆâÉâËâÎâ×âáâéâöâøãããã ã ããããã!ã*ã7ãDãFãOãRãUãXã[ãdãgãjãlãoãxããŠã—ã™ãĸãĨã¨ãĢãŽãˇãēãŊãŋãÂãËãØãâãīãņãúãũäääääääää#ä0ä:äGäIäRäUäXä[ä^ägäjämäoärä{äˆä•ä—ä äŖäĻäŠäŦäĩä¸äģäŊäĀäÉäÖäãäåäîäņäôä÷äúååå å ååå å)å6å8åAåDåGåJåMåVåYå\å^åaåjåså|å‰å‹å”å—åšåå åŠåŦå¯åąå´åŊåÆåĪåÜåŪåįåéåëåíåīåøåûåūæææ ææ æ-æ/æ8æ:æ<æ>æ@æIæLæOæQæTæ]ægæqæ~æ€æ‰æŒææ’æ•æžæĄæ¤æĻæŠæ˛æŋæĖæÎæ×æÚæŨæāæãæėæīæōæôæ÷įį9įGįoįxį‚įŒį™į›į¤į§įĒį­į°įšįŧįŋįÁįÄįÍįÕįâįäįíįīįņįķįõįūčččč ččč)č6č8čAčCčEčGčIčRčUčXčZč]čfčsč~č‹čč–č™čœčŸčĸčĢčŽčąčŗčļčŋčÉčĶčāčâčëčîčņčôč÷ééééé ééé(é5é7é@éBéDéFéHéQéTéWéYé\éeéoéyé†éˆé‘é”é—éšééĻéŠéŦéŽéąéēéĮéÔéÖéßéáéãéåéįéđéķéöéøéûęęęę(ę*ęyę|ęę‚ę…ęˆę‹ęŽę‘ę”ę—ęšęę ęŖęĻęŠęŦę¯ę˛ęĩę¸ęģęžęÁęÄęĮęĘęÍęĐęĶęÖęŲęÜęßęâęåęčęëë:ë=ë@ëCëFëIëLëOëRëUëXë[ë^ëaëdëgëjëmëpësëvëyë|ëë‚ë…ëˆë‹ëŽë‘ë”ë—ëšëë ëŖëĻëŠëŦëšëģëžëĀëõėėėė ė>ėKėMėPėRė‡ė”ė–ė™ė›ėĐėŨėßėâėäíí&í(í+í-íbíoíqítívíĢí¸íēíŊíŋíôîîîîî=îJîLîOîQî†î“î•î˜îšîĪîÜîŪîáîãīī%ī'ī*ī,īaīnīpīsīuīĒīˇīšīŧīžīķđđđđđ<đIđKđNđPđ…đ’đ”đ—đ™đÎđÛđŨđāđâņņ$ņ&ņ)ņ+ņ`ņmņoņrņtņŠņļņ¸ņģņŊņōņ˙ōōōō;ōHōJōMōOō„ō‘ō“ō–ō˜ōÍōÚōÜōßōáķķ#ķ%ķ(ķ*ķ_ķlķnķqķsķ¨ķĩķˇķēķŧķņķūôôôô:ôGôIôLôNôƒôô’ô•ô—ôĖôŲôÛôŪôāõõ"õ$õ'õ)õ^õkõmõpõrõ§õ´õļõšõģõđõũõ˙ööö9öFöHöKöMö‚öö‘ö”ö—öšööšöÂöÄöÅöŌöÔö×öÚöŨöāöü÷ ÷ ÷÷÷÷÷$÷&÷)÷,÷/÷2÷?÷A÷D÷G÷J÷M÷i÷v÷x÷{÷~÷÷„÷‘÷“÷–÷™÷œ÷Ÿ÷Ŧ÷Ž÷ą÷´÷ˇ÷ē÷Ö÷ã÷å÷č÷ë÷î÷ņ÷ūøøøø ø øøøø!ø$ø'øCøPøRøUøXø[ø^økømøpøsøvøyø†øˆø‹øŽø‘ø”øøŸøĸøĨøēøÍø×øŲøÚøŨøāøãøđųųųųų%ų-ų0ų3ų<ųJųQųXųaųmųrų~ų‡ų ų§ųĀųÖųãųåųčųëųîųņųūúúúú ú ú(ú5ú7ú:ú=ú@úCúPúRúUúXú[ú^úzú‡ú‰úŒúú’ú•úĸú¤ú§úĒú­ú°úŊúŋúÂúÅúČúËúØúÚúŨúāúãúæúīúņúôú÷û ûûûûûû%û8û;û=ûJûLûOûRûUûXûeûgûjûmûpûsû€û‚û…ûˆû‹ûŽû›ûû ûŖûĻûŠûļû¸ûģûžûÁûÄûÍûĪûŌûÕûęûėûíûđûķûöüüü üüüü%ü'ü*ü-ü0ü3ü@üBüEüHüKüNü[ü]ü`ücüfüiürütüwüzüü‘ü’ü•ü˜ü›ü¨üĒü­ü°üŗüļüŋüÁüÄüĮüÜüŪüßüâüåüčüõü÷üúüũũũũũũũũũũ:ũCũEũHũKũ`ũbũcũfũiũlũyũ|ũ~ũ‹ũũũ“ũ–ũ™ũĻũ¨ũĢũŽũąũ´ũÁũÃũÆũÉũĖũĪũÜũŪũáũäũįũęũ÷ũųũüũ˙ūūūūūūūū ū-ū/ū6ū9ū<ū?ūFūIūLūOūpū“ū´ūŊūŋūÂūÅūŲūđūųūüū˙˙˙ ˙ ˙˙˙ ˙"˙'˙*˙-˙2˙5˙8˙L˙d˙‰˙˙˙Ÿ˙ĸ˙Ĩ˙¨˙Ģ˙š˙Į˙Ô˙Ö˙Ũ˙ā˙ã˙æ˙í˙đ˙ķ˙ö)D]fhqtwz}ŠŒ“–™œŖĻŠŦÕ/8:@MOVY\_filox{‰–˜ŸĸĨ¨¯˛ĩ¸ÁČŅÔ4ACPSVY\_bortwz|~ŖČō%.6?BKRWlnwy|~‹¤§Ē­°ŗļšŧŋÂÅÜßâåčëîņô÷úũ  O\^ac°ŊŋÂÄXegjl°ŊŋÂÄcpruwˇÄÆÉË      n { } € ‚     # & + . 1 7 > G P ] _ d g j o r u { „  š œ Ą ¤ § Ŧ ¯ ˛ ¸ Á Ę × Ų Ū á ä é ė ī õ ū      ! & ) , 2 ; D Q S X [ ^ c f i r { ˆ Š  ’ • š    Ļ ¯ ¸ Å Į Ė Ī Ō × Ú Ũ æ ī ü ū        & 3 5 : = @ E H K T ] j l q t w |  ‚ ˆ ‘ š Ŗ Ĩ ē ŧ ŋ Â Å Č Ë Î Ņ Ô × ä æ é ë ô ö f s u x z ƒ … ī ü ū vƒ…ˆŠ“•˙ „‘“–˜ĄŖ (*œŠĢްšģ)68;=FH˛ÄÉĪŅpcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/pjkerly.xcuserdatad/000077500000000000000000000000001475730356400327545ustar00rootroot00000000000000UserInterfaceState.xcuserstate000066400000000000000000002517611475730356400407450ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/pjkerly.xcuserdatadbplist00Ô†‡X$versionX$objectsY$archiverT$top† ¯Ą'()*+,-.DEFGHIJKLMNOPVW\defghnvw{~Š‹ŒŽžŸ ĄĸŖ¤ĒĢą˛ēģŧÄÅÍÎÔØŲÚāæū˙ $%-.167=ADGMQRhijklmnopqtx{~„А˜™šĸŖ¤Ŧ­ŽÄÅÆĮČÉĘËĖÍĶÔÚÛåæįčėū˙"*+,456:BCDNOPQRSWghijklmtwz}€ƒ†‰Œ’•˜›žĄ¤§Ē­°ŗļšŧŋÂÅČËÎŅÔ×ÚŨāãæéėīōõøûū  "%(+.147:=@CFILORUX[^adgjmpsvy|‚…ˆ‹Ž‘”—š ŖĻŠŦ¯˛ĩ¸ģžÁÄĮĘÍĐĶÖŲÜßâåčëîņô÷úũ  !$'*-0369<?BEHKNQTWZ]`cfilorux{~„‡Š“–™œŸĸĨ¨ĢŽą´ˇēŊĀÃÆÉĖĪŌÕØÛŪáäįęíđķöųü˙  #&),/258;>ADGJMPSVY\_behknqtwz}€„<‡‰Ž’œĄ§¨Ŧ­ąĩšēģÁÄČÉÕÖרŲåæįčéėōķųū!)*+345=GMQUVW[`dhlmq…†‡ˆ‰Š‹Œ•–—ŸŖ¤°ą˛ŗ´ēģĀČÉĘËĖÔÕÖ×Ûāáâėíîīđņõ       " % ( + . 1 4 7 : = @ C F I L O R U X [ ^ a d g j m p s v y |  ‚ … ˆ ‹ Ž ‘ ” — š    Ŗ Ļ Š Ŧ ¯ ˛ ĩ ¸ ģ ž Á Ä Į Ę Í Đ Ķ Ö Ų Ü ß â å č ë î ņ ô ÷ ú ũ         ! $ ' * - 0 3 6 9 < ? B E H K N Q T W Z ] ` c f i l o r u x { ~  „ ‡ Š   “ – ™ œ Ÿ ĸ Ĩ ¨ Ģ Ž ą ´ ˇ ē Ŋ Ā Ã Æ É Ė Ī Ō Õ Ø Û Ū á ä į ę í đ ķ ö ų ü ˙          # & ) , / 2 5 8 ; > A D G J M P S V Y \ _ b e h k n q t w z } € ƒ † ‰ Œ  ’ • ˜ › ž Ą ¤ § Ē ­ ° ŗ ļ š ŧ ŋ Â Å Č Ë Î Ņ Ô × Ú Ũ ā ã æ é ė ī ō õ ø û ū          " % ) 5 6 : ? @ J K L P ` g j m p s v y |  ‚ … ˆ ‹ Ž ‘ ” — š    Ŗ Ļ Š Ŧ ¯ ˛ ĩ ¸ ģ ž Á Ä Į Ę Í Đ Ķ Ö Ų Ü ß â å č ë î ņ ô ÷ ú ũ  !$'*-0369<?BEHKNQTWZ]`cfilorux{~„‡Š“–™œŸĸĨ¨ĢŽą´ˇēŊĀÃÆÉĖĪŌÕØÛŪáäįęíđķöųü˙  #&),/258;>ADGJMPSVY\_behknqtwz}€ƒ†‰Œ’•˜›žĄ¤§Ē­°ŗļšŧŋÂÅČËÎŅÔ×ÚŨāãæéėīōõøûū  "%(+.147:=@CFILORUX[^adgjmpswz‚†‡‹Œ–—˜™žŖĻĒĢŦšžÁÅÆĮÍÎĪãäåæįčéęëėíîīđöú%&'()*+5678BCDEOPQUYijklmqtuvz…U$nullĶ WNS.keysZNS.objectsV$classĸ €€ĸ€˙€!_$F4F24FE0-12C0-4EA1-B30C-7297EF84ECD8_IDEWorkspaceDocumentĶ &§€€€€€ € € § ! $$€ ũū€€€€€%_>IDEWorkspaceTabController_95C7B26E-9A03-42FB-BAB5-20CF5592BF08^IDEWindowFrame_!IDEOrderedWorkspaceTabControllers_,IDEWorkspaceWindowControllerUniqueIdentifier_IDEActiveWorkspaceTabController_IDEWindowToolbarIsVisible_IDEWindowTabBarIsVisibleĶ /9&Š012345678€ €€€€€€€€Š:$<=>?$AB€€€€€&€q€€{€|€%_AssistantEditorsLayout_IDEShowNavigator[IDETabLabel_-IDEWorkspaceTabControllerUtilityAreaSplitView_IDENavigatorArea_,IDEWorkspaceTabControllerDesignAreaSplitView_IDEShowUtilities^IDETabFilePath]IDEEditorArea _PcmMsr.xcodeprojĶ QS&ĄR€ĄT€€%_DVTSplitViewItemsŌ X[ĸYZ€€"€$Ķ ]`ĸ^_€€ĸab€€ €!]DVTIdentifier_DVTViewMagnitudeP#@°ŌijklZ$classnameX$classes\NSDictionaryĸkmXNSObjectĶ orĸ^_€€ĸat€€#€!#@n Ōijxy^NSMutableArrayŖxzmWNSArrayŌij|}_NSMutableDictionaryŖ|kmĶ „&¤€‚ƒ€'€(€)€*¤…†‡ˆ€+€C€`€a€%_ Xcode.IDEKit.Navigator.Structure_ Xcode.IDEKit.Navigator.BatchFind_SelectedNavigator_Xcode.IDEKit.Navigator.IssuesĶ –&ϐ‘’“”•€,€-€.€/€0€1Ļ—˜™™›œ€2€=€?€?€@€A€%_IDEExpandedItemsTree_,IDENavigatorExpandedItemsBeforeFilteringTree_IDESCMStatusFilteringEnabled_!IDERecentDocumentFilteringEnabled^IDEVisibleRect_IDESelectedTreeĶ Ĩ§ĄĻ€3Ą¨€4€!_IDEValuesAsTreeĶ ŦŽ&Ą­€5Ą¯€6€%VPcmMsrĶ ŗļ&ĸ´ĩ€7€8ĸ´¸€7€9€%P]PcmMsrLibraryĶ ŊĀ&ĸ´ŋ€7€:ĸ´Â€7€;€%VPcmMsrĶ ÆÉ&ĸĮ´€<€7ĸ´´€7€7€%_Supporting FilesĶ ĪŅĄĻ€3ĄŌ€>€!Ķ ÕÖ&  €%_{{0, 0}, {259, 754}}Ķ ÛŨĄĻ€3ĄŪ€B€!Ķ áã&Ą­€5Ą´€7€%Ķ įō&Ēčéęëėíîīđņ€D€E€F€G€H€I€J€K€L€MĒķ:õö÷øöö$ü€N€€P€Y€Z€\€Y€Y€€^€%_$IDEBatchFindNavigatorCollapsedGroups_#IDEBatchFindNavigatorScrollPosition_)IDEBatchFindNavigatorFindAttributedString_IDEBatchFindMatchStyle_'IDEBatchFindNavigatorSelectedRowIndexes_,IDEBatchFindNavigatorReplaceAttributedString_IDEBatchFindFindType_IDEBatchFindNavigatorFindMode_IDEBatchFindIgnoreCase_.IDEBatchFindNavigatorSelectedLocationsStateKeyŌ M \NSRangeCount€OŌij  _NSMutableIndexSetŖm_NSMutableIndexSetZNSIndexSetĶ XNSString\NSAttributes€Q€X€SŌ YNS.string€R_PRODUCT_BUNDLE_IDENTIFIERŌij_NSMutableStringŖmXNSStringĶ !Ą €TĄ"€U€!VNSFontÔ&'( )*+,VNSSizeXNSfFlagsVNSName#@& €V€W_.AppleSystemUIFontŌij/0VNSFontĸ/mŌij23_NSMutableAttributedStringŖ45m_NSMutableAttributedString_NSAttributedStringÔ8  9:;<?ZNSIndexSetĸ@mZNSIndexSetŌ B€]€XŌ F€RPĶ HJĄĻ€3ĄK€_€!Ķ NO&  €%_ Xcode.IDEKit.Navigator.StructureĶ S]&ŠT”VWXYZ[\€b€0€c€d€e€f€g€h€iŠ™_`a™cd™f€?€j€k€m€?€n€o€?€p€%_IDEErrorFilteringEnabled_IDECollapsedFiles_IDEExpandedIssues^IDEShowsByType_IDESelectedNavigables_IDECollapsedTypes_IDERecentFilteringEnabled_IDECollapsedGroups_{{0, 0}, {259, 725}}Ō rs €lŌijuv\NSMutableSetŖuwmUNSSetŌ ys €lŌ |[ €$Ō s €lŌ ‚s €lĶ …‡&ĄR€Ąˆ€r€%Ō ‹[ŖŒŽ€s€v€y€$Ķ ‘”ĸ^_€€ĸ•–€t€u€!_IDENavigatorArea#@p@Ķ ›žĸ^_€€ĸŸ €w€x€!]IDEEditorArea#@‹€Ķ Ĩ¨ĸ^_€€ĸŠ–€z€u€!_IDEUtilitiesArea_T/Users/pjkerly/PCMRepository/ssg_intelpcm-main/MacMSRDriver/PcmMsr/PcmMsr-Info.plistĶ ¯š&Š°ą˛ŗ´ĩ€}€~€€€€€‚€ƒ€„€…Š™ģ$::ŋĀÁ€?€†€€€́čéņ€%_ShowDebuggerArea_IDEEditorMode_Standard]IDEShowEditor_VersionEditorSubmodeZEditorMode_IDEDefaultDebugArea_ DefaultPersistentRepresentations_DebuggerSplitViewZlayoutTreeĶ ÎĐ&ĄĪ€‡ĄŅ€ˆ€%_%EditorLayout_PersistentRepresentationĶ Õ×&Ąր‰Ą؀Š€%TMainĶ ÜāŖŨŪ߀‹€Œ€Ŗá:『€Ņ€!_)EditorLayout_StateSavingStateDictionaries_EditorLayout_Selected_EditorLayout_GeometryŌ éëĄꀏĀĶ íõ&§îīđņōķô€€‘€’€“€”€•€–§ö÷øųųûü€—€˜Á́́΁Ī€%\FileDataType[EditorState_ArchivableRepresentation_NavigableItemName_DocumentNavigableItemName_DocumentExtensionIdentifier[DocumentURL_com.apple.xcode.projectĶ  ¤   €™€š€›€œ¤ €€Ĩ€Ļ€!_(Xcode3ProjectEditor.sourceList.splitview_,Xcode3ProjectEditorPreviousTargetEditorClass_,Xcode3ProjectEditorSelectedDocumentLocations_-Xcode3ProjectEditor_Xcode3BuildSettingsEditorĶ &Ą€žĄ€Ÿ€%_DVTSplitViewItemsŌ [ĸ € €Ŗ€$Ķ #&ĸ^_€€ĸ'(€Ą€ĸ€!_!sourceListSplitViewItemIdentifier#@e@Ķ -0ĸ^_€€ĸa2€€¤€!#@†(_Xcode3BuildSettingsEditorŌ 7ëĄ8€§ĀÔ;<= >?@AYselectionYtimestamp[documentURL€Ē€Š€¨Á_Tfile:///Users/pjkerly/PCMRepository/ssg_intelpcm-main/MacMSRDriver/PcmMsr.xcodeproj/#Aŧ;AÅ{Ķ EI&ŖFGH€Ģ€Ŧ€­ŖJKL€Ž€¯€°€%VEditorVTarget_"Xcode3BuildSettingsEditorLocations_Xcode3BuildSettingsEditor\PcmMsrDriverŌ TëĄU€ąĀĶ X_&ĻYZ[\]^€˛€ŗ€´€ĩ€ļ€ˇĻ`ab::b€¸Ŋŋ€€ŋ€%_#Collapsed Build Property Categories_Selected Build Properties_$Xcode3BuildSettingsEditorDisplayMode_#Xcode3BuildPropertyValueDisplayMode_Xcode3BuildSettingsEditorMode_"Xcode3BuildPropertyNameDisplayModeŌ n[¯opqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩϧ¨ŠĒĢŦ­Ž¯°ą˛ŗ´ĩšēģŧŊžŋĀÁÂÃÄÅÆĮČÉĘËĖÍÎĪĐŅŌĶÔÕÖרŲÚÛÜŨŪßāáâãäåæįčéęëėíîīđņōķôõö÷øųúûüũū˙      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr€š€ē€ģ€ŧ€Ŋ€ž€ŋ€Ā€Á€Â€Ã€Ä€Å€Æ€Į€Č€É€Ę€Ë€Ė€Í€Î€Ī€Đ€Ņ€Ō€Ķ€Ô€Õ€Ö€×€Ø€Ų€Ú€Û€Ü€Ũ€Ū€ß€ā€á€â€ã€ä€å€æ€į€č€é€ę€ë€ė€í€î€ī€đ€ņ€ō€ķ€ô€õ€ö€÷€ø€ų€ú€û€ü€ũ€ū€˙      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Ё‹Œށ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩρ§¨Ёǁ́Ŧ­ށ¯°ą˛ŗ´ĩļˇ¸šēģŧ€$Ō v€R_Architectures||ADDITIONAL_SDKSŌ y€R_Architectures||ARCHSŌ |€R_Architectures||SDKROOTŌ €R_#Architectures||SUPPORTED_PLATFORMSŌ ‚€R_Architectures||VALID_ARCHSŌ …€R_'Assets||ASSET_PACK_MANIFEST_URL_PREFIXŌ ˆ€R_,Assets||EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLEŌ ‹€R_#Assets||ENABLE_ON_DEMAND_RESOURCESŌ Ž€R_1Assets||ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGSŌ ‘€R_+Assets||ON_DEMAND_RESOURCES_PREFETCH_ORDERŌ ”€R_Build Locations||SYMROOTŌ —€R_Build Locations||OBJROOTŌ š€R_%Build Locations||SHARED_PRECOMPS_DIRŌ €R_Build Options||BUILD_VARIANTSŌ  €R_Build Options||GCC_VERSIONŌ Ŗ€R_/Build Options||EMBEDDED_CONTENT_CONTAINS_SWIFTŌ Ļ€R_'Build Options||GENERATE_PROFILING_CODEŌ Š€R_@Build Options||PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIRŌ Ŧ€R_.Build Options||APPLICATION_EXTENSION_API_ONLYŌ ¯€R_2Build Options||SCAN_ALL_SOURCE_FILES_FOR_INCLUDESŌ ˛€R_ Build Options||VALIDATE_PRODUCTŌ ĩ€R_%Code Signing||CODE_SIGN_ENTITLEMENTSŌ ¸€R_!Code Signing||CODE_SIGN_IDENTITYŌ ģ€R_,Code Signing||CODE_SIGN_RESOURCE_RULES_PATHŌ ž€R_$Code Signing||OTHER_CODE_SIGN_FLAGSŌ Á€R_#Code Signing||PROVISIONING_PROFILEŌ ĀR_Deployment||STRIPFLAGSŌ Į€R_Deployment||ALTERNATE_GROUPŌ ʀR_Deployment||ALTERNATE_OWNERŌ ̀R_Deployment||ALTERNATE_MODEŌ ЀR_(Deployment||ALTERNATE_PERMISSIONS_FILESŌ Ķ€R_!Deployment||COMBINE_HIDPI_IMAGESŌ րR_ Deployment||DEPLOYMENT_LOCATIONŌ Ų€R_&Deployment||DEPLOYMENT_POSTPROCESSINGŌ ܀R_Deployment||INSTALL_GROUPŌ ߀R_Deployment||INSTALL_OWNERŌ â€R_Deployment||INSTALL_MODE_FLAGŌ å€R_Deployment||DSTROOTŌ č€R_Deployment||INSTALL_PATHŌ ë€R_%Deployment||MACOSX_DEPLOYMENT_TARGETŌ î€R_%Deployment||PRODUCT_DEFINITION_PLISTŌ ņ€R_-Deployment||RESOURCES_TARGETED_DEVICE_FAMILYŌ ô€R_Deployment||SKIP_INSTALLŌ ÷€R_$Deployment||STRIP_INSTALLED_PRODUCTŌ ú€R_Deployment||STRIP_STYLEŌ ũ€R_Deployment||SEPARATE_STRIPŌ €R_"Headers||COPY_HEADERS_RUN_UNIFDEFŌ €R_$Headers||COPY_HEADERS_UNIFDEF_FLAGSŌ €R_Kernel Module||MODULE_NAMEŌ  €R_Kernel Module||MODULE_STARTŌ  €R_Kernel Module||MODULE_STOPŌ €R_Kernel Module||MODULE_VERSIONŌ €R_Linking||BUNDLE_LOADERŌ €R_%Linking||DYLIB_COMPATIBILITY_VERSIONŌ €R_Linking||DYLIB_CURRENT_VERSIONŌ €R_Linking||DEAD_CODE_STRIPPINGŌ €R_'Linking||LINKER_DISPLAYS_MANGLED_NAMESŌ !€R_,Linking||PRESERVE_DEAD_CODE_INITS_AND_TERMSŌ $€R_Linking||LD_DYLIB_INSTALL_NAMEŌ '€R_!Linking||DYLIB_INSTALL_NAME_BASEŌ *€R_Linking||EXPORTED_SYMBOLS_FILEŌ -€R_Linking||LD_NO_PIEŌ 0€R_Linking||INIT_ROUTINEŌ 3€R_&Linking||LINK_WITH_STANDARD_LIBRARIESŌ 6€R_Linking||MACH_O_TYPEŌ 9€R_Linking||ORDER_FILEŌ <€R_Linking||OTHER_LIBTOOLFLAGSŌ ?€R_Linking||OTHER_LDFLAGSŌ B€R_Linking||OTHER_TAPI_FLAGSŌ E€R_%Linking||GENERATE_MASTER_OBJECT_FILEŌ H€R_Linking||PRELINK_LIBSŌ K€R_Linking||KEEP_PRIVATE_EXTERNSŌ N€R_7Linking||LD_QUOTE_LINKER_ARGUMENTS_FOR_COMPILER_DRIVERŌ Q€R_$Linking||REEXPORTED_FRAMEWORK_NAMESŌ T€R_"Linking||REEXPORTED_LIBRARY_NAMESŌ W€R_"Linking||REEXPORTED_LIBRARY_PATHSŌ Z€R_!Linking||LD_RUNPATH_SEARCH_PATHSŌ ]€R_Linking||SEPARATE_SYMBOL_EDITŌ `€R_Linking||PRELINK_FLAGSŌ c€R_!Linking||SUPPORTS_TEXT_BASED_APIŌ f€R_Linking||SECTORDER_FLAGSŌ i€R_Linking||TEXT_BASED_API_FILEŌ l€R_!Linking||UNEXPORTED_SYMBOLS_FILEŌ o€R_Linking||WARNING_LDFLAGSŌ r€R_Linking||LD_GENERATE_MAP_FILEŌ u€R_%Packaging||APPLY_RULES_IN_COPY_FILESŌ x€R_.Packaging||CREATE_INFOPLIST_SECTION_IN_BINARYŌ {€R_Packaging||DEFINES_MODULEŌ ~€R_ Packaging||EXECUTABLE_EXTENSIONŌ €R_Packaging||EXECUTABLE_PREFIXŌ „€R_+Packaging||INFOPLIST_EXPAND_BUILD_SETTINGSŌ ‡€R_!Packaging||GENERATE_PKGINFO_FILEŌ Š€R_Packaging||FRAMEWORK_VERSIONŌ €R_Packaging||INFOPLIST_FILEŌ €R_.Packaging||INFOPLIST_OTHER_PREPROCESSOR_FLAGSŌ “€R_#Packaging||INFOPLIST_OUTPUT_FORMATŌ –€R_.Packaging||INFOPLIST_PREPROCESSOR_DEFINITIONSŌ ™€R_#Packaging||INFOPLIST_PREFIX_HEADERŌ œ€R_Packaging||MODULEMAP_FILEŌ Ÿ€R_ Packaging||INFOPLIST_PREPROCESSŌ ĸ€R_&Packaging||COPYING_PRESERVES_HFS_DATAŌ Ĩ€R_'Packaging||PRIVATE_HEADERS_FOLDER_PATHŌ ¨€R_"Packaging||MODULEMAP_PRIVATE_FILEŌ Ģ€R_%Packaging||PRODUCT_BUNDLE_IDENTIFIERŌ Ž€R_Packaging||PRODUCT_MODULE_NAMEŌ ą€R_Packaging||PRODUCT_NAMEŌ ´€R_$Packaging||PLIST_FILE_OUTPUT_FORMATŌ ˇ€R_&Packaging||PUBLIC_HEADERS_FOLDER_PATHŌ ē€R_(Packaging||STRINGS_FILE_OUTPUT_ENCODINGŌ Ŋ€R_Packaging||WRAPPER_EXTENSIONŌ Ā€R_'Search Paths||ALWAYS_SEARCH_USER_PATHSŌ ÀR_%Search Paths||FRAMEWORK_SEARCH_PATHSŌ ƀR_"Search Paths||HEADER_SEARCH_PATHSŌ ɀR_#Search Paths||LIBRARY_SEARCH_PATHSŌ Ė€R_Search Paths||REZ_SEARCH_PATHSŌ Ī€R_<Search Paths||EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ Ō€R_<Search Paths||INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ՀR_Search Paths||USE_HEADERMAPŌ ؀R_'Search Paths||USER_HEADER_SEARCH_PATHSŌ ۀR_Testing||TEST_HOSTŌ Ū€R_2Testing||TREAT_MISSING_BASELINES_AS_TEST_FAILURESŌ á€R_$Versioning||CURRENT_PROJECT_VERSIONŌ ä€R_Versioning||VERSION_INFO_FILEŌ į€R_%Versioning||VERSION_INFO_EXPORT_DECLŌ ę€R_ Versioning||VERSION_INFO_PREFIXŌ í€R_ Versioning||VERSION_INFO_SUFFIXŌ đ€R_Versioning||VERSIONING_SYSTEMŌ ķ€R_!Versioning||VERSION_INFO_BUILDERŌ ö€R_/(null) - Deployment||IPHONEOS_DEPLOYMENT_TARGETŌ ų€R_+(null) - Deployment||TVOS_DEPLOYMENT_TARGETŌ ü€R_.(null) - Deployment||WATCHOS_DEPLOYMENT_TARGETŌ ˙€R_?Apple LLVM 7.0 - Code Generation||CLANG_DEBUG_INFORMATION_LEVELŌ €R_?Apple LLVM 7.0 - Code Generation||CLANG_X86_VECTOR_INSTRUCTIONSŌ €R_€R_6Apple LLVM 7.0 - Custom Compiler Flags||WARNING_CFLAGSŌ A€R_4Apple LLVM 7.0 - Language||GCC_CHAR_IS_UNSIGNED_CHARŌ D€R_1Apple LLVM 7.0 - Language||GCC_ENABLE_ASM_KEYWORDŌ G€R_2Apple LLVM 7.0 - Language||GCC_C_LANGUAGE_STANDARDŌ J€R_,Apple LLVM 7.0 - Language||GCC_CW_ASM_SYNTAXŌ M€R_-Apple LLVM 7.0 - Language||GCC_INPUT_FILETYPEŌ P€R_:Apple LLVM 7.0 - Language||GCC_LINK_WITH_DYNAMIC_LIBRARIESŌ S€R_/Apple LLVM 7.0 - Language||GCC_ENABLE_TRIGRAPHSŌ V€R_BApple LLVM 7.0 - Language||GCC_ENABLE_FLOATING_POINT_LIBRARY_CALLSŌ Y€R_BApple LLVM 7.0 - Language||GCC_INCREASE_PRECOMPILED_HEADER_SHARINGŌ \€R_7Apple LLVM 7.0 - Language||GCC_PRECOMPILE_PREFIX_HEADERŌ _€R_,Apple LLVM 7.0 - Language||GCC_PREFIX_HEADERŌ b€R_7Apple LLVM 7.0 - Language||GCC_ENABLE_BUILTIN_FUNCTIONSŌ e€R_4Apple LLVM 7.0 - Language||GCC_ENABLE_PASCAL_STRINGSŌ h€R_*Apple LLVM 7.0 - Language||GCC_SHORT_ENUMSŌ k€R_=Apple LLVM 7.0 - Language||GCC_USE_STANDARD_INCLUDE_SEARCHINGŌ n€R_Apple LLVM 7.0 - Language - Objective C||CLANG_ENABLE_OBJC_ARCŌ €R_4Apple LLVM 7.0 - Preprocessing||ENABLE_NS_ASSERTIONSŌ ’€R_:Apple LLVM 7.0 - Preprocessing||ENABLE_STRICT_OBJC_MSGSENDŌ •€R_QApple LLVM 7.0 - Preprocessing||GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPSŌ ˜€R_@Apple LLVM 7.0 - Warning Policies||GCC_WARN_INHIBIT_ALL_WARNINGSŌ ›€R_4Apple LLVM 7.0 - Warning Policies||GCC_WARN_PEDANTICŌ ž€R_?Apple LLVM 7.0 - Warning Policies||GCC_TREAT_WARNINGS_AS_ERRORSŌ Ą€R_KApple LLVM 7.0 - Warnings - All languages||GCC_WARN_CHECK_SWITCH_STATEMENTSŌ ¤€R_NApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_DEPRECATED_FUNCTIONSŌ §€R_LApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_DOCUMENTATION_COMMENTSŌ Ē€R_@Apple LLVM 7.0 - Warnings - All languages||CLANG_WARN_EMPTY_BODYŌ ­€R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_FOUR_CHARACTER_CONSTANTSŌ °€R_:Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_SHADOWŌ ŗ€R_EApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_BOOL_CONVERSIONŌ ļ€R_IApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_CONSTANT_CONVERSIONŌ š€R_KApple LLVM 7.0 - Warnings - All languages||GCC_WARN_64_TO_32_BIT_CONVERSIONŌ ŧ€R_EApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_ENUM_CONVERSIONŌ ŋ€R_DApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_INT_CONVERSIONŌ €R_NApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_IMPLICIT_SIGN_CONVERSIONŌ ŀR_TApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSIONŌ ȀR_SApple LLVM 7.0 - Warnings - All languages||GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETEDŌ ˀR_EApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_RETURN_TYPEŌ ΀R_GApple LLVM 7.0 - Warnings - All languages||GCC_WARN_MISSING_PARENTHESESŌ Ņ€R_TApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERSŌ ԀR_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_PROTOTYPESŌ ׀R_IApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_NEWLINEŌ ڀR_AApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_ASSIGN_ENUMŌ Ũ€R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_POINTER_SIGNEDNESSŌ ā€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_SIGN_COMPAREŌ ã€R_TApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSIONŌ æ€R_aApple LLVM 7.0 - Warnings - All languages||GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORSŌ é€R_]Apple LLVM 7.0 - Warnings - All languages||GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORSŌ ė€R_MApple LLVM 7.0 - Warnings - All languages||GCC_WARN_TYPECHECK_CALLS_TO_PRINTFŌ ī€R_GApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNINITIALIZED_AUTOSŌ ō€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNKNOWN_PRAGMASŌ õ€R_FApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_UNREACHABLE_CODEŌ ø€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_FUNCTIONŌ û€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_LABELŌ ū€R_DApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_PARAMETERŌ €R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_VALUEŌ €R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_VARIABLEŌ €R_BApple LLVM 7.0 - Warnings - C++||CLANG_WARN__EXIT_TIME_DESTRUCTORSŌ  €R_@Apple LLVM 7.0 - Warnings - C++||GCC_WARN_NON_VIRTUAL_DESTRUCTORŌ  €R_BApple LLVM 7.0 - Warnings - C++||GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONSŌ €R_FApple LLVM 7.0 - Warnings - C++||GCC_WARN_ABOUT_INVALID_OFFSETOF_MACROŌ €R_? @AB°9E<FMXchildrenVparent[contentType_ documentArchivableRepresentation[orientation€ųûķÕ“” •–HI™šLøôˁā÷Ō NëĄOõĀĶĸŖ R:ρö€ČVPcmMsr˙˙˙˙˙˙˙Ķ= <@¯°€¨ˀÖ>? @AB\°EM°Mú€û€Ō aëĄ;ōĀŌijef_'IDEWorkspaceTabControllerLayoutTreeNodeĸgm_'IDEWorkspaceTabControllerLayoutTreeNodeŌijij_#IDEWorkspaceTabControllerLayoutTreeĸkm_#IDEWorkspaceTabControllerLayoutTree_{{688, 4}, {1400, 873}}Ō n륀ĀĶ r{&¨stuvwxyz¨$}~€‚ƒ€cft{ž€%_BreakpointsActivated_DefaultEditorStatesForURLs\ActiveScheme_ActiveRunDestination_DefaultEditorFrameSizeForURLs_0LastCompletedPersistentSchemeBasedActivityReport_DocumentWindows_RecentEditorDocumentURLsĶ Ž‘&ĸ  ĸ’“ J€%_7Xcode.Xcode3ProjectSupport.EditorDocument.Xcode3Project_'Xcode.IDEKit.EditorDocument.PlistEditorĶ ˜›&ĸ™ü Īĸœ9€%Ķŧ Ŋ°ŋĸ€Ё _gfile:///Users/pjkerly/Downloads/IntelPerformanceCounterMonitor-PCM-V2.10/MacMSRDriver/PcmMsr.xcodeproj/Ķ ĨĒ&¤Ļ§¨Ё¤ĢŦ­ށ8€%_(Xcode3ProjectEditor.sourceList.splitview_,Xcode3ProjectEditorPreviousTargetEditorClass_,Xcode3ProjectEditorSelectedDocumentLocations_-Xcode3ProjectEditor_Xcode3BuildSettingsEditorĶ ĩˇ&ĄļĄ¸€%_DVTSplitViewItemsŌ ŧ[ĸŊž€$Ķ ÁÄĸÂÁĸÅƁ€!]DVTIdentifier_DVTViewMagnitude_!sourceListSplitViewItemIdentifier#@d`Ķ ÍĐĸÂÁĸŅԁ€!P#@†`_Xcode3BuildSettingsEditorŌ ØëĄ؁ ĀÔ;<= ÜŨŪA#"!Á_gfile:///Users/pjkerly/Downloads/IntelPerformanceCounterMonitor-PCM-V2.10/MacMSRDriver/PcmMsr.xcodeproj/#Aŧ;3æOšĶ ãį&Ŗä忁$%&Ŗčéę'()€%VEditorVTarget_"Xcode3BuildSettingsEditorLocations_Xcode3BuildSettingsEditor\PcmMsrDriverŌ ōëĄķ*ĀĶ öũ&Ļ÷øųúûü+,-./0Ļ:˙b:b€1ŋ€3ŋ€%_Xcode3BuildSettingsEditorMode_Selected Build Properties_$Xcode3BuildSettingsEditorDisplayMode_#Xcode3BuildPropertyValueDisplayMode_#Collapsed Build Property Categories_"Xcode3BuildPropertyNameDisplayModeŌ  [Ą 2€$Ō €R_%Packaging||PRODUCT_BUNDLE_IDENTIFIERŌ [¯ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩϧ¨ŠĒĢŦ­Ž¯°ą˛ŗ´ĩšēģŧŊžŋĀÁÂÃÄÅÆĮČÉĘËĖÍÎĪĐŅŌĶÔÕÖרŲÚÛÜŨŪßāáâãäåæįčéęëėíîīđņōķôõö÷øųúûüũū˙                  456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Ё‹Œށ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩρ§¨Ёǁ́Ŧ­ށ¯°ą˛ŗ´ĩļˇ¸šēģŧŊžŋÁÁāŁƁĮȁɁʁˁˁ́΁΁Ёҁԁ́ԁՁցׁ؁؁ځہ܁Ũہ߁āáâãäåæįčéęëėíîīđņōķôõö÷øųúûüũū˙      !"#$%&'()*+,-./01234567€$Ō  €R_Architectures||ADDITIONAL_SDKSŌ  €R_Architectures||ARCHSŌ  !€R_Architectures||SDKROOTŌ  $€R_#Architectures||SUPPORTED_PLATFORMSŌ  '€R_Architectures||VALID_ARCHSŌ  *€R_'Assets||ASSET_PACK_MANIFEST_URL_PREFIXŌ  -€R_,Assets||EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLEŌ  0€R_#Assets||ENABLE_ON_DEMAND_RESOURCESŌ  3€R_1Assets||ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGSŌ  6€R_+Assets||ON_DEMAND_RESOURCES_PREFETCH_ORDERŌ  9€R_Build Locations||SYMROOTŌ  <€R_Build Locations||OBJROOTŌ  ?€R_%Build Locations||SHARED_PRECOMPS_DIRŌ  B€R_Build Options||BUILD_VARIANTSŌ  E€R_Build Options||GCC_VERSIONŌ  H€R_/Build Options||EMBEDDED_CONTENT_CONTAINS_SWIFTŌ  K€R_'Build Options||GENERATE_PROFILING_CODEŌ  N€R_@Build Options||PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIRŌ  Q€R_.Build Options||APPLICATION_EXTENSION_API_ONLYŌ  T€R_2Build Options||SCAN_ALL_SOURCE_FILES_FOR_INCLUDESŌ  W€R_ Build Options||VALIDATE_PRODUCTŌ  Z€R_%Code Signing||CODE_SIGN_ENTITLEMENTSŌ  ]€R_!Code Signing||CODE_SIGN_IDENTITYŌ  `€R_,Code Signing||CODE_SIGN_RESOURCE_RULES_PATHŌ  c€R_$Code Signing||OTHER_CODE_SIGN_FLAGSŌ  f€R_#Code Signing||PROVISIONING_PROFILEŌ  i€R_Deployment||STRIPFLAGSŌ  l€R_Deployment||ALTERNATE_GROUPŌ  o€R_Deployment||ALTERNATE_OWNERŌ  r€R_Deployment||ALTERNATE_MODEŌ  u€R_(Deployment||ALTERNATE_PERMISSIONS_FILESŌ  x€R_!Deployment||COMBINE_HIDPI_IMAGESŌ  {€R_ Deployment||DEPLOYMENT_LOCATIONŌ  ~€R_&Deployment||DEPLOYMENT_POSTPROCESSINGŌ  €R_Deployment||INSTALL_GROUPŌ  „€R_Deployment||INSTALL_OWNERŌ  ‡€R_Deployment||INSTALL_MODE_FLAGŌ  Š€R_Deployment||DSTROOTŌ  €R_Deployment||INSTALL_PATHŌ  €R_%Deployment||MACOSX_DEPLOYMENT_TARGETŌ  “€R_%Deployment||PRODUCT_DEFINITION_PLISTŌ  –€R_-Deployment||RESOURCES_TARGETED_DEVICE_FAMILYŌ  ™€R_Deployment||SKIP_INSTALLŌ  œ€R_$Deployment||STRIP_INSTALLED_PRODUCTŌ  Ÿ€R_Deployment||STRIP_STYLEŌ  ĸ€R_Deployment||SEPARATE_STRIPŌ  Ĩ€R_"Headers||COPY_HEADERS_RUN_UNIFDEFŌ  ¨€R_$Headers||COPY_HEADERS_UNIFDEF_FLAGSŌ  Ģ€R_Kernel Module||MODULE_NAMEŌ  Ž€R_Kernel Module||MODULE_STARTŌ  ą€R_Kernel Module||MODULE_STOPŌ  ´€R_Kernel Module||MODULE_VERSIONŌ  ˇ€R_Linking||BUNDLE_LOADERŌ  ē€R_%Linking||DYLIB_COMPATIBILITY_VERSIONŌ  Ŋ€R_Linking||DYLIB_CURRENT_VERSIONŌ  Ā€R_Linking||DEAD_CODE_STRIPPINGŌ  ÀR_'Linking||LINKER_DISPLAYS_MANGLED_NAMESŌ  ƀR_,Linking||PRESERVE_DEAD_CODE_INITS_AND_TERMSŌ  ɀR_Linking||LD_DYLIB_INSTALL_NAMEŌ  Ė€R_!Linking||DYLIB_INSTALL_NAME_BASEŌ  Ī€R_Linking||EXPORTED_SYMBOLS_FILEŌ  Ō€R_Linking||LD_NO_PIEŌ  ՀR_Linking||INIT_ROUTINEŌ  ؀R_&Linking||LINK_WITH_STANDARD_LIBRARIESŌ  ۀR_Linking||MACH_O_TYPEŌ  Ū€R_Linking||ORDER_FILEŌ  á€R_Linking||OTHER_LIBTOOLFLAGSŌ  ä€R_Linking||OTHER_LDFLAGSŌ  į€R_Linking||OTHER_TAPI_FLAGSŌ  ę€R_%Linking||GENERATE_MASTER_OBJECT_FILEŌ  í€R_Linking||PRELINK_LIBSŌ  đ€R_Linking||KEEP_PRIVATE_EXTERNSŌ  ķ€R_7Linking||LD_QUOTE_LINKER_ARGUMENTS_FOR_COMPILER_DRIVERŌ  ö€R_$Linking||REEXPORTED_FRAMEWORK_NAMESŌ  ų€R_"Linking||REEXPORTED_LIBRARY_NAMESŌ  ü€R_"Linking||REEXPORTED_LIBRARY_PATHSŌ  ˙€R_!Linking||LD_RUNPATH_SEARCH_PATHSŌ  €R_Linking||SEPARATE_SYMBOL_EDITŌ  €R_Linking||PRELINK_FLAGSŌ  €R_!Linking||SUPPORTS_TEXT_BASED_APIŌ  €R_Linking||SECTORDER_FLAGSŌ  €R_Linking||TEXT_BASED_API_FILEŌ  €R_!Linking||UNEXPORTED_SYMBOLS_FILEŌ  €R_Linking||WARNING_LDFLAGSŌ  €R_Linking||LD_GENERATE_MAP_FILEŌ  €R_%Packaging||APPLY_RULES_IN_COPY_FILESŌ  €R_.Packaging||CREATE_INFOPLIST_SECTION_IN_BINARYŌ  €R_Packaging||DEFINES_MODULEŌ  #€R_ Packaging||EXECUTABLE_EXTENSIONŌ  &€R_Packaging||EXECUTABLE_PREFIXŌ  )€R_+Packaging||INFOPLIST_EXPAND_BUILD_SETTINGSŌ  ,€R_!Packaging||GENERATE_PKGINFO_FILEŌ  /€R_Packaging||FRAMEWORK_VERSIONŌ  2€R_Packaging||INFOPLIST_FILEŌ  5€R_.Packaging||INFOPLIST_OTHER_PREPROCESSOR_FLAGSŌ  8€R_#Packaging||INFOPLIST_OUTPUT_FORMATŌ  ;€R_.Packaging||INFOPLIST_PREPROCESSOR_DEFINITIONSŌ  >€R_#Packaging||INFOPLIST_PREFIX_HEADERŌ  A€R_Packaging||MODULEMAP_FILEŌ  D€R_ Packaging||INFOPLIST_PREPROCESSŌ  G€R_&Packaging||COPYING_PRESERVES_HFS_DATAŌ  J€R_'Packaging||PRIVATE_HEADERS_FOLDER_PATHŌ  M€R_"Packaging||MODULEMAP_PRIVATE_FILEŌ  P€R_%Packaging||PRODUCT_BUNDLE_IDENTIFIERŌ  S€R_Packaging||PRODUCT_MODULE_NAMEŌ  V€R_Packaging||PRODUCT_NAMEŌ  Y€R_$Packaging||PLIST_FILE_OUTPUT_FORMATŌ  \€R_&Packaging||PUBLIC_HEADERS_FOLDER_PATHŌ  _€R_(Packaging||STRINGS_FILE_OUTPUT_ENCODINGŌ  b€R_Packaging||WRAPPER_EXTENSIONŌ  e€R_'Search Paths||ALWAYS_SEARCH_USER_PATHSŌ  h€R_%Search Paths||FRAMEWORK_SEARCH_PATHSŌ  k€R_"Search Paths||HEADER_SEARCH_PATHSŌ  n€R_#Search Paths||LIBRARY_SEARCH_PATHSŌ  q€R_Search Paths||REZ_SEARCH_PATHSŌ  t€R_<Search Paths||EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ  w€R_<Search Paths||INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ  z€R_Search Paths||USE_HEADERMAPŌ  }€R_'Search Paths||USER_HEADER_SEARCH_PATHSŌ  €€R_Testing||TEST_HOSTŌ  ƒ€R_2Testing||TREAT_MISSING_BASELINES_AS_TEST_FAILURESŌ  †€R_$Versioning||CURRENT_PROJECT_VERSIONŌ  ‰€R_Versioning||VERSION_INFO_FILEŌ  Œ€R_%Versioning||VERSION_INFO_EXPORT_DECLŌ  €R_ Versioning||VERSION_INFO_PREFIXŌ  ’€R_ Versioning||VERSION_INFO_SUFFIXŌ  •€R_Versioning||VERSIONING_SYSTEMŌ  ˜€R_!Versioning||VERSION_INFO_BUILDERŌ  ›€R_/(null) - Deployment||IPHONEOS_DEPLOYMENT_TARGETŌ  ž€R_+(null) - Deployment||TVOS_DEPLOYMENT_TARGETŌ  Ą€R_.(null) - Deployment||WATCHOS_DEPLOYMENT_TARGETŌ  ¤€R_?Apple LLVM 7.0 - Code Generation||CLANG_DEBUG_INFORMATION_LEVELŌ  §€R_?Apple LLVM 7.0 - Code Generation||CLANG_X86_VECTOR_INSTRUCTIONSŌ  Ē€R_Apple LLVM 7.0 - Language - Objective C||CLANG_ENABLE_OBJC_ARCŌ  4€R_4Apple LLVM 7.0 - Preprocessing||ENABLE_NS_ASSERTIONSŌ  7€R_:Apple LLVM 7.0 - Preprocessing||ENABLE_STRICT_OBJC_MSGSENDŌ  :€R_QApple LLVM 7.0 - Preprocessing||GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPSŌ  =€R_@Apple LLVM 7.0 - Warning Policies||GCC_WARN_INHIBIT_ALL_WARNINGSŌ  @€R_4Apple LLVM 7.0 - Warning Policies||GCC_WARN_PEDANTICŌ  C€R_?Apple LLVM 7.0 - Warning Policies||GCC_TREAT_WARNINGS_AS_ERRORSŌ  F€R_KApple LLVM 7.0 - Warnings - All languages||GCC_WARN_CHECK_SWITCH_STATEMENTSŌ  I€R_NApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_DEPRECATED_FUNCTIONSŌ  L€R_LApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_DOCUMENTATION_COMMENTSŌ  O€R_@Apple LLVM 7.0 - Warnings - All languages||CLANG_WARN_EMPTY_BODYŌ  R€R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_FOUR_CHARACTER_CONSTANTSŌ  U€R_:Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_SHADOWŌ  X€R_EApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_BOOL_CONVERSIONŌ  [€R_IApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_CONSTANT_CONVERSIONŌ  ^€R_KApple LLVM 7.0 - Warnings - All languages||GCC_WARN_64_TO_32_BIT_CONVERSIONŌ  a€R_EApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_ENUM_CONVERSIONŌ  d€R_DApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_INT_CONVERSIONŌ  g€R_NApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_IMPLICIT_SIGN_CONVERSIONŌ  j€R_TApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSIONŌ  m€R_SApple LLVM 7.0 - Warnings - All languages||GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETEDŌ  p€R_EApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_RETURN_TYPEŌ  s€R_GApple LLVM 7.0 - Warnings - All languages||GCC_WARN_MISSING_PARENTHESESŌ  v€R_TApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERSŌ  y€R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_PROTOTYPESŌ  |€R_IApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_NEWLINEŌ  €R_AApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_ASSIGN_ENUMŌ  ‚€R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_POINTER_SIGNEDNESSŌ  …€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_SIGN_COMPAREŌ  ˆ€R_TApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSIONŌ  ‹€R_aApple LLVM 7.0 - Warnings - All languages||GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORSŌ  Ž€R_]Apple LLVM 7.0 - Warnings - All languages||GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORSŌ  ‘€R_MApple LLVM 7.0 - Warnings - All languages||GCC_WARN_TYPECHECK_CALLS_TO_PRINTFŌ  ”€R_GApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNINITIALIZED_AUTOSŌ  —€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNKNOWN_PRAGMASŌ  š€R_FApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_UNREACHABLE_CODEŌ  €R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_FUNCTIONŌ   €R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_LABELŌ  Ŗ€R_DApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_PARAMETERŌ  Ļ€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_VALUEŌ  Š€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_VARIABLEŌ  Ŧ€R_BApple LLVM 7.0 - Warnings - C++||CLANG_WARN__EXIT_TIME_DESTRUCTORSŌ  ¯€R_@Apple LLVM 7.0 - Warnings - C++||GCC_WARN_NON_VIRTUAL_DESTRUCTORŌ  ˛€R_BApple LLVM 7.0 - Warnings - C++||GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONSŌ  ĩ€R_FApple LLVM 7.0 - Warnings - C++||GCC_WARN_ABOUT_INVALID_OFFSETOF_MACROŌ  ¸€R_=€¨Á#Aŧ;AÄhėĶ A E&ŖFG D€Ģ€Ŧ?Ŗ FK H@€¯A€%_"Xcode3BuildSettingsEditorLocations_Xcode3BuildSettingsEditorŌ MëĄ NBĀĶ Q X&ĻYZ[\]^€˛€ŗ€´€ĩ€ļ€ˇĻ Y Zb::bCHŋ€€ŋ€%Ō a[¯ b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~  €  ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ  Ž   ‘ ’ “ ” • – — ˜ ™ š › œ  ž Ÿ   Ą ĸ Ŗ ¤ Ĩ Ļ § ¨ Š Ē Ģ Ŧ ­ Ž ¯ ° ą ˛ ŗ ´ ĩ ļ ˇ ¸ š ē ģ ŧ Ŋ ž ŋ Ā Á Â Ã Ä Å Æ Į Č É Ę Ë Ė Í Î Ī Đ Ņ Ō Ķ Ô Õ Ö × Ø Ų Ú Û Ü Ũ Ū ß ā á â ã ä å æ į č é ę ë ė í î ī đ ņ ō ķ ô õ ö ÷ ø ų ú û ü ũ ū ˙                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d eDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Ё‹Œށ‘’“”•–—˜™š›œžŸ ĄĸŖ¤Ĩρ§¨Ёǁ́Ŧ­ށ¯°ą˛ŗ´ĩļˇ¸šēģŧŊžŋÁÁāŁƁĮȁɁʁˁˁ́΁΁Ёҁԁ́ԁՁցׁ؁؁ځہ܁Ũہ߁āáâãäåæįčéęëėíîīđņōķôõö÷øųúûüũū˙      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG€$Ō  i€R_Architectures||ADDITIONAL_SDKSŌ  l€R_Architectures||ARCHSŌ  o€R_Architectures||SDKROOTŌ  r€R_#Architectures||SUPPORTED_PLATFORMSŌ  u€R_Architectures||VALID_ARCHSŌ  x€R_'Assets||ASSET_PACK_MANIFEST_URL_PREFIXŌ  {€R_,Assets||EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLEŌ  ~€R_#Assets||ENABLE_ON_DEMAND_RESOURCESŌ  €R_1Assets||ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGSŌ  „€R_+Assets||ON_DEMAND_RESOURCES_PREFETCH_ORDERŌ  ‡€R_Build Locations||SYMROOTŌ  Š€R_Build Locations||OBJROOTŌ  €R_%Build Locations||SHARED_PRECOMPS_DIRŌ  €R_Build Options||BUILD_VARIANTSŌ  “€R_Build Options||GCC_VERSIONŌ  –€R_/Build Options||EMBEDDED_CONTENT_CONTAINS_SWIFTŌ  ™€R_'Build Options||GENERATE_PROFILING_CODEŌ  œ€R_@Build Options||PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIRŌ  Ÿ€R_.Build Options||APPLICATION_EXTENSION_API_ONLYŌ  ĸ€R_2Build Options||SCAN_ALL_SOURCE_FILES_FOR_INCLUDESŌ  Ĩ€R_ Build Options||VALIDATE_PRODUCTŌ  ¨€R_%Code Signing||CODE_SIGN_ENTITLEMENTSŌ  Ģ€R_!Code Signing||CODE_SIGN_IDENTITYŌ  Ž€R_,Code Signing||CODE_SIGN_RESOURCE_RULES_PATHŌ  ą€R_$Code Signing||OTHER_CODE_SIGN_FLAGSŌ  ´€R_#Code Signing||PROVISIONING_PROFILEŌ  ˇ€R_Deployment||STRIPFLAGSŌ  ē€R_Deployment||ALTERNATE_GROUPŌ  Ŋ€R_Deployment||ALTERNATE_OWNERŌ  Ā€R_Deployment||ALTERNATE_MODEŌ  ÀR_(Deployment||ALTERNATE_PERMISSIONS_FILESŌ  ƀR_!Deployment||COMBINE_HIDPI_IMAGESŌ  ɀR_ Deployment||DEPLOYMENT_LOCATIONŌ  Ė€R_&Deployment||DEPLOYMENT_POSTPROCESSINGŌ  Ī€R_Deployment||INSTALL_GROUPŌ  Ō€R_Deployment||INSTALL_OWNERŌ  ՀR_Deployment||INSTALL_MODE_FLAGŌ  ؀R_Deployment||DSTROOTŌ  ۀR_Deployment||INSTALL_PATHŌ  Ū€R_%Deployment||MACOSX_DEPLOYMENT_TARGETŌ  á€R_%Deployment||PRODUCT_DEFINITION_PLISTŌ  ä€R_-Deployment||RESOURCES_TARGETED_DEVICE_FAMILYŌ  į€R_Deployment||SKIP_INSTALLŌ  ę€R_$Deployment||STRIP_INSTALLED_PRODUCTŌ  í€R_Deployment||STRIP_STYLEŌ  đ€R_Deployment||SEPARATE_STRIPŌ  ķ€R_"Headers||COPY_HEADERS_RUN_UNIFDEFŌ  ö€R_$Headers||COPY_HEADERS_UNIFDEF_FLAGSŌ  ų€R_Kernel Module||MODULE_NAMEŌ  ü€R_Kernel Module||MODULE_STARTŌ  ˙€R_Kernel Module||MODULE_STOPŌ €R_Kernel Module||MODULE_VERSIONŌ €R_Linking||BUNDLE_LOADERŌ €R_%Linking||DYLIB_COMPATIBILITY_VERSIONŌ  €R_Linking||DYLIB_CURRENT_VERSIONŌ €R_Linking||DEAD_CODE_STRIPPINGŌ €R_'Linking||LINKER_DISPLAYS_MANGLED_NAMESŌ €R_,Linking||PRESERVE_DEAD_CODE_INITS_AND_TERMSŌ €R_Linking||LD_DYLIB_INSTALL_NAMEŌ €R_!Linking||DYLIB_INSTALL_NAME_BASEŌ €R_Linking||EXPORTED_SYMBOLS_FILEŌ  €R_Linking||LD_NO_PIEŌ #€R_Linking||INIT_ROUTINEŌ &€R_&Linking||LINK_WITH_STANDARD_LIBRARIESŌ )€R_Linking||MACH_O_TYPEŌ ,€R_Linking||ORDER_FILEŌ /€R_Linking||OTHER_LIBTOOLFLAGSŌ 2€R_Linking||OTHER_LDFLAGSŌ 5€R_Linking||OTHER_TAPI_FLAGSŌ 8€R_%Linking||GENERATE_MASTER_OBJECT_FILEŌ ;€R_Linking||PRELINK_LIBSŌ >€R_Linking||KEEP_PRIVATE_EXTERNSŌ A€R_7Linking||LD_QUOTE_LINKER_ARGUMENTS_FOR_COMPILER_DRIVERŌ D€R_$Linking||REEXPORTED_FRAMEWORK_NAMESŌ G€R_"Linking||REEXPORTED_LIBRARY_NAMESŌ J€R_"Linking||REEXPORTED_LIBRARY_PATHSŌ M€R_!Linking||LD_RUNPATH_SEARCH_PATHSŌ P€R_Linking||SEPARATE_SYMBOL_EDITŌ S€R_Linking||PRELINK_FLAGSŌ V€R_!Linking||SUPPORTS_TEXT_BASED_APIŌ Y€R_Linking||SECTORDER_FLAGSŌ \€R_Linking||TEXT_BASED_API_FILEŌ _€R_!Linking||UNEXPORTED_SYMBOLS_FILEŌ b€R_Linking||WARNING_LDFLAGSŌ e€R_Linking||LD_GENERATE_MAP_FILEŌ h€R_%Packaging||APPLY_RULES_IN_COPY_FILESŌ k€R_.Packaging||CREATE_INFOPLIST_SECTION_IN_BINARYŌ n€R_Packaging||DEFINES_MODULEŌ q€R_ Packaging||EXECUTABLE_EXTENSIONŌ t€R_Packaging||EXECUTABLE_PREFIXŌ w€R_+Packaging||INFOPLIST_EXPAND_BUILD_SETTINGSŌ z€R_!Packaging||GENERATE_PKGINFO_FILEŌ }€R_Packaging||FRAMEWORK_VERSIONŌ €€R_Packaging||INFOPLIST_FILEŌ ƒ€R_.Packaging||INFOPLIST_OTHER_PREPROCESSOR_FLAGSŌ †€R_#Packaging||INFOPLIST_OUTPUT_FORMATŌ ‰€R_.Packaging||INFOPLIST_PREPROCESSOR_DEFINITIONSŌ Œ€R_#Packaging||INFOPLIST_PREFIX_HEADERŌ €R_Packaging||MODULEMAP_FILEŌ ’€R_ Packaging||INFOPLIST_PREPROCESSŌ •€R_&Packaging||COPYING_PRESERVES_HFS_DATAŌ ˜€R_'Packaging||PRIVATE_HEADERS_FOLDER_PATHŌ ›€R_"Packaging||MODULEMAP_PRIVATE_FILEŌ ž€R_%Packaging||PRODUCT_BUNDLE_IDENTIFIERŌ Ą€R_Packaging||PRODUCT_MODULE_NAMEŌ ¤€R_Packaging||PRODUCT_NAMEŌ §€R_$Packaging||PLIST_FILE_OUTPUT_FORMATŌ Ē€R_&Packaging||PUBLIC_HEADERS_FOLDER_PATHŌ ­€R_(Packaging||STRINGS_FILE_OUTPUT_ENCODINGŌ °€R_Packaging||WRAPPER_EXTENSIONŌ ŗ€R_'Search Paths||ALWAYS_SEARCH_USER_PATHSŌ ļ€R_%Search Paths||FRAMEWORK_SEARCH_PATHSŌ š€R_"Search Paths||HEADER_SEARCH_PATHSŌ ŧ€R_#Search Paths||LIBRARY_SEARCH_PATHSŌ ŋ€R_Search Paths||REZ_SEARCH_PATHSŌ €R_<Search Paths||EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ŀR_<Search Paths||INCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIESŌ ȀR_Search Paths||USE_HEADERMAPŌ ˀR_'Search Paths||USER_HEADER_SEARCH_PATHSŌ ΀R_Testing||TEST_HOSTŌ Ņ€R_2Testing||TREAT_MISSING_BASELINES_AS_TEST_FAILURESŌ ԀR_$Versioning||CURRENT_PROJECT_VERSIONŌ ׀R_Versioning||VERSION_INFO_FILEŌ ڀR_%Versioning||VERSION_INFO_EXPORT_DECLŌ Ũ€R_ Versioning||VERSION_INFO_PREFIXŌ ā€R_ Versioning||VERSION_INFO_SUFFIXŌ ã€R_Versioning||VERSIONING_SYSTEMŌ æ€R_!Versioning||VERSION_INFO_BUILDERŌ é€R_/(null) - Deployment||IPHONEOS_DEPLOYMENT_TARGETŌ ė€R_+(null) - Deployment||TVOS_DEPLOYMENT_TARGETŌ ī€R_.(null) - Deployment||WATCHOS_DEPLOYMENT_TARGETŌ ō€R_?Apple LLVM 7.0 - Code Generation||CLANG_DEBUG_INFORMATION_LEVELŌ õ€R_?Apple LLVM 7.0 - Code Generation||CLANG_X86_VECTOR_INSTRUCTIONSŌ ø€R_Apple LLVM 7.0 - Language - Objective C||CLANG_ENABLE_OBJC_ARCŌ ‚€R_4Apple LLVM 7.0 - Preprocessing||ENABLE_NS_ASSERTIONSŌ …€R_:Apple LLVM 7.0 - Preprocessing||ENABLE_STRICT_OBJC_MSGSENDŌ ˆ€R_QApple LLVM 7.0 - Preprocessing||GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPSŌ ‹€R_@Apple LLVM 7.0 - Warning Policies||GCC_WARN_INHIBIT_ALL_WARNINGSŌ Ž€R_4Apple LLVM 7.0 - Warning Policies||GCC_WARN_PEDANTICŌ ‘€R_?Apple LLVM 7.0 - Warning Policies||GCC_TREAT_WARNINGS_AS_ERRORSŌ ”€R_KApple LLVM 7.0 - Warnings - All languages||GCC_WARN_CHECK_SWITCH_STATEMENTSŌ —€R_NApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_DEPRECATED_FUNCTIONSŌ š€R_LApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_DOCUMENTATION_COMMENTSŌ €R_@Apple LLVM 7.0 - Warnings - All languages||CLANG_WARN_EMPTY_BODYŌ  €R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_FOUR_CHARACTER_CONSTANTSŌ Ŗ€R_:Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_SHADOWŌ Ļ€R_EApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_BOOL_CONVERSIONŌ Š€R_IApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_CONSTANT_CONVERSIONŌ Ŧ€R_KApple LLVM 7.0 - Warnings - All languages||GCC_WARN_64_TO_32_BIT_CONVERSIONŌ ¯€R_EApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_ENUM_CONVERSIONŌ ˛€R_DApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_INT_CONVERSIONŌ ĩ€R_NApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_IMPLICIT_SIGN_CONVERSIONŌ ¸€R_TApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSIONŌ ģ€R_SApple LLVM 7.0 - Warnings - All languages||GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETEDŌ ž€R_EApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_RETURN_TYPEŌ Á€R_GApple LLVM 7.0 - Warnings - All languages||GCC_WARN_MISSING_PARENTHESESŌ ĀR_TApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERSŌ Į€R_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_PROTOTYPESŌ ʀR_IApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_MISSING_NEWLINEŌ ̀R_AApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_ASSIGN_ENUMŌ ЀR_LApple LLVM 7.0 - Warnings - All languages||GCC_WARN_ABOUT_POINTER_SIGNEDNESSŌ Ķ€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_SIGN_COMPAREŌ րR_TApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSIONŌ Ų€R_aApple LLVM 7.0 - Warnings - All languages||GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORSŌ ܀R_]Apple LLVM 7.0 - Warnings - All languages||GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORSŌ ߀R_MApple LLVM 7.0 - Warnings - All languages||GCC_WARN_TYPECHECK_CALLS_TO_PRINTFŌ â€R_GApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNINITIALIZED_AUTOSŌ å€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNKNOWN_PRAGMASŌ č€R_FApple LLVM 7.0 - Warnings - All languages||CLANG_WARN_UNREACHABLE_CODEŌ ë€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_FUNCTIONŌ î€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_LABELŌ ņ€R_DApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_PARAMETERŌ ô€R_@Apple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_VALUEŌ ÷€R_CApple LLVM 7.0 - Warnings - All languages||GCC_WARN_UNUSED_VARIABLEŌ ú€R_BApple LLVM 7.0 - Warnings - C++||CLANG_WARN__EXIT_TIME_DESTRUCTORSŌ ũ€R_@Apple LLVM 7.0 - Warnings - C++||GCC_WARN_NON_VIRTUAL_DESTRUCTORŌ €R_BApple LLVM 7.0 - Warnings - C++||GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONSŌ €R_FApple LLVM 7.0 - Warnings - C++||GCC_WARN_ABOUT_INVALID_OFFSETOF_MACROŌ €R_?@ŒŽ€%#@R: \PcmMsrDriverĶ FJ&Ŗ‚ƒŖKLM‘’€%#?đc % ŌR STWNS.dataOabplist00Ô;Troot€#-27EKR[boqsu†Ž™œž ŖĨ§Š°¸ÁČŅØáäæč!$-4<ILNPU]`eruz’§šŧÁ?Á“ŌijVW]NSMutableDataŖVXmVNSDataĶ Za&Ļ\]_`•–ƒ—˜Ļ!bdebb„ŋ™›ŋŋ€%_"IDEActivityReportStringSegmentType_"IDEActivityReportStringSegmentDate_'IDEActivityReportStringSegmentDateStyle_'IDEActivityReportStringSegmentTimeStyleŌn opWNS.time#Aŧ;cø\́šŌijrsVNSDateĸrm_Today at 4:15 PMęŌ w[Ą €€$Ō {[¤ü}ō΁MuŸ€$Ķŧ Ŋ°ŋ„€Ё _nfile:///Users/pjkerly/Downloads/IntelPerformanceCounterMonitor-PCM-V2.10/MacMSRDriver/PcmMsr/PcmMsr-Info.plist_NSKeyedArchiverŅˆ‰UState€"+5:? … ‹ ˜   Ģ ˛ ˇ š ģ Ā Â Å Į î   ! # % ' ) + - / > @ C F H J L N P ‘   Ä ķ  1 L Y l n p r t v x z | ~ ‘ “ • — ™ ›  Ÿ Ą Ŗ Ĩ ž Ņ Ũ O b q   ‚ • ĸ Ĩ § Ē Ŧ Ž Â Ë Đ Ō Ô Ö ã č ę ė ņ ķ õ ÷    " + 6 ? L Q Z g l n p u w y { „  œ Ŗ Ģ ´ Ę Ņ Ū į é ë í ī ø ú ü ū%H\|‰–˜šœž ĸ¯ąŗĩˇšģŊÔ"FUgtwy|~€’Ÿĸ¤§ŠĢ˛ŋÄÆČÍĪŅĶÔâīôöøũ˙  %')+>KNPSUWdefhi€’•—™ĻŠĢް˛ŋÔÖØÚÜŪāâäæčũ˙   :`ŒĨĪū5Nˆ•— ´ģĪÚįđũ˙ 4=OV_loqtvx— §°ŗĩˇĖÕÜáę )>@Q\egikt„˜šœĨ§¨ĩ¸ēŊŋÁÎĪĐŌõ!#%':<>@BDFHJLNi}‘ ¸Ėčũ )6=CLMOXY[degpqs€ƒ…ˆŠŒ•œž ĸ¤ąļ¸ēŋÁÃÅØáîķõ÷üū&+-/468:M¤ąÄÆČĘĖÎĐŌÔÖéëíīņķöųü˙-;R]s–ĒĩÂÅĮĘĖÎö  !(*,.579<>j‚šŖĻ¨Ģ¸ĮÉËÍĪŅĶÕäæčëîņô÷ų-A]{‡ĄŽˇšģŊŋČĘĖÎŅĶū-\Œ™œžĄŖĨšÂĮÉËÍÚßáãčęėî(-/168:<Eajmorƒ—ŖĨ§ŠŦ  "$&-/135<Ch„‘šŸĸ¯ŧžĀÂÄÆČÕ×ÚŨßáäæ (Ou•ēÃ Ī Ņ Ķ Õ × Ų Û Ũ ß á ã å į é ë í ī ņ ķ õ ÷ ų û ũ ˙!!!!! ! ! !!!!!!!!!!!!#!%!'!)!+!-!/!1!3!5!7!9!;!=!?!A!C!E!G!I!K!M!O!Q!S!U!W!Y![!]!`!c!f!i!l!o!r!u!x!{!~!!„!‡!Š!!!“!–!™!œ!Ÿ!ĸ!Ĩ!¨!Ģ!Ž!ą!´!ˇ!ē!Ŋ!Ā!Ã!Æ!É!Ė!Ī!Ō!Õ!Ø!Û!Ū!á!ä!į!ę!í!đ!ķ!ö!ų!ü!˙"""" """"""" "#"&")","/"2"5"8";">"A"D"G"J"M"P"S"V"Y"\"_"b"e"h"k"n"q"t"w"z"}"€"ƒ"†"‰"Œ""’"•"˜"›"ž"Ą"¤"§"Ē"­"°"ŗ"ļ"š"ŧ"ŋ"Â"Å"Č"Ë"Î"Ņ"Ô"×"Ú"Ũ"ā"ã"æ"é"ė"ī"ō"õ"ø"û"ū#### # #######"#%#(#+#.#1#4#7#:#=#@#C#F#I#L#O#R#U#X#[#^#a#d#g#j#m#p#s#v#y#|##‚#…#ˆ#‹#Ž#‘#”#–#Ÿ#Ą#Ã#Ė#Î#æ#ī#ņ$ $$$<$E$G$e$n$p$š$Ŗ$Ĩ$Ô$Ũ$ß%%%%D%M%O%}%†%ˆ%¤%­%¯%Ë%Ô%Ö%ū&& &*&3&5&S&\&^&&™&›&Å&Î&Đ''''O'X'Z''˜'š'Ŋ'Æ'Č'đ'ų'û((((*(Y(b(d(‹(”(–(ŧ(Å(Į(á(ę(ė) )))5)>)@)^)g)i)”))Ÿ)Ã)Ė)Î)ņ)ú)ü*%*.*0*M*V*X*u*~*€*Ą*Ē*Ŧ*Ã*Ė*Î*ę*ķ*õ++&+(+P+Y+[+‹+”+–+˛+ģ+Ŋ+ä+í+ī, ,,,3,<,>,c,l,n,•,ž, ,ž,Į,É,č,ņ,ķ----=-F-H-b-k-m-•-ž- -Â-Ë-Í-í-ö-ø.".+.-.\.e.g.‰.’.”.¸.Á.Ã.å.î.đ////*/3/5/^/g/i//Š/Œ/Ŗ/Ŧ/Ž/Í/Ö/Ø/ō/û/ũ00#0%0M0V0X0q0z0|00Ļ0¨0â0ë0í1111D1M1O1t1}11Ŗ1Ŧ1Ž1Ī1Ø1Ú1ô1ũ1˙2#2,2.2J2S2U2u2~2€2¤2­2¯2Ë2Ô2Ö2÷333*33353f3o3q3Ž3—3™3ŧ3Å3Į3į3đ3ō4 4)4+4O4X4Z4z4ƒ4…4ĸ4Ģ4­4Ū4į4é5555K5T5V5|5…5‡5¤5­5¯5Ō5Û5Ũ6666;6D6F6k6t6v6ž6§6Š6Ë6Ô6Ö6ņ6ú6ü7#7,7.7W7`7b77–7˜7¸7Á7Ã7í7ö7ø8 8)8+8P8Y8[88Š8Œ8Ž8ˇ8š8ø999B9K9M9l9u9w9Ą9Ē9Ŧ9Â9Ë9Í:: : :4:=:?:`:i:k:“:œ:ž:Á:Ę:Ė:ī:ø:ú;;$;&;J;S;U;‡;;’;Ā;É;Ë;ü<<> >>Q>Z>\>Ą>Ē>Ŧ>î>÷>ų?&?/?1?g?p?r?Ģ?´?ļ?ú@@@7@@@B@}@†@ˆ@Į@Đ@ŌAAAAUA^A`A—A AĸAáAęAėB%B.B0BgBpBrBĻB¯BąBæBīBņC C)C+C[CdCfCŖCŦCŽCāCéCëD0D9D;D€D‰D‹DÅDÎDĐD˙EE EDEMEOE†EE‘EžEĮEÉF FFFSF\F^F“FœFžFÛFäFæGG&G(G…GŽGGÕGŪGāHH%H'HeHnHpHļHŋHÁII IIPIYI[I’I›IIÚIãIåJ9JBJDJ‡JJ’JÉJŌJÔKKK!KoKxKzKËKÔKÖL%L.L0LsL|L~LÍLÖLØMMM MhMqMsMŋMČMĘNN!N#NkNtNvNŊNÆNČOO"O$O{O„O†OÜOåOįP/P8P:P„PPPæPīPņQ@QIQKQ—Q QĸQæQīQņR@RIRKRŽR—R™RđRųRûS_ShSjSĘSĶSÕT%T.T0TzTƒT…TËTÔTÖUU(U*UpUyU{UžUĮUÉVVVV^VgViV¯V¸VēV˙WW WMWVWXWWĻW¨WņWúWüX;XDXFX’X›XXëXôXöYLYUYWY­YļY¸ZZZZgZpZrZŧZÅZĮ[[[[`[i[k[Æ[Ī[Ņ\)\2\4\Œ\•\—\í\ö\ø],]5]7]m]v]x]ˇ]Ā]Â^^^^_^h^j^ą^ē^ŧ__ _ _C_L_N_“_œ_ž_æ_ī_ņ`C`L`N`˜`Ą`Ŗ`î`÷`ųaAaJaLa—a aĸaōaûaũbIbRbTb¯b¸bēccccncwcycÉcŌcÔd&d/d1d‚d‹dŽd‘d“dœdždÆdĪdÔdŨdũee$e:eGeHeIeKe`ese‚e•eąe´eˇeēeŊeĀeōeûeūfffff"f%f'f*f1f:fYf^f}f†f“f•f˜fšfŖfšfžfÔfŨgg g8g?gyg†gŽgšgœgŸgĄgĒg°gĩgžgÁgÄgĮgŪgëgôg÷gúgũhh h hhhhh h*h5hMhZhchfhihlhohxhzh|hhhƒhĄhžháhúiiiiiiiii!i5iBiEiGiJiMiOiXi]i`icieiriwiyi{i€iƒi†iˆi‘iši§iŦiŽi°iĩi¸iģiŊiĮiĐiŨiŪißiáiîiņiķiöiųiûjj j jjjj#j%j'j,j/j2j4j>jGjTjYj[j]jbjejhjjj|j…j–jĢjÅjājãjåjčjëkk kk kCkOkQkTkWkZkokrkukxk{k~k‡kŠkkkk kĸkĨkŦkĩkÂkÄkĮkÉkâkåkįkękėkõkøkûkūll1l6l`lill”lēlÔlŨlālâlålōmmm m mmmmmm,m.m1m4m7m:m=m@mCmEm\mym†mmŊmđnnn*n/n2n5n:n=n@nBn|nĻnŗn¸nģnžnÃnÆnÉnËnØnÚnŨnāoJoWo`ocofoiolouoxo{o~ooƒoŽoŨp p<pIpLpOpRpUpWpkptpyp|pppŽp“p–p™pžpĄp¤pĻp´pĮpëpôqqq q qqqqqq#q?qHqKqNqQqbqeqhqkqnqØqáqîqõqøqûqūrrr rrrrrCr_rlrurxr{r~r‹r˜r›ržrĄr¤r§rĒrˇršrŧrŋrÁrÄrĮrÉréss,sRsxssĻsŠsŦsŽsˇsšsásęuöuųuüu˙vvvv vvvvvvv v#v&v)v,v/v2v5v8v;v>vAvDvGvJvMvPvSvVvYv\v_vbvevhvkvnvqvtvwvzv}v€vƒv†v‰vŒvv’v•v˜v›vžvĄv¤v§vĒv­v°vŗvļvšvŧvŋvÂvÅvČvËvÎvŅvÔv×vÚvŨvāvãvævévėvīvōvõvøvûvūwwww w wwwwwww"w%w(w+w.w1w4w7w:w=w@wCwFwIwLwOwRwUwXw[w^wawdwgwjwmwpwswvwyw|ww‚w…wˆw‹wŽw‘w”w—wšww wŖwĻwŠwŦw¯w˛wĩw¸wģwžwÁwÄwĮwĘwÍwĐwĶwÖwŲwÜwßwâwåwčwëwîwņwôw÷wúwũxxxx x xxxxxxx!x$x'x*x-x0x3x6x9x<x?xBxExHxKxNxQxTxWxZx]x`xcxfxixlxoxrxuxxx{x~xx„x‡xŠxxx“x–x™xœxŸxĸxĨx¨xĢxŽxąx´xˇxēxŊxĀxÃxÆxÉxĖxĪxŌxÕxØxÛxŪxáxäxįxęxíxđxķxöxųxüx˙yyy yy1y:y<yTy]y_yyy‚y„yĒyŗyĩyĶyÜyŪzzzzBzKzMzsz|z~z˛zģzŊzëzôzö{{{{9{B{D{l{u{w{˜{Ą{Ŗ{Á{Ę{Ė{ū|| |3|<|>||Š|Œ|Ŋ|Æ|Č|ũ}}}+}4}6}^}g}i}}–}˜}Į}Đ}Ō}ų~~~*~3~5~O~X~Z~y~‚~„~Ŗ~Ŧ~Ž~Ė~Õ~×  1:<_hj“œžģÄÆãė€€1€:€<€X€a€c€‹€”€–€ž€Į€É€ų )+R[]xƒĄĒŦŅÚÜ‚‚ ‚‚,‚5‚7‚V‚_‚a‚‚ˆ‚Ђ̂´‚ļ‚Đ‚Ų‚Ûƒƒ ƒƒ0ƒ9ƒ;ƒ[ƒdƒfƒƒ™ƒ›ƒĘƒĶƒÕƒ÷„„„&„/„1„S„\„^„t„}„„˜„Ą„Ŗ„˄Մׄ„ú…………;…D…F…`…i…k…ˆ…‘…“…ģ…Ä…Æ…ß…č…ę† †††P†Y†[†‚†‹††˛†ģ†Ŋ†â†ë†í‡‡‡‡=‡F‡H‡b‡k‡m‡‘‡š‡œ‡¸‡Á‡Ã‡ã‡ė‡îˆˆˆˆ9ˆBˆDˆeˆnˆpˆ˜ˆĄˆŖˆÔˆŨˆßˆü‰‰‰*‰3‰5‰U‰^‰`‰Ž‰—‰™‰Ŋ‰Æ‰Č‰č‰ņ‰ķŠŠŠŠLŠUŠWŠ}Š†ŠˆŠšŠÂŠÄŠęŠķŠõ‹‹‹‹@‹I‹K‹t‹}‹‹Š‹˛‹´‹Ų‹â‹äŒ ŒŒŒ9ŒBŒDŒ_ŒhŒjŒ‘ŒšŒœŒÅŒÎŒĐŒû&/1[dfŽ—™žĮÉīøúŽŽ%Ž'ŽfŽoŽqŽ°ŽšŽģŽÚŽãŽå09;py{ĸĢ­Î×Ų  /8:]fh‰’”¸ÁÃõū‘‘.‘7‘9‘j‘s‘u‘ˇ‘Ā‘Â’’ ’’N’W’Y’‘’š’œ’ß’č’ę“/“8“:“q“z“|“ŋ“ȓʔ”””\”e”g””””Ÿ”Ք۔╕"•$•h•q•s•Ĩ•Ž•°•ë•ô•ö–5–>–@–u–~–€–Ã–Ė–Î————O—X—Z—“—œ—ž—Õ—Ū—ā˜˜˜˜T˜]˜_˜Ž˜—˜™˜É˜Ō˜Ô™™™™N™W™Y™ž™§™Š™î™÷™ųš3š<š>šmšvšxš˛šģšŊšôšũš˙›,›5›7›w›€›‚›Á›Ę›Ėœœ œ œIœRœTœ‹œ”œ–œķœüœūCLNŠ“•ĶÜŪž$ž-ž/žrž{ž}žžžĮžÉŸŸ Ÿ ŸHŸQŸSŸ§Ÿ°Ÿ˛ŸõŸū  7 @ B „   Ũ æ čĄ9ĄBĄDĄ“ĄœĄžĄáĄęĄėĸ;ĸDĸFĸƒĸŒĸŽĸÖĸßĸáŖ-Ŗ6Ŗ8Ŗ†ŖŖ‘ŖŲŖâŖä¤+¤4¤6¤‡¤¤’¤é¤ō¤ôĨJĨSĨUĨĨĻĨ¨ĨōĨûĨũĻTĻ]Ļ_ĻŽĻˇĻš§§§§T§]§_§Ž§ˇ§š§ü¨¨¨^¨g¨i¨Í¨Ö¨ØŠ8ŠAŠCŠ“ŠœŠžŠčŠņŠķĒ9ĒBĒDĒĒ–Ē˜ĒŪĒįĒéĢ,Ģ5Ģ7Ģ~̇̉ĢĖĢÕĢ×ŦŦ&Ŧ(ŦmŦvŦxŦģŦÄŦÆ­ ­­­_­h­j­Š­˛­´ŽŽ Ž ŽYŽbŽdŽēŽÃŽÅ¯¯$¯&¯t¯}¯¯Õ¯Ū¯ā°*°3°5°}°†°ˆ°Î°×°Ųą4ą=ą?ą—ą ąĸąú˛˛˛[˛d˛f˛š˛Ŗ˛Ĩ˛Û˛ä˛æŗ%ŗ.ŗ0ŗ‚ŗ‹ŗŗÍŗÖŗØ´´(´*´o´x´z´ą´ē´ŧĩĩ ĩ ĩTĩ]ĩ_ĩąĩēĩŧļļļļ\ļeļgļ¯ļ¸ļ玎ˇˇ`ˇiˇkˇˇˇĀˇÂ¸¸&¸(¸~¸‡¸‰¸Ü¸å¸įš7š@šBš”ššŸšđšũšūš˙ēēēēēēēē(ē*ē-ē0ē3ē5ēQēZē]ē`ēcētēwēzē|ēēˆē•ēœēžē ēŖēĒē­ē¯ē˛ē´ēŲēõēūģģģģģ!ģ#ģ%ģ'ģ)ģ+ģ-ģ:ģ=ģ@ģCģEģGģJģLģUŊaŊdŊgŊjŊmŊpŊsŊvŊyŊ|ŊŊ‚Ŋ…ŊˆŊ‹ŊŽŊ‘Ŋ”Ŋ—ŊšŊŊ ŊŖŊĻŊŠŊŦŊ¯Ŋ˛ŊĩŊ¸ŊģŊžŊÁŊÄŊĮŊĘŊÍŊĐŊĶŊÖŊŲŊÜŊßŊâŊåŊčŊëŊîŊņŊôŊ÷ŊúŊũžžžž ž žžžžžžž!ž$ž'ž*ž-ž0ž3ž6ž9ž<ž?žBžEžHžKžNžQžTžWžZž]ž`žcžfžižlžožržužxž{ž~žž„ž‡žŠžžž“ž–ž™žœžŸžĸžĨž¨žĢžŽžąž´žˇžēžŊžĀžÃžÆžÉžĖžĪžŌžÕžØžÛžŪžážäžįžęžížđžķžöžųžüž˙ŋŋŋŋ ŋŋŋŋŋŋŋ ŋ#ŋ&ŋ)ŋ,ŋ/ŋ2ŋ5ŋ8ŋ;ŋ>ŋAŋDŋGŋJŋMŋPŋSŋVŋYŋ\ŋ_ŋbŋeŋhŋkŋnŋqŋtŋwŋzŋ}ŋ€ŋƒŋ†ŋ‰ŋŒŋŋ’ŋ•ŋ˜ŋ›ŋžŋĄŋ¤ŋ§ŋĒŋ­ŋ°ŋŗŋļŋšŋŧŋŋŋÂŋÅŋČŋËŋÎŋŅŋÔŋ×ŋÚŋŨŋāŋãŋæŋéŋėŋīŋōŋõŋøŋûŋūĀĀĀĀ Ā ĀĀĀĀĀĀĀ"Ā%Ā(Ā+Ā.Ā1Ā4Ā7Ā:Ā=Ā@ĀCĀFĀIĀLĀOĀRĀUĀXĀ[Ā^ĀaĀdĀgĀjĀmĀoĀxĀzœĀĨ§ĀŋĀČĀĘĀäĀíĀīÁÁÁ Á>ÁGÁIÁsÁ|Á~Á­ÁļÁ¸ÁŪÁįÁéÂÂ&Â(ÂVÂ_ÂaÂ}†ˆ¤­¯Â×ÂāÂâÃà ÃÃ,Ã5Ã7ÃiÃrÃtÞçÊÃėÃõÃ÷Ä(Ä1Ä3ÄhÄqÄsĖğġÄÉÄŌÄÔÄøÅÅÅ2Å;Å=ÅdÅmÅoઊÅēÅÃÅÅÅäÅíÅīÆÆÆÆ7Æ@ÆBÆmÆvÆxƜÆĨƧÆĘÆĶÆÕÆūĮĮ Į&Į/Į1ĮNĮWĮYĮzĮƒĮ…ĮœĮĨĮ§ĮÃĮĖĮÎĮöĮ˙ČČ)Č2Č4ČdČmČoȋȔȖČŊČÆČČČãČėČîÉ ÉÉÉ<ÉEÉGÉnÉwÉyɗɠÉĸÉÁÉĘÉĖÉęÉķÉõĘĘĘ!Ę;ĘDĘFĘnĘwĘyʛʤĘĻĘÆĘĪĘŅĘûËËË5Ë>Ë@ËbËkËmˑ˚˜˾ËĮËÉËßËčËęĖĖ ĖĖ7Ė@ĖBĖZĖcĖeĖ|˅ˇĖĻĖ¯ĖąĖËĖÔĖÖĖķĖüĖūÍ&Í/Í1ÍJÍSÍUÍvÍ́ÍģÍÄÍÆÍíÍöÍøÎÎ&Î(ÎMÎVÎXÎ|Î…Î‡Î¨ÎąÎŗÎÍÎÖÎØÎüĪĪĪ#Ī,Ī.ĪNĪWĪYĪ}Ī†ĪˆĪ¤Ī­Ī¯ĪĐĪŲĪÛĐĐ ĐĐ?ĐHĐJĐgĐpĐrЕОРĐĀĐÉĐËĐųŅŅŅ(Ņ1Ņ3ŅSŅ\Ņ^Ņ{҄҆ҎŅĀŅÂŅčŅņŅķŌ$Ō-Ō/ŌUŌ^Ō`Ō}Ō†ŌˆŌĢŌ´ŌļŌßŌčŌęĶĶĶĶDĶMĶOĶẁ͂ͤͭ͝ĶĘĶĶĶÕĶüÔÔÔ0Ô9Ô;ÔfÔoÔqÔ‘ÔšÔœÔÆÔĪÔŅÔųÕÕÕ)Õ2Õ4ÕZÕcÕeՇՐՒÕŅÕÚÕÜÖÖ$Ö&ÖEÖNÖPÖzփօ֛֤ÖĻÖÛÖäÖæ× ×××9×B×D×l×u×wךת×Ĩ×Č×Ņ×Ķ×ô×ũ×˙Ø#Ø,Ø.Ø`ØiØkؙØĸؤØÕØŪØāŲ"Ų+Ų-ŲoŲxŲzŲšŲÂŲÄŲüÚÚÚJÚSÚUÚšÚŖÚĨÚÜÚåÚįÛ*Û3Û5ÛzۃۅÛĮÛĐÛŌÛ˙ÜÜ Ü@ÜIÜK܄܍܏ÜĶÜÜÜŪŨŨŨŨVŨ_ŨaŨ ŨŠŨĢŨāŨéŨëŪ.Ū7Ū9ŪpŪyŪ{ŪēŪÃŪÅŪūßß ß@ßIßKß߈ߊßŋßČßĘßųāāā4ā=ā?ā|ā…ā‡āšāÂāÄá áááYábádážá§áŠáØáááãââ&â(â_âhâjâ—â âĸâââëâíã,ã5ã7ãlãuãwã´ãŊãŋãöã˙ää^ägäiäŽäˇäšäõäūåå>åGåIåå˜åšåŨåæåčæ)æ2æ4ækætævæŗæŧæžįįįį`įiįkįĸįĢį­įīįøįúčHčQčSč¤č­č¯čūéé éLéUéWéĻé¯éąéîé÷éųęAęJęLę˜ęĄęŖęņęúęüëDëMëOë–ëŸëĄëōëûëũėTė]ė_ėĩėžėĀíííí]ífíhíŋíČíĘîî"î$îpîyî{îŋîČîĘīī"ī$īgīpīrīÉīŌīÔđ8đAđCđŖđŦđŽđūņņ ņSņ\ņ^ņ¤ņ­ņ¯ņøōōōIōRōTō—ō ōĸōéōōōôķ7ķ@ķBķˆķ‘ķ“ķØķáķãô&ô/ô1ôvôôôĘôĶôÕõõõõkõtõvõÄõÍõĪö%ö.ö0ö†öö‘ößöčöę÷@÷I÷K÷•÷ž÷ ÷č÷ņ÷ķø9øBøDøŸø¨øĒųų ų ųeųnųpųÆųĪųŅúúúúFúOúQúú™ú›úíúöúøû8ûAûCûŠû“û•ûÚûãûåüü%ü'ülüuüwüŋüČüĘũũ%ũ'ũqũzũ|ũĮũĐũŌūū#ū%ūpūyū{ūËūÔūÖ˙"˙+˙-˙ˆ˙‘˙“˙é˙ō˙ôGPRĸĢ­˙ [dgjluwŸŦą´ˇŧŋÂÄŅĶÖŲJWY\_ŊĘŅÔ×Úáäįęė .QZ]`cx†‰ŒŽ—˜šŖĻŠĢŋÖãęíđķúũ&Gjsx{~€‰ŠŒ•˜›˛ÉÖŲÜßâäō˙  #&),/25FIKMPSVY[]gr‹ĸˇĖäũ .56ERUX[^`moru‚‡Š’•˜š §°šÆÍĐĶÖŨāãæč6OXcfilortˆ‹Ž‘˜›žĄŖĖú&/6=JQTWZadgjluw}Š‘”—šĄ¤§ĒŦĩ¸ÅŌŲÜßâéėīōôũ    z } † ” › ĸ ¯ ŧ ŋ Â Å Č Ë Î Û Ū á ä į ę í ī  9 c  – ž § Ē ŗ ē ŋ Ō Ô Ũ ā â ä í ö ų ü ˙       Š œ Ą §Š Špcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/000077500000000000000000000000001475730356400227705ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/000077500000000000000000000000001475730356400264165ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcdebugger/000077500000000000000000000000001475730356400305355ustar00rootroot00000000000000Breakpoints.xcbkptlist000066400000000000000000000075111475730356400350540ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcdebugger pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/000077500000000000000000000000001475730356400304005ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsr.xcscheme000066400000000000000000000037251475730356400333310ustar00rootroot00000000000000 PcmMsrLibrary.xcscheme000066400000000000000000000037241475730356400345760ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes xcschememanagement.plist000066400000000000000000000013711475730356400352340ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes SchemeUserState PcmMsr.xcscheme orderHint 0 PcmMsrLibrary.xcscheme orderHint 1 SuppressBuildableAutocreation 81ADBF11156EEB93006D9B47 primary 81DEAF55157008B7005E8EC6 primary 81F91BBB156D9BF8007DD788 primary pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/000077500000000000000000000000001475730356400267565ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/000077500000000000000000000000001475730356400307405ustar00rootroot00000000000000PcmMsrDriver.xcscheme000066400000000000000000000054561475730356400347710ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes PcmMsrLibrary.xcscheme000066400000000000000000000054531475730356400351370ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes xcschememanagement.plist000066400000000000000000000012451475730356400355740ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes SchemeUserState PcmMsrDriver.xcscheme orderHint 0 PcmMsrLibrary.xcscheme orderHint 1 SuppressBuildableAutocreation 81ADBF11156EEB93006D9B47 primary 81F91BBB156D9BF8007DD788 primary pcm-202502/src/MacMSRDriver/PcmMsr/000077500000000000000000000000001475730356400166315ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr/CMakeLists.txt000066400000000000000000000023411475730356400213710ustar00rootroot00000000000000message(STATUS ${IOKIT_LIBRARY}) add_executable( PcmMsrDriver MACOSX_BUNDLE PcmMsr.cpp PcmMsrClient.cpp PcmMsrDriver_info.c PcmMsr-Info.plist ) set_target_properties(PcmMsrDriver PROPERTIES BUNDLE_EXTENSION kext MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/PcmMsr-Info.plist) target_include_directories(PcmMsrDriver PRIVATE ${CMAKE_OSX_SYSROOT}/System/Library/Frameworks/Kernel.framework/PrivateHeaders ${CMAKE_OSX_SYSROOT}/System/Library/Frameworks/Kernel.framework/Headers ) target_compile_definitions(PcmMsrDriver PRIVATE -DKERNEL -DKERNEL_PRIVATE -DDRIVER_PRIVATE -DAPPLE -DNeXT ) target_compile_options(PcmMsrDriver PRIVATE "-ffreestanding" "$<$:-fapple-kext>" ) target_link_libraries(PcmMsrDriver PRIVATE "-lkmodc++" "-lkmod" "-lcc_kext" "-nostdlib" "-Xlinker -export_dynamic" "-Xlinker -kext" ) # PcmMsrDriver.kext is built here and located in 'build/bin' set(LIB_EXT_PATH "/Library/Extensions") install(TARGETS PcmMsrDriver DESTINATION "${LIB_EXT_PATH}/") install(CODE "execute_process(COMMAND kmutil load -b com.intel.driver.PcmMsr)") pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsr-Info.plist000066400000000000000000000031311475730356400217760ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable PcmMsrDriver CFBundleIdentifier com.intel.driver.PcmMsr CFBundleInfoDictionaryVersion 6.0 CFBundleName PcmMsrDriver CFBundlePackageType KEXT CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 IOKitPersonalities PcmMsrClient CFBundleIdentifier com.intel.driver.PcmMsr IOClass com_intel_driver_PcmMsr IOMatchCategory com_intel_driver_PcmMsr IOProbeScore 1000 IOProviderClass IOResources IOResourceMatch IOKit IOUserClientClass com_intel_driver_PcmMsrClient OSBundleLibraries com.apple.kpi.bsd 10.9 com.apple.kpi.mach 10.9 com.apple.kpi.unsupported 10.9 com.apple.kpi.iokit 10.9 com.apple.kpi.libkern 10.9 pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsr-Prefix.pch000066400000000000000000000001341475730356400217570ustar00rootroot00000000000000// // Prefix header for all source files of the 'PcmMsr' target in the 'PcmMsr' project // pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsr.cpp000066400000000000000000000200761475730356400205430ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #include #include #include "PcmMsr.h" PcmMsrDriverClassName *g_pci_driver = NULL; #define wrmsr(msr,lo,hi) \ asm volatile ("wrmsr" : : "c" (msr), "a" (lo), "d" (hi)) #define rdmsr(msr,lo,hi) \ asm volatile ("\trdmsr\n" : "=a" (lo), "=d" (hi) : "c" (msr)) extern "C" { extern void mp_rendezvous_no_intrs(void (*func)(void *), void *arg); extern int cpu_number(void); } inline uint64_t RDMSR(uint32_t msr) { uint64_t value; uint32_t low, hi; rdmsr(msr, low, hi); value = ((uint64_t) hi << 32) | low; return value; } inline void WRMSR(uint32_t msr, uint64_t value) { uint32_t low, hi; low = (uint32_t)value; hi = (uint32_t) (value >> 32); wrmsr(msr, low, hi); } void cpuReadMSR(void* pIData){ pcm_msr_data_t* data = (pcm_msr_data_t*)pIData; int cpu = cpu_number(); if(data->cpu_num == cpu) { data->value = RDMSR(data->msr_num); } } void cpuWriteMSR(void* pIDatas){ pcm_msr_data_t* idatas = (pcm_msr_data_t*)pIDatas; int cpu = cpu_number(); if(idatas->cpu_num == cpu) { WRMSR(idatas->msr_num, idatas->value); } } void cpuGetTopoData(void* pTopos){ TopologyEntry* entries = (TopologyEntry*)pTopos; const int cpu = cpu_number(); TopologyEntry & entry = entries[cpu]; entry.os_id = cpu; uint32 smtMaskWidth = 0; uint32 coreMaskWidth = 0; uint32 l2CacheMaskShift = 0; initCoreMasks(smtMaskWidth, coreMaskWidth, l2CacheMaskShift); PCM_CPUID_INFO cpuid_args; pcm_cpuid(0xb, 0x0, cpuid_args); fillEntry(entry, smtMaskWidth, coreMaskWidth, l2CacheMaskShift, cpuid_args.array[3]); } OSDefineMetaClassAndStructors(com_intel_driver_PcmMsr, IOService) #define super IOService bool PcmMsrDriverClassName::start(IOService* provider){ bool success; success = super::start(provider); if (!g_pci_driver) { g_pci_driver = this; } if (success) { registerService(); } return success; } int32_t PcmMsrDriverClassName::getNumCores() { int32_t ncpus = 0; size_t ncpus_size = sizeof(ncpus); if(sysctlbyname("hw.logicalcpu", &ncpus, &ncpus_size, NULL, 0)) { IOLog("%s[%p]::%s() -- sysctl failure retrieving hw.logicalcpu", getName(), this, __FUNCTION__); ncpus = 0; } return ncpus; } bool PcmMsrDriverClassName::init(OSDictionary *dict) { bool result = super::init(dict); if (result) { num_cores = getNumCores(); } return result && num_cores; } void PcmMsrDriverClassName::free() { super::free(); } // We override handleOpen, handleIsOpen, and handleClose to allow multiple clients to access the driver // simultaneously. We always return true for these because we don't care who is accessing and we // don't know how many people will be accessing it. bool PcmMsrDriverClassName::handleOpen(IOService * forClient, IOOptionBits opts, void* args){ return true; } bool PcmMsrDriverClassName::handleIsOpen(const IOService* forClient) const{ return true; } void PcmMsrDriverClassName::handleClose(IOService* forClient, IOOptionBits opts){ } IOReturn PcmMsrDriverClassName::readMSR(pcm_msr_data_t* idatas,pcm_msr_data_t* odatas){ // All the msr_nums should be the same, so we just use the first one to pass to all cores IOReturn ret = kIOReturnBadArgument; if(idatas->cpu_num < num_cores) { mp_rendezvous_no_intrs(cpuReadMSR, (void*)idatas); odatas->cpu_num = idatas->cpu_num; odatas->msr_num = idatas->msr_num; odatas->value = idatas->value; ret = kIOReturnSuccess; } else { IOLog("Tried to read from a core with id higher than max core id.\n"); } return ret; } IOReturn PcmMsrDriverClassName::writeMSR(pcm_msr_data_t* idata){ IOReturn ret = kIOReturnBadArgument; if(idata->cpu_num < num_cores) { mp_rendezvous_no_intrs(cpuWriteMSR, (void*)idata); ret = kIOReturnSuccess; } else { IOLog("Tried to write to a core with id higher than max core id.\n"); } return ret; } IOReturn PcmMsrDriverClassName::buildTopology(TopologyEntry* odata, uint32_t input_num_cores) { size_t topologyBufferSize; // TODO figure out when input_num_cores is used rather than num_cores if (os_mul_overflow(sizeof(TopologyEntry), (size_t) num_cores, &topologyBufferSize)) { return kIOReturnBadArgument; } TopologyEntry *topologies = (TopologyEntry *)IOMallocAligned(topologyBufferSize, 32); if (topologies == nullptr) { return kIOReturnNoMemory; } mp_rendezvous_no_intrs(cpuGetTopoData, (void*)topologies); for(uint32_t i = 0; i < num_cores && i < input_num_cores; i++) { odata[i].os_id = topologies[i].os_id; odata[i].thread_id = topologies[i].thread_id; odata[i].core_id = topologies[i].core_id; odata[i].tile_id = topologies[i].tile_id; odata[i].socket_id = topologies[i].socket_id; } IOFreeAligned(topologies, topologyBufferSize); return kIOReturnSuccess; } IOReturn PcmMsrDriverClassName::getNumInstances(uint32_t* num_insts){ *num_insts = num_clients; return kIOReturnSuccess; } IOReturn PcmMsrDriverClassName::incrementNumInstances(uint32_t* num_insts){ *num_insts = ++num_clients; return kIOReturnSuccess; } IOReturn PcmMsrDriverClassName::decrementNumInstances(uint32_t* num_insts){ *num_insts = --num_clients; return kIOReturnSuccess; } // read uint32_t PcmMsrDriverClassName::read(uint32_t pci_address) { uint32_t value = 0; __asm__("\t" "movw $0xCF8,%%dx\n\t" "andb $0xFC,%%al\n\t" "outl %%eax,%%dx\n\t" "movl $0xCFC,%%edx\n\t" "in %%dx,%%eax\n" : "=a"(value) : "a"(pci_address) : "%edx"); return value; } // write void PcmMsrDriverClassName::write(uint32_t pci_address, uint32_t value) { __asm__("\t" "movw $0xCF8,%%dx\n\t" "andb $0xFC,%%al\n\t" "outl %%eax,%%dx\n\t" "movl $0xCFC,%%edx\n\t" "movl %%ebx,%%eax\n\t" "outl %%eax,%%dx\n" : : "a"(pci_address), "b"(value) : "%edx"); } // mapMemory void* PcmMsrDriverClassName::mapMemory (uint32_t address, UInt8 **virtual_address) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); IOMemoryMap *memory_map = NULL; IOMemoryDescriptor *memory_descriptor = NULL; #ifndef __clang_analyzer__ // address a false-positive memory_descriptor = IOMemoryDescriptor::withPhysicalAddress(address, 4096, kIODirectionInOut); #endif if (memory_descriptor) { IOReturn ioErr = memory_descriptor->prepare(kIODirectionInOut); if (ioErr == kIOReturnSuccess) { memory_map = memory_descriptor->map(); if (memory_map) { if (virtual_address) { *virtual_address = (UInt8*)memory_map->getVirtualAddress(); } else { IOLog("%s[%p]::%s() -- virtual_address is null\n", getName(), this, __FUNCTION__); } } else { IOLog("%s[%p]::%s() -- IOMemoryDescriptor::map() failure\n", getName(), this, __FUNCTION__); } } else { IOLog("%s[%p]::%s() -- IOMemoryDescriptor::prepare() failure\n", getName(), this, __FUNCTION__); } if (!memory_map) { memory_descriptor->release(); } } else { IOLog("%s[%p]::%s() -- IOMemoryDescriptor::withPhysicalAddress() failure\n", getName(), this, __FUNCTION__); } return (void*)memory_map; } // unmapMemory void PcmMsrDriverClassName::unmapMemory (void *memory_map) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); IOMemoryMap *m_map = (IOMemoryMap*)memory_map; if (m_map) { m_map->getMemoryDescriptor()->complete(); #ifndef __clang_analyzer__ // address a false-positive m_map->getMemoryDescriptor()->release(); #endif m_map->unmap(); m_map->release(); } return; } pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsr.h000066400000000000000000000031101475730356400201760ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #include #include "UserKernelShared.h" class PcmMsrDriverClassName : public IOService { OSDeclareDefaultStructors(com_intel_driver_PcmMsr) public: // IOService methods virtual bool start(IOService* provider) override; virtual IOReturn writeMSR(pcm_msr_data_t* data); virtual IOReturn readMSR(pcm_msr_data_t* idata,pcm_msr_data_t* odata); virtual IOReturn buildTopology(TopologyEntry* odata, uint32_t input_num_cores); virtual bool init(OSDictionary *dict) override; virtual void free(void) override; virtual bool handleOpen(IOService* forClient, IOOptionBits opts, void* args) override; virtual bool handleIsOpen(const IOService* forClient) const override; virtual void handleClose(IOService* forClient, IOOptionBits opts) override; virtual int32_t getNumCores(); virtual IOReturn incrementNumInstances(uint32_t* num_instances); virtual IOReturn decrementNumInstances(uint32_t* num_instances); virtual IOReturn getNumInstances(uint32_t* num_instances); // PCI classes static uint32_t read(uint32_t pci_address); static void write(uint32_t pci_address, uint32_t value); void* mapMemory(uint32_t address, UInt8 **virtual_address); void unmapMemory(void* memory_map); private: // number of providers currently using the driver uint32_t num_clients = 0; int32_t num_cores; }; #ifdef DEBUG #define _DEBUG 1 #else #define _DEBUG 0 #endif #define PRINT_DEBUG if (_DEBUG) IOLog pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsrClient.cpp000066400000000000000000000262771475730356400217130ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #include #include #include #include "PcmMsrClient.h" #define super IOUserClient OSDefineMetaClassAndStructors(com_intel_driver_PcmMsrClient, IOUserClient) const IOExternalMethodDispatch PcmMsrClientClassName::sMethods[kNumberOfMethods] = { { (IOExternalMethodAction) &PcmMsrClientClassName::sOpenDriver, 0, 0, 0, 0}, { (IOExternalMethodAction) &PcmMsrClientClassName::sCloseDriver, 0, 0, 0, 0}, { (IOExternalMethodAction) &PcmMsrClientClassName::sReadMSR, 0, kIOUCVariableStructureSize, 0, kIOUCVariableStructureSize}, { (IOExternalMethodAction) &PcmMsrClientClassName::sWriteMSR, 0, kIOUCVariableStructureSize, 0, 0}, { (IOExternalMethodAction) &PcmMsrClientClassName::sBuildTopology, 0, 0, 0, kIOUCVariableStructureSize}, { (IOExternalMethodAction) &PcmMsrClientClassName::sGetNumInstances, 0, 0, 1, 0}, { (IOExternalMethodAction) &PcmMsrClientClassName::sIncrementNumInstances, 0, 0, 1, 0}, { (IOExternalMethodAction) &PcmMsrClientClassName::sDecrementNumInstances, 0, 0, 1, 0}, { (IOExternalMethodAction) &PcmMsrClientClassName::sRead, 1, 0, 1, 0 }, { (IOExternalMethodAction) &PcmMsrClientClassName::sWrite, 2, 0, 0, 0 }, { (IOExternalMethodAction) &PcmMsrClientClassName::sMapMemory, 1, 0, 2, 0 }, { (IOExternalMethodAction) &PcmMsrClientClassName::sUnmapMemory, 1, 0, 0, 0 }, { (IOExternalMethodAction) &PcmMsrClientClassName::sReadMemory, 1, 0, 1, 0 } }; IOReturn PcmMsrClientClassName::externalMethod(uint32_t selector, IOExternalMethodArguments* args, IOExternalMethodDispatch* dispatch, OSObject* target, void* reference) { if (selector < (uint32_t) kNumberOfMethods) { dispatch = (IOExternalMethodDispatch *) &sMethods[selector]; if (!target) { target = this; } } return super::externalMethod(selector, args, dispatch, target, reference); } bool PcmMsrClientClassName::initWithTask(task_t owningTask, void *securityToken, UInt32 type, OSDictionary *properties) { if(!IOUserClient::initWithTask(owningTask, securityToken, type, properties)) { return false; } sSecurityToken = securityToken; return true; } bool PcmMsrClientClassName::start(IOService* provider) { bool result = false; if(clientHasPrivilege(sSecurityToken, kIOClientPrivilegeAdministrator) != kIOReturnSuccess) return false; fProvider = OSDynamicCast(PcmMsrDriverClassName, provider); if (fProvider != NULL) { result = super::start(provider); } else IOLog("PcmMsrClientClassName::start failed.\n"); return result; } IOReturn PcmMsrClientClassName::clientClose(void) { closeUserClient(); if (!terminate()) { IOLog("PcmMsrClientClassName::clientClose failed.\n"); } return kIOReturnSuccess; } bool PcmMsrClientClassName::didTerminate(IOService* provider, IOOptionBits options, bool* defer) { closeUserClient(); *defer = false; return super::didTerminate(provider, options, defer); } IOReturn PcmMsrClientClassName::sOpenDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->openUserClient(); } IOReturn PcmMsrClientClassName::openUserClient(void) { IOReturn result = kIOReturnSuccess; if (fProvider == NULL || isInactive()) { result = kIOReturnNotAttached; IOLog("%s::%s returned kIOReturnNotAttached.\n", getName(), __FUNCTION__); } else if (!fProvider->open(this)) { result = kIOReturnExclusiveAccess; IOLog("%s::%s returned kIOReturnExclusiveAccess.\n", getName(), __FUNCTION__); } return result; } IOReturn PcmMsrClientClassName::checkActiveAndOpened (const char* memberFunction) { if (fProvider == NULL || isInactive()) { IOLog("%s::%s returned kIOReturnNotAttached.\n", getName(), memberFunction); return (IOReturn)kIOReturnNotAttached; } else if (!fProvider->isOpen(this)) { IOLog("%s::%s returned kIOReturnNotOpen.\n", getName(), memberFunction); return (IOReturn)kIOReturnNotOpen; } return kIOReturnSuccess; } IOReturn PcmMsrClientClassName::sCloseDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->closeUserClient(); } IOReturn PcmMsrClientClassName::closeUserClient(void) { IOReturn result = checkActiveAndOpened (__FUNCTION__); if (result == kIOReturnSuccess) fProvider->close(this); return result; } IOReturn PcmMsrClientClassName::sReadMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments){ return target->readMSR((pcm_msr_data_t*) arguments->structureInput, (pcm_msr_data_t*) arguments->structureOutput); } IOReturn PcmMsrClientClassName::readMSR(pcm_msr_data_t* idata, pcm_msr_data_t* odata) { IOReturn result = checkActiveAndOpened (__FUNCTION__); if (result == kIOReturnSuccess) result = fProvider->readMSR(idata, odata); return result; } IOReturn PcmMsrClientClassName::sWriteMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments){ return target -> writeMSR((pcm_msr_data_t*)arguments->structureInput); } IOReturn PcmMsrClientClassName::writeMSR(pcm_msr_data_t* data) { IOReturn result = checkActiveAndOpened (__FUNCTION__); if (result == kIOReturnSuccess) result = fProvider->writeMSR(data); return result; } IOReturn PcmMsrClientClassName::sBuildTopology(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ return target -> buildTopology((TopologyEntry*)args->structureOutput, args->structureOutputSize); } IOReturn PcmMsrClientClassName::buildTopology(TopologyEntry* data, size_t output_size) { uint32_t num_cores = (uint32_t) (output_size / sizeof(TopologyEntry) ); IOReturn result = checkActiveAndOpened (__FUNCTION__); if (result == kIOReturnSuccess) result = fProvider->buildTopology(data, num_cores); return result; } IOReturn PcmMsrClientClassName::sGetNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ return target->getNumInstances((uint32_t*)&args->scalarOutput[0]); } IOReturn PcmMsrClientClassName::getNumInstances(uint32_t* num_insts){ return fProvider->getNumInstances(num_insts); } IOReturn PcmMsrClientClassName::sIncrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ return target->incrementNumInstances((uint32_t*)&args->scalarOutput[0]); } IOReturn PcmMsrClientClassName::incrementNumInstances(uint32_t* num_insts){ return fProvider->incrementNumInstances(num_insts); } IOReturn PcmMsrClientClassName::sDecrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ return target->decrementNumInstances((uint32_t*)&args->scalarOutput[0]); } IOReturn PcmMsrClientClassName::decrementNumInstances(uint32_t* num_insts){ return fProvider->decrementNumInstances(num_insts); } extern PcmMsrDriverClassName* g_pci_driver; // read32 IOReturn PcmMsrClientClassName::sRead(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->read(arguments->scalarInput, arguments->scalarInputCount, arguments->scalarOutput, arguments->scalarOutputCount); } IOReturn PcmMsrClientClassName::read(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); if (inputSize != 1) { IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); return kIOReturnBadArgument; } uint32_t addr = (uint32_t)input[0]; PRINT_DEBUG("addr: %x\n", addr); if (g_pci_driver) { output[0] = g_pci_driver->read(addr); } IOLog("val: %llx\n", output[0]); return kIOReturnSuccess; } // write32 IOReturn PcmMsrClientClassName::sWrite(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->write(arguments->scalarInput, arguments->scalarInputCount); } IOReturn PcmMsrClientClassName::write(const uint64_t* input, uint32_t inputSize) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); if (inputSize != 2) { IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); return kIOReturnBadArgument; } uint32_t addr = (uint32_t)input[0]; uint32_t val = (uint32_t)input[1]; PRINT_DEBUG("addr: %x, val: %x\n", addr, val); if (g_pci_driver) { g_pci_driver->write(addr, val); } return kIOReturnSuccess; } // mapMemory IOReturn PcmMsrClientClassName::sMapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->mapMemory(arguments->scalarInput, arguments->scalarInputCount, arguments->scalarOutput, arguments->scalarOutputCount); } IOReturn PcmMsrClientClassName::mapMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); if (inputSize != 1) { IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); return kIOReturnBadArgument; } uint32_t address = (uint32_t)input[0]; PRINT_DEBUG("address: %x\n", address); if (g_pci_driver) { uint8_t* virtual_address = NULL; void* memory_map = g_pci_driver->mapMemory(address, (uint8_t**)&virtual_address); output[0] = (uint64_t)memory_map; output[1] = (uint64_t)virtual_address; PRINT_DEBUG("memory_map: %p\n", memory_map); PRINT_DEBUG("virtual_address: %p\n", virtual_address); } return kIOReturnSuccess; } // unmapMemory IOReturn PcmMsrClientClassName::sUnmapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->unmapMemory(arguments->scalarInput, arguments->scalarInputCount); } IOReturn PcmMsrClientClassName::unmapMemory(const uint64_t* input, uint32_t inputSize) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); if (inputSize != 1) { IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); return kIOReturnBadArgument; } void* memory_map = (void*)input[0]; PRINT_DEBUG("memory_map: %p\n", memory_map); if (g_pci_driver) { g_pci_driver->unmapMemory(memory_map); } return kIOReturnSuccess; } // readMemory IOReturn PcmMsrClientClassName::sReadMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { return target->readMemory(arguments->scalarInput, arguments->scalarInputCount, arguments->scalarOutput, arguments->scalarOutputCount); } IOReturn PcmMsrClientClassName::readMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize) { PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); if (inputSize != 1) { IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); return kIOReturnBadArgument; } uint8_t* address = (uint8_t*)input[0]; PRINT_DEBUG("address: %p\n", address); uint32_t val = 0; if (g_pci_driver) { val = *(uint32_t*)address; } output[0] = (uint64_t)val; PRINT_DEBUG("val: %x\n", val); return kIOReturnSuccess; } pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsrClient.h000066400000000000000000000073061475730356400213500ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #include #include #include "PcmMsr.h" #define PcmMsrClientClassName com_intel_driver_PcmMsrClient class PcmMsrClientClassName : public IOUserClient { OSDeclareDefaultStructors(com_intel_driver_PcmMsrClient) protected: PcmMsrDriverClassName* fProvider; void* sSecurityToken; static const IOExternalMethodDispatch sMethods[kNumberOfMethods]; public: virtual bool initWithTask(task_t owningTask, void *securityToken, UInt32 type, OSDictionary *properties) override; virtual bool start(IOService *provider) override; virtual IOReturn clientClose(void) override; virtual bool didTerminate(IOService* provider, IOOptionBits opts, bool* defer) override; protected: IOReturn checkActiveAndOpened (const char* memberFunction); virtual IOReturn externalMethod(uint32_t selector, IOExternalMethodArguments* arguments, IOExternalMethodDispatch* dispatch, OSObject* target, void* reference) override; static IOReturn sOpenDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn openUserClient(void); static IOReturn sCloseDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn closeUserClient(void); static IOReturn sReadMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn readMSR(pcm_msr_data_t* idata, pcm_msr_data_t* odata); static IOReturn sWriteMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn writeMSR(pcm_msr_data_t* data); static IOReturn sBuildTopology(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn buildTopology(TopologyEntry* data, size_t output_size); static IOReturn sGetNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn getNumInstances(uint32_t* num_insts); static IOReturn sIncrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn incrementNumInstances(uint32_t* num_insts); static IOReturn sDecrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); virtual IOReturn decrementNumInstances(uint32_t* num_insts); // PCI functions static IOReturn sRead(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); virtual IOReturn read(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize); static IOReturn sWrite(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); virtual IOReturn write(const uint64_t* input, uint32_t inputSize); static IOReturn sMapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); virtual IOReturn mapMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize); static IOReturn sUnmapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); virtual IOReturn unmapMemory(const uint64_t* input, uint32_t inputSize); static IOReturn sReadMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); virtual IOReturn readMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize); }; pcm-202502/src/MacMSRDriver/PcmMsr/PcmMsrDriver_info.c000066400000000000000000000006471475730356400223740ustar00rootroot00000000000000#include extern kern_return_t _start(kmod_info_t *ki, void *data); extern kern_return_t _stop(kmod_info_t *ki, void *data); __attribute__((visibility("default"))) KMOD_EXPLICIT_DECL(com.intel.driver.PcmMsrDriver, "1.0.0d1", _start, _stop) __private_extern__ kmod_start_func_t *_realmain = 0; __private_extern__ kmod_stop_func_t *_antimain = 0; __private_extern__ int _kext_apple_cc = __APPLE_CC__ ; pcm-202502/src/MacMSRDriver/PcmMsr/UserKernelShared.h000066400000000000000000000020301475730356400222030ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, Intel Corporation // written by Austen Ott // #define PcmMsrDriverClassName com_intel_driver_PcmMsr #define kPcmMsrDriverClassName "com_intel_driver_PcmMsr" #ifndef USER_KERNEL_SHARED #define USER_KERNEL_SHARED #define PCM_API // kIOMainPortDefault is not supported before macOS Monterey #if (MAC_OS_X_VERSION_MAX_ALLOWED < 120000) #define kIOMainPortDefault kIOMasterPortDefault #endif #include #include "../../topologyentry.h" using namespace pcm; typedef struct { uint64_t value; uint32_t cpu_num; uint32_t msr_num; } pcm_msr_data_t; typedef struct { uint64_t value; uint32_t msr_num; bool mask; char padding[115]; } k_pcm_msr_data_t; enum { kOpenDriver, kCloseDriver, kReadMSR, kWriteMSR, kBuildTopology, kGetNumInstances, kIncrementNumInstances, kDecrementNumInstances, // PCI functions kRead, kWrite, kMapMemory, kUnmapMemory, kReadMemory, kNumberOfMethods }; #endif pcm-202502/src/MacMSRDriver/PcmMsr/en.lproj/000077500000000000000000000000001475730356400203605ustar00rootroot00000000000000pcm-202502/src/MacMSRDriver/PcmMsr/en.lproj/InfoPlist.strings000066400000000000000000000000551475730356400237020ustar00rootroot00000000000000/* Localized versions of Info.plist keys */ pcm-202502/src/MacMSRDriver/kextload.sh000066400000000000000000000002741475730356400176020ustar00rootroot00000000000000#!/usr/bin/env bash cp -R ../../build/bin/PcmMsrDriver.kext /Library/Extensions/. chown -R root:wheel /Library/Extensions/PcmMsrDriver.kext kextload /Library/Extensions/PcmMsrDriver.kext pcm-202502/src/MacMSRDriver/kextunload.sh000066400000000000000000000001631475730356400201420ustar00rootroot00000000000000#!/usr/bin/env bash kextunload /Library/Extensions/PcmMsrDriver.kext rm -rf /Library/Extensions/PcmMsrDriver.kext pcm-202502/src/PMURegisterDeclarations/000077500000000000000000000000001475730356400176715ustar00rootroot00000000000000pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-4F-1.json000066400000000000000000000163621475730356400237320ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "AnyThread": {"Config": 0, "Position": 21, "Width": 1}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 2, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 6, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 10, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "TIDEnable": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "Filter0": {"Config": 1, "Position": 0, "Width": 64, "DefaultValue": 0}, "TID": {"Config": 1, "Position": 0, "Width": 6, "DefaultValue": 0}, "State": {"Config": 1, "Position": 17, "Width": 7, "DefaultValue": 0}, "Filter1": {"Config": 2, "Position": 0, "Width": 64, "DefaultValue": 59}, "OPC": {"Config": 2, "Position": 20, "Width": 9, "DefaultValue": 0}, "NC": {"Config": 2, "Position": 30, "Width": 1, "DefaultValue": 0}, "ISOC": {"Config": 2, "Position": 31, "Width": 1, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "ha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "ubox" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-55-4.json000066400000000000000000000174101475730356400237100ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "AnyThread": {"Config": 0, "Position": 21, "Width": 1}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 2, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 6, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 10, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "TIDEnable": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "Filter0": {"Config": 1, "Position": 0, "Width": 64, "DefaultValue": 0}, "TID": {"Config": 1, "Position": 0, "Width": 9, "DefaultValue": 0}, "Filter1": {"Config": 2, "Position": 0, "Width": 64, "DefaultValue": 59} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "ubox" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 8}, "FCMask": {"Config": 0, "Position": 44, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-6A-0.json000066400000000000000000000210311475730356400237130ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed3" : { "OS": {"Config": 0, "Position": 12, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 13, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 15, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "PerfMetrics": {"Config": 2, "Position": 0, "Width": 1, "DefaultValue": 0, "__comment": "fake field to tell the collector to also print the L1 top-down metrics, not just raw slots count"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "TIDEnable": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 9, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "ubox" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-86-5.json000066400000000000000000000136761475730356400237270ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-8E-C.json000066400000000000000000000077011475730356400237540ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "AnyThread": {"Config": 0, "Position": 21, "Width": 1}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 2, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 6, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "AnyThread": {"Config": 0, "Position": 10, "Width": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-8F-6.json000066400000000000000000000216171475730356400237420ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed3" : { "OS": {"Config": 0, "Position": 12, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 13, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 15, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "PerfMetrics": {"Config": 2, "Position": 0, "Width": 1, "DefaultValue": 0, "__comment": "fake field to tell the collector to also print the L1 top-down metrics, not just raw slots count"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "mdf" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "ubox" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-AD-0.json000066400000000000000000000221301475730356400237320ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed3" : { "OS": {"Config": 0, "Position": 12, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 13, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 15, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "PerfMetrics": {"Config": 2, "Position": 0, "Width": 1, "DefaultValue": 0, "__comment": "fake field to tell the collector to also print the L1 top-down metrics, not just raw slots count"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "mdf" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex8" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex16" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-AE-0.json000066400000000000000000000221301475730356400237330ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed3" : { "OS": {"Config": 0, "Position": 12, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 13, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 15, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "PerfMetrics": {"Config": 2, "Position": 0, "Width": 1, "DefaultValue": 0, "__comment": "fake field to tell the collector to also print the L1 top-down metrics, not just raw slots count"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "mdf" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex8" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex16" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-AF-0.json000066400000000000000000000176341475730356400237510ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "mdf" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex8" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex16" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-B6-0.json000066400000000000000000000176341475730356400237320ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "mdf" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex8" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "pciex16" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/PMURegisterDeclarations/GenuineIntel-6-CF-1.json000066400000000000000000000216171475730356400237500ustar00rootroot00000000000000{ "core" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, "Invert": {"Config": 0, "Position": 23, "Width": 1}, "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, "InTX": {"Config": 0, "Position": 32, "Width": 1, "DefaultValue": 0}, "InTXCheckpointed": {"Config": 0, "Position": 33, "Width": 1, "DefaultValue": 0}, "MSRIndex": { "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} } }, "fixed0" : { "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed1" : { "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed2" : { "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} }, "fixed3" : { "OS": {"Config": 0, "Position": 12, "Width": 1, "DefaultValue": 1}, "User": {"Config": 0, "Position": 13, "Width": 1, "DefaultValue": 1}, "EnablePMI": {"Config": 0, "Position": 15, "Width": 1, "DefaultValue": 0}, "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, "PerfMetrics": {"Config": 2, "Position": 0, "Width": 1, "DefaultValue": 0, "__comment": "fake field to tell the collector to also print the L1 top-down metrics, not just raw slots count"} } }, "cha" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} } }, "imc" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "xpi" : { "__comment" : "this is for UPI LL and QPI LL uncore PMUs", "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} } }, "m2m" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} } }, "m3upi" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "mdf" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "irp" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "ubox" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} } }, "pcu" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} } }, "iio" : { "programmable" : { "EventCode": {"Config": 0, "Position": 0, "Width": 8}, "UMask": {"Config": 0, "Position": 8, "Width": 8}, "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, "PortMask": {"Config": 0, "Position": 36, "Width": 12}, "FCMask": {"Config": 0, "Position": 48, "Width": 3} } } } pcm-202502/src/WinMSRDriver/000077500000000000000000000000001475730356400154655ustar00rootroot00000000000000pcm-202502/src/WinMSRDriver/MSR.inf000066400000000000000000000041031475730356400166220ustar00rootroot00000000000000; ; MSR.inf ; [Version] Signature="$WINDOWS NT$" Class=Sample ; TODO: edit Class ClassGuid={78A1C341-4539-11d3-B88D-00C04FAD5171} ; TODO: edit ClassGuid Provider=%ManufacturerName% CatalogFile=MSR.cat DriverVer= ; TODO: set DriverVer in stampinf property pages [DestinationDirs] DefaultDestDir = 12 MSR_Device_CoInstaller_CopyFiles = 11 ; ================= Class section ===================== [ClassInstall32] Addreg=SampleClassReg [SampleClassReg] HKR,,,0,%ClassName% HKR,,Icon,,-5 [SourceDisksNames] 1 = %DiskName%,,,"" WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 ; make sure the number matches with SourceDisksNames [SourceDisksFiles] MSR.sys = 1,, ;***************************************** ; Install Section ;***************************************** [Manufacturer] %ManufacturerName%=Standard,NT$ARCH$ [Standard.NT$ARCH$] %MSR.DeviceDesc%=MSR_Device, Root\MSR ; TODO: edit hw-id [MSR_Device.NT] CopyFiles=Drivers_Dir [Drivers_Dir] MSR.sys ;-------------- Service installation [MSR_Device.NT.Services] AddService = MSR,%SPSVCINST_ASSOCSERVICE%, MSR_Service_Inst ; -------------- MSR driver install sections [MSR_Service_Inst] DisplayName = %MSR.SVCDESC% ServiceType = 1 ; SERVICE_KERNEL_DRIVER StartType = 3 ; SERVICE_DEMAND_START ErrorControl = 1 ; SERVICE_ERROR_NORMAL ServiceBinary = %12%\MSR.sys ; ;--- MSR_Device Coinstaller installation ------ ; [MSR_Device.NT.CoInstallers] AddReg=MSR_Device_CoInstaller_AddReg CopyFiles=MSR_Device_CoInstaller_CopyFiles [MSR_Device_CoInstaller_AddReg] HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" [MSR_Device_CoInstaller_CopyFiles] WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll [MSR_Device.NT.Wdf] KmdfService = MSR, MSR_wdfsect [MSR_wdfsect] KmdfLibraryVersion = $KMDFVERSION$ [Strings] SPSVCINST_ASSOCSERVICE= 0x00000002 ManufacturerName="" ;TODO: Replace with your manufacturer name ClassName="Samples" ; TODO: edit ClassName DiskName = "MSR Installation Disk" MSR.DeviceDesc = "MSR Device" MSR.SVCDESC = "MSR Service" pcm-202502/src/WinMSRDriver/MSR.vcxproj000066400000000000000000000166531475730356400175560ustar00rootroot00000000000000īģŋ Debug Win32 Release Win32 Debug x64 Release x64 Debug ARM Release ARM Debug ARM64 Release ARM64 {0A94F800-4821-4654-B0A9-53D02F51A81E} {1bc93793-694f-48fe-9372-81e2b05556fd} v4.5 12.0 Debug Win32 MSR Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal DbgengKernelDebugger C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\um;$(IncludePath) DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger SHA256 pcm-202502/src/WinMSRDriver/makefile000066400000000000000000000000421475730356400171610ustar00rootroot00000000000000!INCLUDE $(NTMAKEENV)\makefile.defpcm-202502/src/WinMSRDriver/msr.h000066400000000000000000000004361475730356400164420ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation #ifndef MSR_INCLUDED #define MSR_INCLUDED /* written by Roman Dementiev */ #include #include #include #include #include "msrstruct.h" #endif pcm-202502/src/WinMSRDriver/msrmain.c000066400000000000000000000312011475730356400172740ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* WARNING: This driver code is only for testing purposes, not for production use */ #include "msr.h" #include "ntdef.h" #include /*! \file msrmain.cpp \brief Test Windows 7 Model Specific Driver implementation */ #define NT_DEVICE_NAME L"\\Driver\\RDMSR" #define DOS_DEVICE_NAME L"\\DosDevices\\RDMSR" struct DeviceExtension { HANDLE devMemHandle; HANDLE counterSetHandle; }; DRIVER_INITIALIZE DriverEntry; __drv_dispatchType(IRP_MJ_CREATE) __drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH dummyFunction; __drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH deviceControl; DRIVER_UNLOAD MSRUnload; #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT,DriverEntry) #pragma alloc_text(PAGE,MSRUnload) #pragma alloc_text(PAGE,dummyFunction) #pragma alloc_text(PAGE,deviceControl) #endif NTSTATUS DriverEntry( __in PDRIVER_OBJECT DriverObject, __in PUNICODE_STRING RegistryPath ) { NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING UnicodeString; UNICODE_STRING dosDeviceName; PDEVICE_OBJECT MSRSystemDeviceObject = NULL; struct DeviceExtension * pExt = NULL; UNICODE_STRING devMemPath; OBJECT_ATTRIBUTES attr; UNREFERENCED_PARAMETER(RegistryPath); RtlInitUnicodeString(&UnicodeString, NT_DEVICE_NAME); RtlInitUnicodeString(&dosDeviceName, DOS_DEVICE_NAME); status = IoCreateDevice(DriverObject, sizeof(struct DeviceExtension), &UnicodeString, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &MSRSystemDeviceObject ); if (!NT_SUCCESS(status)) return status; DriverObject->DriverUnload = MSRUnload; DriverObject->MajorFunction[IRP_MJ_CLOSE] = dummyFunction; DriverObject->MajorFunction[IRP_MJ_CREATE] = dummyFunction; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = deviceControl; pExt = DriverObject->DeviceObject->DeviceExtension; RtlInitUnicodeString(&devMemPath, L"\\Device\\PhysicalMemory"); InitializeObjectAttributes(&attr, &devMemPath, OBJ_KERNEL_HANDLE, (HANDLE)NULL, (PSECURITY_DESCRIPTOR)NULL); status = ZwOpenSection(&pExt->devMemHandle, SECTION_MAP_READ | SECTION_MAP_WRITE, &attr); if (!NT_SUCCESS(status)) { DbgPrint("Error: failed ZwOpenSection(devMemHandle) => %08X\n", status); return status; } pExt->counterSetHandle = NULL; IoCreateSymbolicLink(&dosDeviceName, &UnicodeString); return status; } NTSTATUS dummyFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); PAGED_CODE(); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } VOID MSRUnload(PDRIVER_OBJECT DriverObject) { PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject; UNICODE_STRING nameString; PAGED_CODE(); RtlInitUnicodeString(&nameString, DOS_DEVICE_NAME); IoDeleteSymbolicLink(&nameString); if (deviceObject != NULL) { IoDeleteDevice(deviceObject); } } NTSTATUS deviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION IrpStackLocation = NULL; struct MSR_Request * input_msr_req = NULL; struct PCICFG_Request * input_pcicfg_req = NULL; struct MMAP_Request* input_mmap_req = NULL; ULONG64 * output = NULL; GROUP_AFFINITY old_affinity, new_affinity; ULONG inputSize = 0; PCI_SLOT_NUMBER slot; unsigned size = 0; PROCESSOR_NUMBER ProcNumber; struct DeviceExtension* pExt = NULL; LARGE_INTEGER offset; SIZE_T mmapSize = 0; PVOID baseAddress = NULL; pExt = DeviceObject->DeviceExtension; PAGED_CODE(); IrpStackLocation = IoGetCurrentIrpStackLocation(Irp); if (IrpStackLocation) { inputSize = IrpStackLocation->Parameters.DeviceIoControl.InputBufferLength; if (IrpStackLocation->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(ULONG64)) { input_msr_req = (struct MSR_Request *)Irp->AssociatedIrp.SystemBuffer; input_pcicfg_req = (struct PCICFG_Request *)Irp->AssociatedIrp.SystemBuffer; input_mmap_req = (struct MMAP_Request*)Irp->AssociatedIrp.SystemBuffer; output = (ULONG64 *)Irp->AssociatedIrp.SystemBuffer; RtlSecureZeroMemory(&ProcNumber, sizeof(PROCESSOR_NUMBER)); switch (IrpStackLocation->Parameters.DeviceIoControl.IoControlCode) { case IO_CTL_MSR_WRITE: if (inputSize < sizeof(struct MSR_Request)) { status = STATUS_INVALID_PARAMETER; break; } RtlSecureZeroMemory(&new_affinity, sizeof(GROUP_AFFINITY)); RtlSecureZeroMemory(&old_affinity, sizeof(GROUP_AFFINITY)); KeGetProcessorNumberFromIndex(input_msr_req->core_id, &ProcNumber); new_affinity.Group = ProcNumber.Group; new_affinity.Mask = 1ULL << (ProcNumber.Number); KeSetSystemGroupAffinityThread(&new_affinity, &old_affinity); __try { __writemsr(input_msr_req->msr_address, input_msr_req->write_value); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); DbgPrint("Error: exception with code 0x%X in IO_CTL_MSR_WRITE core 0x%X msr 0x%llX value 0x%llX\n", status, input_msr_req->core_id, input_msr_req->msr_address, input_msr_req->write_value); } KeRevertToUserGroupAffinityThread(&old_affinity); Irp->IoStatus.Information = 0; // result size break; case IO_CTL_MSR_READ: if (inputSize < sizeof(struct MSR_Request)) { status = STATUS_INVALID_PARAMETER; break; } RtlSecureZeroMemory(&new_affinity, sizeof(GROUP_AFFINITY)); RtlSecureZeroMemory(&old_affinity, sizeof(GROUP_AFFINITY)); KeGetProcessorNumberFromIndex(input_msr_req->core_id, &ProcNumber); new_affinity.Group = ProcNumber.Group; new_affinity.Mask = 1ULL << (ProcNumber.Number); KeSetSystemGroupAffinityThread(&new_affinity, &old_affinity); __try { *output = __readmsr(input_msr_req->msr_address); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); DbgPrint("Error: exception with code 0x%X in IO_CTL_MSR_READ core 0x%X msr 0x%llX\n", status, input_msr_req->core_id, input_msr_req->msr_address); } KeRevertToUserGroupAffinityThread(&old_affinity); Irp->IoStatus.Information = sizeof(ULONG64); // result size break; case IO_CTL_MMAP_SUPPORT: *output = 1; Irp->IoStatus.Information = sizeof(ULONG64); // result size break; case IO_CTL_MMAP: offset = input_mmap_req->address; mmapSize = input_mmap_req->size; status = ZwMapViewOfSection(pExt->devMemHandle, ZwCurrentProcess(), &baseAddress, 0L, PAGE_SIZE, &offset, &mmapSize, ViewUnmap, 0, PAGE_READWRITE); if (status != STATUS_SUCCESS || baseAddress == NULL) { DbgPrint("Error: ZwMapViewOfSection failed, %lld %lld (%ld).\n", offset.QuadPart, mmapSize, status); } else { *output = (ULONG64)baseAddress; Irp->IoStatus.Information = sizeof(PVOID); // result size } break; case IO_CTL_MUNMAP: status = ZwUnmapViewOfSection(ZwCurrentProcess(), (PVOID) input_mmap_req->address.QuadPart); break; case IO_CTL_PMU_ALLOC_SUPPORT: *output = 1; Irp->IoStatus.Information = sizeof(ULONG64); // result size break; case IO_CTL_PMU_ALLOC: if (pExt->counterSetHandle == NULL) { status = HalAllocateHardwareCounters(NULL, 0, NULL, &(pExt->counterSetHandle)); } *output = status; Irp->IoStatus.Information = sizeof(ULONG64); // result size break; case IO_CTL_PMU_FREE: if (pExt->counterSetHandle != NULL) { status = HalFreeHardwareCounters(pExt->counterSetHandle); if (status == STATUS_SUCCESS) { pExt->counterSetHandle = NULL; } } *output = status; Irp->IoStatus.Information = sizeof(ULONG64); // result size break; case IO_CTL_PCICFG_WRITE: if (inputSize < sizeof(struct PCICFG_Request) || (input_pcicfg_req->bytes != 4 && input_pcicfg_req->bytes != 8)) { status = STATUS_INVALID_PARAMETER; break; } slot.u.AsULONG = 0; slot.u.bits.DeviceNumber = input_pcicfg_req->dev; slot.u.bits.FunctionNumber = input_pcicfg_req->func; #pragma warning(push) #pragma warning(disable: 4996) __try { size = HalSetBusDataByOffset(PCIConfiguration, input_pcicfg_req->bus, slot.u.AsULONG, &(input_pcicfg_req->write_value), input_pcicfg_req->reg, input_pcicfg_req->bytes); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); size = 0; DbgPrint("Error: exception with code 0x%X in IO_CTL_PCICFG_WRITE b 0x%X d 0x%X f 0x%X reg 0x%X bytes 0x%X value 0x%llX\n", status, input_pcicfg_req->bus, input_pcicfg_req->dev, input_pcicfg_req->func, input_pcicfg_req->reg, input_pcicfg_req->bytes, input_pcicfg_req->write_value); } #pragma warning(pop) if (size != input_pcicfg_req->bytes) { status = STATUS_INVALID_PARAMETER; break; } Irp->IoStatus.Information = 0; // result size break; case IO_CTL_PCICFG_READ: if (inputSize < sizeof(struct PCICFG_Request) || (input_pcicfg_req->bytes != 4 && input_pcicfg_req->bytes != 8)) { status = STATUS_INVALID_PARAMETER; break; } slot.u.AsULONG = 0; slot.u.bits.DeviceNumber = input_pcicfg_req->dev; slot.u.bits.FunctionNumber = input_pcicfg_req->func; #pragma warning(push) #pragma warning(disable: 4996) __try { size = HalGetBusDataByOffset(PCIConfiguration, input_pcicfg_req->bus, slot.u.AsULONG, output, input_pcicfg_req->reg, input_pcicfg_req->bytes); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); size = 0; DbgPrint("Error: exception with code 0x%X in IO_CTL_PCICFG_READ b 0x%X d 0x%X f 0x%X reg 0x%X bytes 0x%X\n", status, input_pcicfg_req->bus, input_pcicfg_req->dev, input_pcicfg_req->func, input_pcicfg_req->reg, input_pcicfg_req->bytes); } #pragma warning(pop) if (size != input_pcicfg_req->bytes) { status = STATUS_INVALID_PARAMETER; break; } Irp->IoStatus.Information = size; // result size break; default: status = STATUS_INVALID_DEVICE_REQUEST; } } else status = STATUS_INVALID_PARAMETER; } else status = STATUS_INVALID_DEVICE_REQUEST; Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; } pcm-202502/src/WinMSRDriver/msrstruct.h000066400000000000000000000031611475730356400177050ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* written by Roman Dementiev */ #ifndef MSR_STRUCT_HEADER #define MSR_STRUCT_HEADER #ifndef CTL_CODE #include #endif #define MSR_DEV_TYPE 50000 #define IO_CTL_MSR_READ CTL_CODE(MSR_DEV_TYPE, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_MSR_WRITE CTL_CODE(MSR_DEV_TYPE, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_PCICFG_READ CTL_CODE(MSR_DEV_TYPE, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_PCICFG_WRITE CTL_CODE(MSR_DEV_TYPE, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_MMAP_SUPPORT CTL_CODE(MSR_DEV_TYPE, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_MMAP CTL_CODE(MSR_DEV_TYPE, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_MUNMAP CTL_CODE(MSR_DEV_TYPE, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_PMU_ALLOC_SUPPORT CTL_CODE(MSR_DEV_TYPE, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_PMU_ALLOC CTL_CODE(MSR_DEV_TYPE, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IO_CTL_PMU_FREE CTL_CODE(MSR_DEV_TYPE, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS) struct MSR_Request { int core_id; ULONG64 msr_address; ULONG64 write_value; /* value to write if write requet ignored if read request */ }; struct PCICFG_Request { ULONG bus, dev, func, reg, bytes; // "bytes" can be only 4 or 8 /* value to write if write request ignored if read request */ ULONG64 write_value; }; struct MMAP_Request { LARGE_INTEGER address; SIZE_T size; }; #endif pcm-202502/src/WinMSRDriver/sources000066400000000000000000000003331475730356400170720ustar00rootroot00000000000000TARGETNAME=msr TARGETTYPE=DRIVER NTDDI_VERSION=NTDDI_WIN7 MSC_WARNING_LEVEL=/W3 /WX INCLUDES=\ $(DDK_INC_PATH); TARGETLIBS=\ $(DDK_LIB_PATH)\ntoskrnl.lib SOURCES=msrmain.c PRECOMPILED_INCLUDE=msr.h pcm-202502/src/bw.cpp000066400000000000000000000156631475730356400143210ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev, // Patrick Konsor // #include #include "bw.h" #include "pci.h" #include "utils.h" #include namespace pcm { constexpr auto PCM_CLIENT_IMC_BAR_OFFSET = 0x0048; constexpr auto PCM_TGL_IMC_STEP = 0x10000; unsigned int PCM_TGL_IMC_DRAM_DATA_READS[2] = { 0x5058, 0xd858 }; unsigned int PCM_TGL_IMC_DRAM_DATA_WRITES[2] = { 0x50A0, 0xd8A0 }; unsigned int PCM_TGL_IMC_MMAP_SIZE[2] = { 0x5000 + 0x1000, 0xd000 + 0x1000 }; unsigned int PCM_TGL_IMC_EVENT_BASE[2] = { 0x5000, 0xd000 }; uint64 getClientIMCStartAddr() { PciHandleType imcHandle(0, 0, 0, 0); // memory controller device coordinates: domain 0, bus 0, device 0, function 0 uint64 imcbar = 0; imcHandle.read64(PCM_CLIENT_IMC_BAR_OFFSET, &imcbar); // std::cout << "DEBUG: imcbar=" << std::hex << imcbar << "\n" << std::dec << std::flush; if (!imcbar) { std::cerr << "ERROR: imcbar is zero.\n"; throw std::exception(); } return imcbar & (~(4096ULL - 1ULL)); // round down to 4K } TGLClientBW::TGLClientBW() { const auto startAddr = getClientIMCStartAddr(); for (size_t i = 0; i < mmioRange.size(); ++i) { for (size_t model = 0; model < mmioRange[i].size(); ++model) { mmioRange[i][model] = std::make_shared(startAddr + i * PCM_TGL_IMC_STEP + PCM_TGL_IMC_EVENT_BASE[model], PCM_TGL_IMC_MMAP_SIZE[model] - PCM_TGL_IMC_EVENT_BASE[model]); } } } uint64 TGLClientBW::getImcReads() { uint64 result = 0; for (auto & r : mmioRange) for (size_t model = 0; model < r.size(); ++model) { result += r[model]->read64(PCM_TGL_IMC_DRAM_DATA_READS[model] - PCM_TGL_IMC_EVENT_BASE[model]); } return result; } uint64 TGLClientBW::getImcWrites() { uint64 result = 0; for (auto & r : mmioRange) for (size_t model = 0; model < r.size(); ++model) { result += r[model]->read64(PCM_TGL_IMC_DRAM_DATA_WRITES[model] - PCM_TGL_IMC_EVENT_BASE[model]); } return result; } #define PCM_CLIENT_IMC_DRAM_GT_REQUESTS (0x5040) #define PCM_CLIENT_IMC_DRAM_IA_REQUESTS (0x5044) #define PCM_CLIENT_IMC_DRAM_IO_REQUESTS (0x5048) #define PCM_CLIENT_IMC_DRAM_DATA_READS (0x5050) #define PCM_CLIENT_IMC_DRAM_DATA_WRITES (0x5054) #define PCM_CLIENT_IMC_MMAP_SIZE (0x6000) #define PCM_CLIENT_IMC_EVENT_BASE (0x5000) ClientBW::ClientBW() { mmioRange = std::make_shared(getClientIMCStartAddr() + PCM_CLIENT_IMC_EVENT_BASE, PCM_CLIENT_IMC_MMAP_SIZE - PCM_CLIENT_IMC_EVENT_BASE); } uint64 ClientBW::getImcReads() { return mmioRange->read32(PCM_CLIENT_IMC_DRAM_DATA_READS - PCM_CLIENT_IMC_EVENT_BASE); } uint64 ClientBW::getImcWrites() { return mmioRange->read32(PCM_CLIENT_IMC_DRAM_DATA_WRITES - PCM_CLIENT_IMC_EVENT_BASE); } uint64 ClientBW::getGtRequests() { return mmioRange->read32(PCM_CLIENT_IMC_DRAM_GT_REQUESTS - PCM_CLIENT_IMC_EVENT_BASE); } uint64 ClientBW::getIaRequests() { return mmioRange->read32(PCM_CLIENT_IMC_DRAM_IA_REQUESTS - PCM_CLIENT_IMC_EVENT_BASE); } uint64 ClientBW::getIoRequests() { return mmioRange->read32(PCM_CLIENT_IMC_DRAM_IO_REQUESTS - PCM_CLIENT_IMC_EVENT_BASE); } #define PCM_ADL_IMC_EVENT_BASE (0xd000) #define PCM_ADL_IMC_DRAM_DATA_READS (0x858) #define PCM_ADL_IMC_DRAM_DATA_WRITES (0x8A0) ADLClientBW::ADLClientBW() { mmioRange = std::make_shared(getClientIMCStartAddr() + PCM_ADL_IMC_EVENT_BASE, 0x1000); } uint64 ADLClientBW::getImcReads() { return mmioRange->read32(PCM_ADL_IMC_DRAM_DATA_READS); } uint64 ADLClientBW::getImcWrites() { return mmioRange->read32(PCM_ADL_IMC_DRAM_DATA_WRITES); } #define PCM_SERVER_IMC_DRAM_DATA_READS (0x2290) #define PCM_SERVER_IMC_DRAM_DATA_WRITES (0x2298) #define PCM_SERVER_IMC_PMM_DATA_READS (0x22a0) #define PCM_SERVER_IMC_PMM_DATA_WRITES (0x22a8) #define PCM_SERVER_IMC_MMAP_SIZE (0x4000) std::vector getServerBars(const size_t regBase, const uint32 numIMC, const uint32 root_segment_ubox0, const uint32 root_bus_ubox0) { std::vector result; PciHandleType ubox0Handle(root_segment_ubox0, root_bus_ubox0, SERVER_UBOX0_REGISTER_DEV_ADDR, SERVER_UBOX0_REGISTER_FUNC_ADDR); uint32 mmioBase = 0; ubox0Handle.read32(0xd0, &mmioBase); // std::cout << "mmioBase is 0x" << std::hex << mmioBase << std::dec << std::endl; for (uint32 i = 0; i < numIMC; ++i) { uint32 memOffset = 0; ubox0Handle.read32(regBase + i * 4, &memOffset); // std::cout << "memOffset for imc "< result = getServerBars(0xd4, 1, root_segment_ubox0, root_bus_ubox0); assert(result.size() == 1); return result[0]; } std::vector getServerMemBars(const uint32 numIMC, const uint32 root_segment_ubox0, const uint32 root_bus_ubox0) { return getServerBars(0xd8, numIMC, root_segment_ubox0, root_bus_ubox0); } ServerBW::ServerBW(const uint32 numIMC, const uint32 root_segment_ubox0, const uint32 root_bus_ubox0) { auto memBars = getServerMemBars(numIMC, root_segment_ubox0, root_bus_ubox0); for (auto & memBar: memBars) { mmioRanges.push_back(std::make_shared(memBar, PCM_SERVER_IMC_MMAP_SIZE)); } } uint64 ServerBW::getImcReads() { uint64 result = 0; for (auto & mmio: mmioRanges) { // std::cout << "PCM_SERVER_IMC_DRAM_DATA_READS: " << mmio->read64(PCM_SERVER_IMC_DRAM_DATA_READS) << std::endl; result += mmio->read64(PCM_SERVER_IMC_DRAM_DATA_READS); } return result; } uint64 ServerBW::getImcWrites() { uint64 result = 0; for (auto & mmio : mmioRanges) { result += mmio->read64(PCM_SERVER_IMC_DRAM_DATA_WRITES); } return result; } uint64 ServerBW::getPMMReads() { uint64 result = 0; for (auto & mmio : mmioRanges) { result += mmio->read64(PCM_SERVER_IMC_PMM_DATA_READS); } return result; } uint64 ServerBW::getPMMWrites() { uint64 result = 0; for (auto & mmio : mmioRanges) { result += mmio->read64(PCM_SERVER_IMC_PMM_DATA_WRITES); } return result; } } // namespace pcm pcm-202502/src/bw.h000066400000000000000000000041441475730356400137560ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012-2022, Intel Corporation // written by Roman Dementiev // #pragma once /*! \file bw.h \brief Interfaces to access free-running bandwidth counters */ #include #include #include #include "mmio.h" namespace pcm { class FreeRunningBWCounters { public: virtual uint64 getImcReads() { return 0; } virtual uint64 getImcWrites() { return 0; } virtual uint64 getGtRequests() { return 0; } virtual uint64 getIaRequests() { return 0; } virtual uint64 getIoRequests() { return 0; } virtual uint64 getPMMReads() { return 0; } virtual uint64 getPMMWrites() { return 0; } virtual ~FreeRunningBWCounters() {} }; class TGLClientBW : public FreeRunningBWCounters { std::array, 2>, 2> mmioRange; public: TGLClientBW(); uint64 getImcReads() override; uint64 getImcWrites() override; }; class ADLClientBW : public FreeRunningBWCounters { std::shared_ptr mmioRange; public: ADLClientBW(); uint64 getImcReads() override; uint64 getImcWrites() override; }; class ClientBW : public FreeRunningBWCounters { std::shared_ptr mmioRange; public: ClientBW(); uint64 getImcReads() override; uint64 getImcWrites() override; uint64 getGtRequests() override; uint64 getIaRequests() override; uint64 getIoRequests() override; }; std::vector getServerMemBars(const uint32 numIMC, const uint32 root_segment_ubox0, const uint32 root_bus_ubox0); size_t getServerSCFBar(const uint32 root_segment_ubox0, const uint32 root_bus_ubox0); class ServerBW { std::vector > mmioRanges; ServerBW(); public: ServerBW(const uint32 numIMC, const uint32 root_segment_ubox0, const uint32 root_bus_ubox0); uint64 getImcReads(); uint64 getImcWrites(); uint64 getPMMReads(); uint64 getPMMWrites(); }; } // namespace pcm pcm-202502/src/client/000077500000000000000000000000001475730356400144505ustar00rootroot00000000000000pcm-202502/src/client/.cproject000066400000000000000000000132221475730356400162620ustar00rootroot00000000000000 pcm-202502/src/client/.project000066400000000000000000000044351475730356400161250ustar00rootroot00000000000000 client org.eclipse.cdt.managedbuilder.core.genmakebuilder clean,full,incremental, ?name? org.eclipse.cdt.make.core.append_environment true org.eclipse.cdt.make.core.autoBuildTarget all org.eclipse.cdt.make.core.buildArguments org.eclipse.cdt.make.core.buildCommand make org.eclipse.cdt.make.core.cleanBuildTarget clean org.eclipse.cdt.make.core.contents org.eclipse.cdt.make.core.activeConfigSettings org.eclipse.cdt.make.core.enableAutoBuild false org.eclipse.cdt.make.core.enableCleanBuild true org.eclipse.cdt.make.core.enableFullBuild true org.eclipse.cdt.make.core.fullBuildTarget all org.eclipse.cdt.make.core.stopOnError true org.eclipse.cdt.make.core.useDefaultBuildCmd true org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder full,incremental, org.eclipse.cdt.core.cnature org.eclipse.cdt.core.ccnature org.eclipse.cdt.managedbuilder.core.managedBuildNature org.eclipse.cdt.managedbuilder.core.ScannerConfigNature pcm-202502/src/client/client.cpp000066400000000000000000000063321475730356400164360ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../daemon/common.h" #include "client.h" namespace PCMDaemon { Client::Client() : pollIntervalMs_(0), shmIdLocation_(DEFAULT_SHM_ID_LOCATION), shmAttached_(false), lastUpdatedClientTsc_(0) {} void Client::setSharedMemoryIdLocation(const std::string& location) { if(shmAttached_) { throw std::runtime_error("Shared memory segment already attached. You must call this method before the .connect() method."); } shmIdLocation_ = location; } void Client::setPollInterval(int pollMs) { pollIntervalMs_ = pollMs; } void Client::connect() { setupSharedMemory(); //Set last updated timestamp to avoid a detected change //when the client starts lastUpdatedClientTsc_ = sharedPCMState_->lastUpdateTscEnd; } PCMDaemon::SharedPCMState& Client::read() { if(pollIntervalMs_ <= 0) { throw std::runtime_error("The poll interval is not set or is negative."); } if(!shmAttached_) { throw std::runtime_error("Not attached to shared memory segment. Call .connect() method."); } while(true) { // Check client version matches daemon version if(strlen(sharedPCMState_->version) > 0 && strcmp(sharedPCMState_->version, VERSION) != 0) { std::cout << sharedPCMState_->lastUpdateTscEnd << " " << lastUpdatedClientTsc_ << "\n"; std::stringstream ss; ss << "Out of date PCM daemon client. Client version: " << VERSION << " Daemon version: " << sharedPCMState_->version; throw std::runtime_error(ss.str()); } if(countersHaveUpdated()) { //There is new data lastUpdatedClientTsc_ = sharedPCMState_->lastUpdateTscEnd; return *sharedPCMState_; } else { //Nothing has changed since we last checked usleep(pollIntervalMs_ * 1000); } } } bool Client::countersHaveUpdated() { return lastUpdatedClientTsc_ != sharedPCMState_->lastUpdateTscEnd; } void Client::setupSharedMemory() { int sharedMemoryId; FILE *fp = fopen (shmIdLocation_.c_str(), "r"); if (!fp) { std::cerr << "Failed to open to shared memory key location: " << shmIdLocation_ << "\n"; exit(EXIT_FAILURE); } const int maxCharsToRead = 11; char readBuffer[maxCharsToRead + 1]; std::fill((char*)readBuffer, ((char*)readBuffer) + sizeof(readBuffer), 0); const auto nread = fread(&readBuffer, maxCharsToRead, 1, fp); if (nread == 0 && feof(fp) == 0) { fclose (fp); std::stringstream ss; ss << "fread failed for " << shmIdLocation_; throw std::runtime_error(ss.str()); } fclose (fp); assert(nread <= maxCharsToRead); sharedMemoryId = atoi(readBuffer); sharedPCMState_ = (PCMDaemon::SharedPCMState*)shmat(sharedMemoryId, NULL, 0); if (sharedPCMState_ == (void *)-1) { std::stringstream ss; ss << "Failed to attach shared memory segment (errno=" << errno << ") " << strerror(errno); throw std::runtime_error(ss.str()); } shmAttached_ = true; } } pcm-202502/src/client/client.h000066400000000000000000000013351475730356400161010ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe #include #include #include #ifndef CLIENT_H_ #define CLIENT_H_ #include "../daemon/common.h" namespace PCMDaemon { class Client { public: Client(); void setSharedMemoryIdLocation(const std::string& location); void setPollInterval(int pollMs); void connect(); PCMDaemon::SharedPCMState& read(); bool countersHaveUpdated(); private: void setupSharedMemory(); int pollIntervalMs_; std::string shmIdLocation_; bool shmAttached_; PCMDaemon::SharedPCMState* sharedPCMState_ = nullptr; PCMDaemon::uint64 lastUpdatedClientTsc_; }; } #endif /* CLIENT_H_ */ pcm-202502/src/client/main.cpp000066400000000000000000000373611475730356400161120ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe //Test program for PCM Daemon client #include #include #include #include #include "client.h" void printTitle(std::string title) { std::cout << std::setw(26) << std::left << title; } int main(int argc, char * argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " pollMs\n"; return -1; } try { PCMDaemon::Client client; // client.setSharedMemoryIdLocation("/tmp/test-file"); client.connect(); client.setPollInterval(atoi(argv[1])); int coutPrecision = 2; while (true) { PCMDaemon::SharedPCMState& state = client.read(); PCMDaemon::SharedPCMCounters& counters = state.pcm; std::cout << "\n----- Something changed -----\n\n"; // Display internal metrics printTitle("Last updated TSC"); std::cout << state.lastUpdateTscEnd << "\n"; printTitle("Timestamp"); std::cout << state.timestamp << "\n"; printTitle("Cycles to get counters"); std::cout << state.cyclesToGetPCMState << "\n"; printTitle("Poll interval (ms)"); std::cout << state.pollMs << "\n"; std::cout << "\n\n"; // Display system counters printTitle("Num. of cores"); std::cout << counters.system.numOfCores << "\n"; printTitle("Num. of online cores"); std::cout << counters.system.numOfOnlineCores << "\n"; printTitle("Num. of sockets"); std::cout << counters.system.numOfSockets << "\n"; printTitle("Num. of online sockets"); std::cout << counters.system.numOfOnlineSockets << "\n"; printTitle("QPI/UPI links per socket"); std::cout << counters.system.numOfQPILinksPerSocket << "\n"; std::cout << "\n\n"; // Display core counters printTitle("Core ID"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].coreId << " "; } std::cout << "\n"; printTitle("Socket ID"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].socketId << " "; } std::cout << "\n"; printTitle("IPC"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].instructionsPerCycle << " "; } std::cout << "\n"; printTitle("Cycles"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].cycles << " "; } std::cout << "\n"; printTitle("Inst. Ret."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].instructionsRetired << " "; } std::cout << "\n"; printTitle("Exec usg."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].execUsage << " "; } std::cout << "\n"; printTitle("Rela. Freq."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].relativeFrequency << " "; } std::cout << "\n"; printTitle("Active Rela. Freq"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].activeRelativeFrequency << " "; } std::cout << "\n"; printTitle("L3 C Miss"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l3CacheMisses << " "; } std::cout << "\n"; printTitle("L3 C Reference"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l3CacheReference << " "; } std::cout << "\n"; printTitle("L2 C Miss"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l2CacheMisses << " "; } std::cout << "\n"; printTitle("L3 Hit Ratio"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l3CacheHitRatio << " "; } std::cout << "\n"; printTitle("L2 Hit Ratio"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l2CacheHitRatio << " "; } std::cout << "\n"; printTitle("L3 C MPI"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l3CacheMPI << " "; } std::cout << "\n"; printTitle("L2 C MPI"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l2CacheMPI << " "; } std::cout << "\n"; printTitle("L3 Occu. Avail."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l3CacheOccupancyAvailable << " "; } std::cout << "\n"; printTitle("L3 Occu."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].l3CacheOccupancy << " "; } std::cout << "\n"; printTitle("L. Mem. BW Avail."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].localMemoryBWAvailable << " "; } std::cout << "\n"; printTitle("L. Mem. BW"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].localMemoryBW << " "; } std::cout << "\n"; printTitle("R. Mem. BW Avail."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].remoteMemoryBWAvailable << " "; } std::cout << "\n"; printTitle("R. Mem. BW"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].remoteMemoryBW << " "; } std::cout << "\n"; printTitle("L. Mem. Accesses"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].localMemoryAccesses << " "; } std::cout << "\n"; printTitle("R. Mem. Accesses"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].remoteMemoryAccesses << " "; } std::cout << "\n"; printTitle("Thermal headroom"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineCores; ++i) { std::cout << std::setprecision(coutPrecision) << counters.core.cores[i].thermalHeadroom << " "; } std::cout << "\n"; std::cout << "\n\n"; // Display memory counters printTitle("PMM Metrics Avail."); std::cout << std::setprecision(coutPrecision) << counters.memory.pmmMetricsAvailable << " "; std::cout << "\n"; printTitle("DRAM Read p/Sock."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].read << " "; } std::cout << "\n"; printTitle("DRAM Write p/Sock."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].write << " "; } std::cout << "\n"; if (counters.memory.pmmMetricsAvailable) { printTitle("PMM Read p/Sock."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].pmmRead << " "; } std::cout << "\n"; printTitle("PMM Write p/Sock."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].pmmWrite << " "; } std::cout << "\n"; printTitle("PMM Memory Mode hit rate p/Sock. "); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].memoryModeHitRate << " "; } std::cout << "\n"; } printTitle("Mem Total p/Sock."); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].total << " "; } std::cout << "\n"; printTitle("DRAM Read Sys."); std::cout << std::setprecision(coutPrecision) << counters.memory.system.read << " "; std::cout << "\n"; printTitle("DRAM Write Sys."); std::cout << std::setprecision(coutPrecision) << counters.memory.system.write << " "; std::cout << "\n"; if (counters.memory.pmmMetricsAvailable) { printTitle("PMM Read Sys."); std::cout << std::setprecision(coutPrecision) << counters.memory.system.pmmRead << " "; std::cout << "\n"; printTitle("PMM Write Sys."); std::cout << std::setprecision(coutPrecision) << counters.memory.system.pmmWrite << " "; std::cout << "\n"; } printTitle("Mem Total Sys."); std::cout << std::setprecision(coutPrecision) << counters.memory.system.total << " "; std::cout << "\n"; printTitle("Mem Energy Avail."); std::cout << std::setprecision(coutPrecision) << counters.memory.dramEnergyMetricsAvailable << " "; std::cout << "\n"; if (counters.memory.dramEnergyMetricsAvailable) { printTitle("Mem Energy p/Sock"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.memory.sockets[i].dramEnergy << " "; } std::cout << "\n"; } std::cout << "\n\n"; // Display QPI counters printTitle("QPI/UPI in. Avail."); std::cout << std::setprecision(coutPrecision) << counters.qpi.incomingQPITrafficMetricsAvailable << " "; std::cout << "\n"; if (counters.qpi.incomingQPITrafficMetricsAvailable) { printTitle("QPI/UPI No. of Links"); std::cout << std::setprecision(coutPrecision) << counters.system.numOfQPILinksPerSocket << "\n"; printTitle("QPI/UPI in. p/Sock"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.qpi.incoming[i].total << " "; } std::cout << "\n"; printTitle("QPI/UPI in. p/Link/Sock"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << "Socket: " << i << " (bytes)\t\t"; for (PCMDaemon::uint32 l = 0; l < counters.system.numOfQPILinksPerSocket; ++l) { std::cout << std::setw(12) << std::left << std::setprecision(coutPrecision) << counters.qpi.incoming[i].links[l].bytes << " "; } std::cout << "\n"; printTitle(""); std::cout << "Socket: " << i << " (utilization)\t"; for (PCMDaemon::uint32 l = 0; l < counters.system.numOfQPILinksPerSocket; ++l) { std::cout << std::setw(12) << std::left << std::setprecision(coutPrecision) << counters.qpi.incoming[i].links[l].utilization << " "; } std::cout << "\n"; printTitle(""); } std::cout << "\n"; printTitle("QPI/UPI in. Total"); std::cout << std::setprecision(coutPrecision) << counters.qpi.incomingTotal << " "; std::cout << "\n\n"; } printTitle("QPI/UPI out. Avail."); std::cout << std::setprecision(coutPrecision) << counters.qpi.outgoingQPITrafficMetricsAvailable << " "; std::cout << "\n"; if (counters.qpi.outgoingQPITrafficMetricsAvailable) { printTitle("QPI/UPI No. of Links"); std::cout << std::setprecision(coutPrecision) << counters.system.numOfQPILinksPerSocket << "\n"; printTitle("QPI/UPI out. p/Sock"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << std::setprecision(coutPrecision) << counters.qpi.outgoing[i].total << " "; } std::cout << "\n"; printTitle("QPI/UPI out. p/Link/Sock"); for (PCMDaemon::uint32 i = 0; i < counters.system.numOfOnlineSockets; ++i) { std::cout << "Socket: " << i << " (bytes)\t\t"; for (PCMDaemon::uint32 l = 0; l < counters.system.numOfQPILinksPerSocket; ++l) { std::cout << std::setw(12) << std::left << std::setprecision(coutPrecision) << counters.qpi.outgoing[i].links[l].bytes << " "; } std::cout << "\n"; printTitle(""); std::cout << "Socket: " << i << " (utilization)\t"; for (PCMDaemon::uint32 l = 0; l < counters.system.numOfQPILinksPerSocket; ++l) { std::cout << std::setw(12) << std::left << std::setprecision(coutPrecision) << counters.qpi.outgoing[i].links[l].utilization << " "; } std::cout << "\n"; printTitle(""); } std::cout << "\n"; printTitle("QPI/UPI out. Total"); std::cout << std::setprecision(coutPrecision) << counters.qpi.outgoingTotal << " "; std::cout << "\n"; } std::cout << std::flush; } } catch (const std::runtime_error & e) { std::cerr << "PCM Error in client. Exception " << e.what() << "\n"; return -1; } return 0; } pcm-202502/src/cpuasynchcounter.h000066400000000000000000000144731475730356400167510ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // // asynchronous CPU conters // // contact: Thomas Willhalm #ifndef CPUASYNCHCOUNTER_HEADER #define CPUASYNCHCOUNTER_HEADER /*! \file cpuasynchcounter.h \brief Implementation of a POSIX thread that periodically saves the current state of counters and exposes them to other threads */ #include #include #include "cpucounters.h" #define DELAY 1 // in seconds using namespace pcm; void * UpdateCounters(void *); class AsynchronCounterState { PCM * m; CoreCounterState * cstates1, * cstates2; SocketCounterState * skstates1, * skstates2; SystemCounterState sstate1, sstate2; pthread_t UpdateThread; pthread_mutex_t CounterMutex; friend void * UpdateCounters(void *); AsynchronCounterState(const AsynchronCounterState &) = delete; const AsynchronCounterState & operator = (const AsynchronCounterState &) = delete; public: AsynchronCounterState() { m = PCM::getInstance(); PCM::ErrorCode status = m->program(); if (status != PCM::Success) { std::cerr << "\nCannot access CPU counters. Try to run 'pcm 1' to check the PMU access status.\n\n"; exit(-1); } cstates1 = new CoreCounterState[m->getNumCores()]; cstates2 = new CoreCounterState[m->getNumCores()]; skstates1 = new SocketCounterState[m->getNumSockets()]; skstates2 = new SocketCounterState[m->getNumSockets()]; for (uint32 i = 0; i < m->getNumCores(); ++i) { cstates1[i] = getCoreCounterState(i); cstates2[i] = getCoreCounterState(i); } for (uint32 i = 0; i < m->getNumSockets(); ++i) { skstates1[i] = getSocketCounterState(i); skstates2[i] = getSocketCounterState(i); } pthread_mutex_init(&CounterMutex, NULL); pthread_create(&UpdateThread, NULL, UpdateCounters, this); } ~AsynchronCounterState() { pthread_cancel(UpdateThread); if (pthread_mutex_destroy(&CounterMutex) != 0) std::cerr << "pthread_mutex_destroy failed\n"; try { m->cleanup(); } catch (const std::runtime_error & e) { std::cerr << "PCM Error in ~AsynchronCounterState(). Exception " << e.what() << "\n"; } deleteAndNullifyArray(cstates1); deleteAndNullifyArray(cstates2); deleteAndNullifyArray(skstates1); deleteAndNullifyArray(skstates2); } uint32 getNumCores() { return m->getNumCores(); } uint32 getNumSockets() { return m->getNumSockets(); } uint32 getQPILinksPerSocket() { return m->getQPILinksPerSocket(); } uint32 getSocketId(uint32 c) { return m->getSocketId(c); } const char * getXpi() { return m->xPI(); } template T get(uint32 core) { pthread_mutex_lock(&CounterMutex); T value = func(cstates2[core]); pthread_mutex_unlock(&CounterMutex); return value; } template T get(uint32 core) { pthread_mutex_lock(&CounterMutex); T value = func(cstates1[core], cstates2[core]); pthread_mutex_unlock(&CounterMutex); return value; } template T get(int param, uint32 core) { pthread_mutex_lock(&CounterMutex); T value = func(param, cstates1[core], cstates2[core]); pthread_mutex_unlock(&CounterMutex); return value; } template T getSocket(uint32 socket) { pthread_mutex_lock(&CounterMutex); T value = func(skstates2[socket]); pthread_mutex_unlock(&CounterMutex); return value; } template T getSocket(uint32 socket) { pthread_mutex_lock(&CounterMutex); T value = func(skstates1[socket], skstates2[socket]); pthread_mutex_unlock(&CounterMutex); return value; } template T getSocket(int param, uint32 socket) { pthread_mutex_lock(&CounterMutex); T value = func(param, skstates1[socket], skstates2[socket]); pthread_mutex_unlock(&CounterMutex); return value; } template T getSocket(uint32 socket, uint32 param) { pthread_mutex_lock(&CounterMutex); T value = func(socket, param, sstate1, sstate2); pthread_mutex_unlock(&CounterMutex); return value; } template T getSystem() { pthread_mutex_lock(&CounterMutex); T value = func(sstate1, sstate2); pthread_mutex_unlock(&CounterMutex); return value; } template T getSystem(int param) { pthread_mutex_lock(&CounterMutex); T value = func(param, sstate1, sstate2); pthread_mutex_unlock(&CounterMutex); return value; } }; void * UpdateCounters(void * state) { AsynchronCounterState * s = (AsynchronCounterState *)state; while (true) { if (pthread_mutex_lock(&(s->CounterMutex)) != 0) std::cerr << "pthread_mutex_lock failed\n"; for (uint32 core = 0; core < s->m->getNumCores(); ++core) { s->cstates1[core] = std::move(s->cstates2[core]); s->cstates2[core] = s->m->getCoreCounterState(core); } for (uint32 socket = 0; socket < s->m->getNumSockets(); ++socket) { s->skstates1[socket] = std::move(s->skstates2[socket]); s->skstates2[socket] = s->m->getSocketCounterState(socket); } s->sstate1 = std::move(s->sstate2); s->sstate2 = s->m->getSystemCounterState(); if (pthread_mutex_unlock(&(s->CounterMutex)) != 0) std::cerr << "pthread_mutex_unlock failed\n"; sleep(1); } return NULL; } #endif pcm-202502/src/cpucounters.cpp000066400000000000000000014720721475730356400162650ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2024, Intel Corporation // written by Roman Dementiev // Otto Bruggeman // Thomas Willhalm // Pat Fay // Austen Ott // Jim Harris (FreeBSD) // and many others /*! \file cpucounters.cpp \brief The bulk of PCM implementation */ //#define PCM_TEST_FALLBACK_TO_ATOM #include #include #ifdef PCM_EXPORTS // pcm-lib.h includes cpucounters.h #include "windows\pcm-lib.h" #else #include "cpucounters.h" #endif #include "msr.h" #include "pci.h" #include "types.h" #include "utils.h" #include "topology.h" #if defined (__FreeBSD__) || defined(__DragonFly__) #include #include #include #include #include #include #include #include #endif #ifdef _MSC_VER #include #include #include #include #include "winring0/OlsApiInit.h" #include "windows/windriver.h" #else #include #if defined(__FreeBSD__) || (defined(__DragonFly__) && __DragonFly_version >= 400707) #include #include #include #endif #include #include #ifdef __linux__ #include #include #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #include #include #endif namespace pcm { #ifdef __APPLE__ // convertUnknownToInt is used in the safe sysctl call to convert an unknown size to an int int convertUnknownToInt(size_t size, char* value); #endif #ifdef _MSC_VER void PCM_API restrictDriverAccess(LPCTSTR path) { restrictDriverAccessNative(path); } HMODULE hOpenLibSys = NULL; #ifndef NO_WINRING bool PCM::initWinRing0Lib() { const BOOL result = InitOpenLibSys(&hOpenLibSys); if (result == FALSE) { DeinitOpenLibSys(&hOpenLibSys); hOpenLibSys = NULL; return false; } BYTE major, minor, revision, release; GetDriverVersion(&major, &minor, &revision, &release); TCHAR buffer[128]; _stprintf_s(buffer, 128, TEXT("\\\\.\\WinRing0_%d_%d_%d"),(int)major,(int)minor, (int)revision); restrictDriverAccess(buffer); return true; } #endif // NO_WINRING #endif #if defined(__FreeBSD__) #define cpu_set_t cpuset_t #endif PCM * PCM::instance = NULL; /* static int bitCount(uint64 n) { int count = 0; while (n) { count += static_cast(n & 0x00000001); n >>= static_cast(1); } return count; } */ std::mutex instanceCreationMutex; PCM * PCM::getInstance() { // lock-free read // cppcheck-suppress identicalConditionAfterEarlyExit if (instance) return instance; std::unique_lock _(instanceCreationMutex); // cppcheck-suppress identicalConditionAfterEarlyExit if (instance) return instance; return instance = new PCM(); } uint64 PCM::extractCoreGenCounterValue(uint64 val) { if (canUsePerf) return val; if(core_gen_counter_width) return extract_bits(val, 0, core_gen_counter_width-1); return val; } uint64 PCM::extractCoreFixedCounterValue(uint64 val) { if (canUsePerf) return val; if(core_fixed_counter_width) return extract_bits(val, 0, core_fixed_counter_width-1); return val; } uint64 PCM::extractUncoreGenCounterValue(uint64 val) { if(uncore_gen_counter_width) return extract_bits(val, 0, uncore_gen_counter_width-1); return val; } uint64 PCM::extractUncoreFixedCounterValue(uint64 val) { if(uncore_fixed_counter_width) return extract_bits(val, 0, uncore_fixed_counter_width-1); return val; } uint64 PCM::extractQOSMonitoring(uint64 val) { //Check if any of the error bit(63) or Unavailable bit(62) of the IA32_QM_CTR MSR are 1 if(val & (3ULL<<62)) { // invalid reading return static_cast(PCM_INVALID_QOS_MONITORING_DATA); } // valid reading return extract_bits(val,0,61); } int32 extractThermalHeadroom(uint64 val) { if(val & (1ULL<<31ULL)) { // valid reading return static_cast(extract_bits(val, 16, 22)); } // invalid reading return static_cast(PCM_INVALID_THERMAL_HEADROOM); } uint64 get_frequency_from_cpuid(); #if defined(__FreeBSD__) || defined(__DragonFly__) void pcm_cpuid_bsd(int leaf, PCM_CPUID_INFO& info, int core) { cpuctl_cpuid_args_t cpuid_args_freebsd; char cpuctl_name[64]; snprintf(cpuctl_name, 64, "/dev/cpuctl%d", core); auto fd = ::open(cpuctl_name, O_RDWR); cpuid_args_freebsd.level = leaf; ::ioctl(fd, CPUCTL_CPUID, &cpuid_args_freebsd); for (int i = 0; i < 4; ++i) { info.array[i] = cpuid_args_freebsd.data[i]; } ::close(fd); } #endif #ifdef __linux__ bool isNMIWatchdogEnabled(const bool silent); bool keepNMIWatchdogEnabled(); #endif void PCM::readCoreCounterConfig(const bool complainAboutMSR) { if (max_cpuid >= 0xa) { // get counter related info PCM_CPUID_INFO cpuinfo; pcm_cpuid(0xa, cpuinfo); perfmon_version = extract_bits_32(cpuinfo.array[0], 0, 7); core_gen_counter_num_max = extract_bits_32(cpuinfo.array[0], 8, 15); core_gen_counter_width = extract_bits_32(cpuinfo.array[0], 16, 23); if (perfmon_version > 1) { core_fixed_counter_num_max = extract_bits_32(cpuinfo.array[3], 0, 4); core_fixed_counter_width = extract_bits_32(cpuinfo.array[3], 5, 12); } else if (1 == perfmon_version) { core_fixed_counter_num_max = 3; core_fixed_counter_width = core_gen_counter_width; } if (isForceRTMAbortModeAvailable()) { uint64 TSXForceAbort = 0; if (MSR.empty()) { if (complainAboutMSR) { std::cerr << "PCM Error: Can't determine the number of available counters reliably because of no access to MSR.\n"; } } else if (MSR[0]->read(MSR_TSX_FORCE_ABORT, &TSXForceAbort) == sizeof(uint64)) { TSXForceAbort &= 1; /* TSXForceAbort is 0 (default mode) => the number of useful gen counters is 3 TSXForceAbort is 1 => the number of gen counters is unchanged */ if (TSXForceAbort == 0) { core_gen_counter_num_max = 3; } } else { std::cerr << "PCM Error: Can't determine the number of available counters reliably because reading MSR_TSX_FORCE_ABORT failed.\n"; } } #if defined(__linux__) const auto env = std::getenv("PCM_NO_AWS_WORKAROUND"); auto aws_workaround = true; if (env != nullptr && std::string(env) == std::string("1")) { aws_workaround = false; } if (aws_workaround == true && vm == true && linux_arch_perfmon == true && core_gen_counter_num_max > 3) { core_gen_counter_num_max = 3; std::cerr << "INFO: Reducing the number of programmable counters to 3 to workaround the fixed cycle counter virtualization issue on AWS.\n"; std::cerr << " You can disable the workaround by setting PCM_NO_AWS_WORKAROUND=1 environment variable\n"; } if (isNMIWatchdogEnabled(true) && keepNMIWatchdogEnabled()) { --core_gen_counter_num_max; std::cerr << "INFO: Reducing the number of programmable counters to " << core_gen_counter_num_max << " because NMI watchdog is enabled.\n"; } #endif } } bool PCM::isFixedCounterSupported(unsigned c) { if (max_cpuid >= 0xa) { PCM_CPUID_INFO cpuinfo; pcm_cpuid(0xa, cpuinfo); return extract_bits_32(cpuinfo.reg.ecx, c, c) || (extract_bits_32(cpuinfo.reg.edx, 4, 0) > c); } return false; } bool PCM::isHWTMAL1Supported() const { #ifdef PCM_USE_PERF if (perfEventTaskHandle.empty() == false) { return false; // per PID/task perf collection does not support HW TMA L1 } #endif static int supported = -1; if (supported < 0) { supported = 0; PCM_CPUID_INFO cpuinfo; pcm_cpuid(1, cpuinfo); if (extract_bits_32(cpuinfo.reg.ecx, 15, 15) && MSR.size()) { uint64 perf_cap; if (MSR[0]->read(MSR_PERF_CAPABILITIES, &perf_cap) == sizeof(uint64)) { supported = (int)extract_bits(perf_cap, 15, 15); } } if (hybrid) { supported = 0; } } return supported > 0; } void PCM::readCPUMicrocodeLevel() { if (MSR.empty()) return; const int ref_core = 0; TemporalThreadAffinity affinity(ref_core); if (affinity.supported() && isCoreOnline(ref_core)) { // see "Update Signature and Verification" and "Determining the Signature" // sections in Intel SDM how to read ucode level if (MSR[ref_core]->write(MSR_IA32_BIOS_SIGN_ID, 0) == sizeof(uint64)) { PCM_CPUID_INFO cpuinfo; pcm_cpuid(1, cpuinfo); // cpuid instructions updates MSR_IA32_BIOS_SIGN_ID uint64 result = 0; if (MSR[ref_core]->read(MSR_IA32_BIOS_SIGN_ID, &result) == sizeof(uint64)) { cpu_microcode_level = result >> 32; } } } } int32 PCM::getMaxCustomCoreEvents() { return core_gen_counter_num_max; } /* int PCM::getCPUModelFromCPUID() { static int result = -1; if (result < 0) { PCM_CPUID_INFO cpuinfo; pcm_cpuid(1, cpuinfo); result = (((cpuinfo.array[0]) & 0xf0) >> 4) | ((cpuinfo.array[0] & 0xf0000) >> 12); } return result; } */ int PCM::getCPUFamilyModelFromCPUID() { static int result = -1; if (result < 0) { PCM_CPUID_INFO cpuinfo; pcm_cpuid(1, cpuinfo); const auto cpu_family_ = (((cpuinfo.array[0]) >> 8) & 0xf) | ((cpuinfo.array[0] & 0xf00000) >> 16); const auto cpu_model_ = (((cpuinfo.array[0]) & 0xf0) >> 4) | ((cpuinfo.array[0] & 0xf0000) >> 12); result = PCM_CPU_FAMILY_MODEL(cpu_family_, cpu_model_); } return result; } bool PCM::detectModel() { char buffer[1024]; union { char cbuf[16]; int ibuf[16 / sizeof(int)]; } buf; PCM_CPUID_INFO cpuinfo; pcm_cpuid(0, cpuinfo); std::fill(buffer, buffer + 1024, 0); std::fill(buf.cbuf, buf.cbuf + 16, 0); buf.ibuf[0] = cpuinfo.array[1]; buf.ibuf[1] = cpuinfo.array[3]; buf.ibuf[2] = cpuinfo.array[2]; if (strncmp(buf.cbuf, "GenuineIntel", 4 * 3) != 0) { std::cerr << getUnsupportedMessage() << "\n"; return false; } max_cpuid = cpuinfo.array[0]; pcm_cpuid(1, cpuinfo); cpu_family = (((cpuinfo.array[0]) >> 8) & 0xf) | ((cpuinfo.array[0] & 0xf00000) >> 16); cpu_model_private = (((cpuinfo.array[0]) & 0xf0) >> 4) | ((cpuinfo.array[0] & 0xf0000) >> 12); cpu_family_model = PCM_CPU_FAMILY_MODEL(cpu_family, cpu_model_private); cpu_stepping = cpuinfo.array[0] & 0x0f; if (cpuinfo.reg.ecx & (1UL << 31UL)) { vm = true; std::cerr << "Detected a hypervisor/virtualization technology. Some metrics might not be available due to configuration or availability of virtual hardware features.\n"; } readCoreCounterConfig(); pcm_cpuid(7, 0, cpuinfo); std::cerr << "\n===== Processor information =====\n"; #ifdef __linux__ auto checkLinuxCpuinfoFlag = [](const std::string& flag) -> bool { std::ifstream linuxCpuinfo("/proc/cpuinfo"); if (linuxCpuinfo.is_open()) { std::string line; while (std::getline(linuxCpuinfo, line)) { auto tokens = split(line, ':'); if (tokens.size() >= 2 && tokens[0].find("flags") == 0) { for (const auto & curFlag : split(tokens[1], ' ')) { if (flag == curFlag) { return true; } } } } linuxCpuinfo.close(); } return false; }; linux_arch_perfmon = checkLinuxCpuinfoFlag("arch_perfmon"); std::cerr << "Linux arch_perfmon flag : " << (linux_arch_perfmon ? "yes" : "no") << "\n"; if (vm == true && linux_arch_perfmon == false) { std::cerr << "ERROR: vPMU is not enabled in the hypervisor. Please see details in https://software.intel.com/content/www/us/en/develop/documentation/vtune-help/top/set-up-analysis-target/on-virtual-machine.html \n"; std::cerr << " you can force-continue by setting PCM_IGNORE_ARCH_PERFMON=1 environment variable.\n"; auto env = std::getenv("PCM_IGNORE_ARCH_PERFMON"); auto ignore_arch_perfmon = false; if (env != nullptr && std::string(env) == std::string("1")) { ignore_arch_perfmon = true; } if (!ignore_arch_perfmon) { return false; } } #endif hybrid = (cpuinfo.reg.edx & (1 << 15)) ? true : false; std::cerr << "Hybrid processor : " << (hybrid ? "yes" : "no") << "\n"; std::cerr << "IBRS and IBPB supported : " << ((cpuinfo.reg.edx & (1 << 26)) ? "yes" : "no") << "\n"; std::cerr << "STIBP supported : " << ((cpuinfo.reg.edx & (1 << 27)) ? "yes" : "no") << "\n"; std::cerr << "Spec arch caps supported : " << ((cpuinfo.reg.edx & (1 << 29)) ? "yes" : "no") << "\n"; std::cerr << "Max CPUID level : " << max_cpuid << "\n"; std::cerr << "CPU family : " << cpu_family << "\n"; std::cerr << "CPU model number : " << cpu_model_private << "\n"; return true; } bool PCM::isRDTDisabled() const { static int flag = -1; if (flag < 0) { // flag not yet initialized const char * varname = "PCM_NO_RDT"; char* env = nullptr; #ifdef _MSC_VER _dupenv_s(&env, NULL, varname); #else env = std::getenv(varname); #endif if (env != nullptr && std::string(env) == std::string("1")) { std::cout << "Disabling RDT usage because PCM_NO_RDT=1 environment variable is set.\n"; flag = 1; } else { flag = 0; } #ifdef _MSC_VER freeAndNullify(env); #endif } return flag > 0; } bool PCM::QOSMetricAvailable() const { if (isRDTDisabled()) return false; #ifndef __linux__ if (isSecureBoot()) return false; #endif PCM_CPUID_INFO cpuinfo; pcm_cpuid(0x7,0,cpuinfo); return (cpuinfo.reg.ebx & (1<<12))?true:false; } bool PCM::L3QOSMetricAvailable() const { if (isRDTDisabled()) return false; #ifndef __linux__ if (isSecureBoot()) return false; #endif PCM_CPUID_INFO cpuinfo; pcm_cpuid(0xf,0,cpuinfo); return (cpuinfo.reg.edx & (1<<1))?true:false; } bool PCM::L3CacheOccupancyMetricAvailable() const { PCM_CPUID_INFO cpuinfo; if (!(QOSMetricAvailable() && L3QOSMetricAvailable())) return false; pcm_cpuid(0xf,0x1,cpuinfo); return (cpuinfo.reg.edx & 1)?true:false; } bool isMBMEnforced() { static int flag = -1; if (flag < 0) { // flag not yet initialized flag = pcm::safe_getenv("PCM_ENFORCE_MBM") == std::string("1") ? 1 : 0; } return flag > 0; } bool PCM::CoreLocalMemoryBWMetricAvailable() const { if (isMBMEnforced() == false && cpu_family_model == SKX && cpu_stepping < 5) return false; // SKZ4 errata PCM_CPUID_INFO cpuinfo; if (!(QOSMetricAvailable() && L3QOSMetricAvailable())) return false; pcm_cpuid(0xf,0x1,cpuinfo); return (cpuinfo.reg.edx & 2)?true:false; } bool PCM::CoreRemoteMemoryBWMetricAvailable() const { if (isMBMEnforced() == false && cpu_family_model == SKX && cpu_stepping < 5) return false; // SKZ4 errata PCM_CPUID_INFO cpuinfo; if (!(QOSMetricAvailable() && L3QOSMetricAvailable())) return false; pcm_cpuid(0xf, 0x1, cpuinfo); return (cpuinfo.reg.edx & 4) ? true : false; } unsigned PCM::getMaxRMID() const { unsigned maxRMID = 0; PCM_CPUID_INFO cpuinfo; pcm_cpuid(0xf,0,cpuinfo); maxRMID = (unsigned)cpuinfo.reg.ebx + 1; return maxRMID; } void PCM::initRDT() { if (!(QOSMetricAvailable() && L3QOSMetricAvailable())) return; #ifdef __linux__ auto env = std::getenv("PCM_USE_RESCTRL"); if (env != nullptr && std::string(env) == std::string("1")) { std::cerr << "INFO: using Linux resctrl driver for RDT metrics (L3OCC, LMB, RMB) because environment variable PCM_USE_RESCTRL=1\n"; resctrl.init(); useResctrl = true; return; } if (resctrl.isMounted()) { std::cerr << "INFO: using Linux resctrl driver for RDT metrics (L3OCC, LMB, RMB) because resctrl driver is mounted.\n"; resctrl.init(); useResctrl = true; return; } if (isSecureBoot()) { std::cerr << "INFO: using Linux resctrl driver for RDT metrics (L3OCC, LMB, RMB) because Secure Boot mode is enabled.\n"; resctrl.init(); useResctrl = true; return; } #endif std::cerr << "Initializing RMIDs" << std::endl; unsigned maxRMID; /* Calculate maximum number of RMID supported by socket */ maxRMID = getMaxRMID(); DBG(2, "Maximum RMIDs per socket in the system : " , maxRMID ); std::vector rmid(num_sockets); for(int32 i = 0; i < num_sockets; i ++) rmid[i] = maxRMID - 1; /* Associate each core with 1 RMID */ for(int32 core = 0; core < num_cores; core ++ ) { if(!isCoreOnline(core)) continue; uint64 msr_pqr_assoc = 0 ; uint64 msr_qm_evtsel = 0 ; MSR[core]->lock(); //Read 0xC8F MSR for each core MSR[core]->read(IA32_PQR_ASSOC, &msr_pqr_assoc); DBG(3, "initRMID reading IA32_PQR_ASSOC 0x" , std::hex , msr_pqr_assoc , std::dec); DBG(3, "Socket Id : " , topology[core].socket_id); msr_pqr_assoc &= 0xffffffff00000000ULL; msr_pqr_assoc |= (uint64)(rmid[topology[core].socket_id] & ((1ULL<<10)-1ULL)); DBG(3, "initRMID writing IA32_PQR_ASSOC 0x" , std::hex , msr_pqr_assoc , std::dec); //Write 0xC8F MSR with new RMID for each core MSR[core]->write(IA32_PQR_ASSOC,msr_pqr_assoc); msr_qm_evtsel = static_cast(rmid[topology[core].socket_id] & ((1ULL<<10)-1ULL)); msr_qm_evtsel <<= 32; //Write 0xC8D MSR with new RMID for each core DBG(3, "initRMID writing IA32_QM_EVTSEL 0x" , std::hex , msr_qm_evtsel , std::dec); MSR[core]->write(IA32_QM_EVTSEL,msr_qm_evtsel); MSR[core]->unlock(); /* Initializing the memory bandwidth counters */ if (CoreLocalMemoryBWMetricAvailable()) { memory_bw_local.push_back(std::make_shared(new CounterWidthExtender::MBLCounter(MSR[core]), 24, 1000)); if (CoreRemoteMemoryBWMetricAvailable()) { memory_bw_total.push_back(std::make_shared(new CounterWidthExtender::MBTCounter(MSR[core]), 24, 1000)); } } rmid[topology[core].socket_id] --; } /* Get The scaling factor by running CPUID.0xF.0x1 instruction */ L3ScalingFactor = getL3ScalingFactor(); } void PCM::initQOSevent(const uint64 event, const int32 core) { if(!isCoreOnline(core)) return; uint64 msr_qm_evtsel = 0 ; //Write 0xC8D MSR with the event id MSR[core]->read(IA32_QM_EVTSEL, &msr_qm_evtsel); DBG(3, "initQOSevent reading IA32_QM_EVTSEL 0x" , std::hex , msr_qm_evtsel , std::dec); msr_qm_evtsel &= 0xfffffffffffffff0ULL; msr_qm_evtsel |= event & ((1ULL<<8)-1ULL); DBG(3, "initQOSevent writing IA32_QM_EVTSEL 0x" , std::hex , msr_qm_evtsel , std::dec); MSR[core]->write(IA32_QM_EVTSEL,msr_qm_evtsel); } void PCM::initCStateSupportTables() { #define PCM_PARAM_PROTECT(...) __VA_ARGS__ #define PCM_CSTATE_ARRAY(array_ , val ) \ { \ static uint64 tmp[] = val; \ PCM_COMPILE_ASSERT(sizeof(tmp) / sizeof(uint64) == (static_cast(MAX_C_STATE)+1)); \ array_ = tmp; \ break; \ } // fill package C state array switch(cpu_family_model) { case ATOM: case ATOM_2: case CENTERTON: case AVOTON: case BAYTRAIL: case CHERRYTRAIL: case APOLLO_LAKE: case GEMINI_LAKE: case DENVERTON: case ADL: case RPL: case MTL: case LNL: case ARL: case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0x3F8, 0, 0x3F9, 0, 0x3FA, 0, 0, 0, 0 }) ); case NEHALEM_EP: case NEHALEM: case CLARKDALE: case WESTMERE_EP: case NEHALEM_EX: case WESTMERE_EX: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0, 0x3F8, 0, 0, 0x3F9, 0x3FA, 0, 0, 0}) ); case SANDY_BRIDGE: case JAKETOWN: case IVY_BRIDGE: case IVYTOWN: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0x60D, 0x3F8, 0, 0, 0x3F9, 0x3FA, 0, 0, 0}) ); case HASWELL: case HASWELL_2: case HASWELLX: case BDX_DE: case BDX: case KNL: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0x60D, 0x3F8, 0, 0, 0x3F9, 0x3FA, 0, 0, 0}) ); case SKX: case ICX: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0x60D, 0, 0, 0, 0x3F9, 0, 0, 0, 0}) ); case HASWELL_ULT: case BROADWELL: PCM_SKL_PATH_CASES case BROADWELL_XEON_E3: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0x60D, 0x3F8, 0, 0, 0x3F9, 0x3FA, 0x630, 0x631, 0x632}) ); default: std::cerr << "PCM error: package C-states support array is not initialized. Package C-states metrics will not be shown.\n"; PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }) ); }; // fill core C state array switch(cpu_family_model) { case ATOM: case ATOM_2: case CENTERTON: PCM_CSTATE_ARRAY(coreCStateMsr, PCM_PARAM_PROTECT({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }) ); case NEHALEM_EP: case NEHALEM: case CLARKDALE: case WESTMERE_EP: case NEHALEM_EX: case WESTMERE_EX: PCM_CSTATE_ARRAY(coreCStateMsr, PCM_PARAM_PROTECT({0, 0, 0, 0x3FC, 0, 0, 0x3FD, 0, 0, 0, 0}) ); case SANDY_BRIDGE: case JAKETOWN: case IVY_BRIDGE: case IVYTOWN: case HASWELL: case HASWELL_2: case HASWELL_ULT: case HASWELLX: case BDX_DE: case BDX: case BROADWELL: case BROADWELL_XEON_E3: case BAYTRAIL: case AVOTON: case CHERRYTRAIL: case APOLLO_LAKE: case GEMINI_LAKE: case DENVERTON: PCM_SKL_PATH_CASES case ADL: case RPL: case MTL: case LNL: case ARL: case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: case ICX: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: PCM_CSTATE_ARRAY(coreCStateMsr, PCM_PARAM_PROTECT({0, 0, 0, 0x3FC, 0, 0, 0x3FD, 0x3FE, 0, 0, 0}) ); case KNL: PCM_CSTATE_ARRAY(coreCStateMsr, PCM_PARAM_PROTECT({0, 0, 0, 0, 0, 0, 0x3FF, 0, 0, 0, 0}) ); case SKX: PCM_CSTATE_ARRAY(coreCStateMsr, PCM_PARAM_PROTECT({0, 0, 0, 0, 0, 0, 0x3FD, 0, 0, 0, 0}) ); default: std::cerr << "PCM error: core C-states support array is not initialized. Core C-states metrics will not be shown.\n"; PCM_CSTATE_ARRAY(coreCStateMsr, PCM_PARAM_PROTECT({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }) ); }; } #ifdef __linux__ constexpr auto perfSlotsPath = "/sys/bus/event_source/devices/cpu/events/slots"; constexpr auto perfBadSpecPath = "/sys/bus/event_source/devices/cpu/events/topdown-bad-spec"; constexpr auto perfBackEndPath = "/sys/bus/event_source/devices/cpu/events/topdown-be-bound"; constexpr auto perfFrontEndPath = "/sys/bus/event_source/devices/cpu/events/topdown-fe-bound"; constexpr auto perfRetiringPath = "/sys/bus/event_source/devices/cpu/events/topdown-retiring"; // L2 extensions: constexpr auto perfBrMispred = "/sys/bus/event_source/devices/cpu/events/topdown-br-mispredict"; constexpr auto perfFetchLat = "/sys/bus/event_source/devices/cpu/events/topdown-fetch-lat"; constexpr auto perfHeavyOps = "/sys/bus/event_source/devices/cpu/events/topdown-heavy-ops"; constexpr auto perfMemBound = "/sys/bus/event_source/devices/cpu/events/topdown-mem-bound"; bool PCM::perfSupportsTopDown() { static int yes = -1; if (-1 == yes) { const auto slots = readSysFS(perfSlotsPath, true); const auto bad = readSysFS(perfBadSpecPath, true); const auto be = readSysFS(perfBackEndPath, true); const auto fe = readSysFS(perfFrontEndPath, true); const auto ret = readSysFS(perfRetiringPath, true); bool supported = slots.size() && bad.size() && be.size() && fe.size() && ret.size(); if (isHWTMAL2Supported()) { supported = supported && readSysFS("/sys/bus/event_source/devices/cpu/events/topdown-br-mispredict", true).size() && readSysFS("/sys/bus/event_source/devices/cpu/events/topdown-fetch-lat", true).size() && readSysFS("/sys/bus/event_source/devices/cpu/events/topdown-heavy-ops", true).size() && readSysFS("/sys/bus/event_source/devices/cpu/events/topdown-mem-bound", true).size(); } yes = supported ? 1 : 0; } return 1 == yes; } #endif const std::vector qat_evtsel_mapping = { { "sample_cnt" }, //0x0 { "pci_trans_cnt" }, //0x1 { "max_rd_lat" }, //0x2 { "rd_lat_acc_avg" }, //0x3 { "max_lat" }, //0x4 { "lat_acc_avg" }, //0x5 { "bw_in" }, //0x6 { "bw_out" }, //0x7 { "at_page_req_lat_acc_avg" }, //0x8 { "at_trans_lat_acc_avg" }, //0x9 { "at_max_tlb_used" }, //0xA { "util_cpr0" }, //0xB { "util_dcpr0" }, //0xC { "util_dcpr1" }, //0xD { "util_dcpr2" }, //0xE { "util_xlt0" }, //0xF { "util_xlt1" }, //0x10 { "util_cph0" }, //0x11 { "util_cph1" }, //0x12 { "util_cph2" }, //0x13 { "util_cph3" }, //0x14 { "util_cph4" }, //0x15 { "util_cph5" }, //0x16 { "util_cph6" }, //0x17 { "util_cph7" }, //0x18 { "util_ath0" }, //0x19 { "util_ath1" }, //0x1A { "util_ath2" }, //0x1B { "util_ath3" }, //0x1C { "util_ath4" }, //0x1D { "util_ath5" }, //0x1E { "util_ath6" }, //0x1F { "util_ath7" }, //0x20 { "util_ucs0" }, //0x21 { "util_ucs1" }, //0x22 { "util_ucs2" }, //0x23 { "util_ucs3" }, //0x24 { "util_pke0" }, //0x25 { "util_pke1" }, //0x26 { "util_pke2" }, //0x27 { "util_pke3" }, //0x28 { "util_pke4" }, //0x29 { "util_pke5" }, //0x2A { "util_pke6" }, //0x2B { "util_pke7" }, //0x2C { "util_pke8" }, //0x2D { "util_pke9" }, //0x2E { "util_pke10" }, //0x2F { "util_pke11" }, //0x30 { "util_pke12" }, //0x31 { "util_pke13" }, //0x32 { "util_pke14" }, //0x33 { "util_pke15" }, //0x34 { "util_pke16" }, //0x35 { "util_pke17" }, //0x36 { "unknown" } //0x37 }; class VirtualDummyRegister : public HWRegister { uint64 lastValue; public: VirtualDummyRegister() : lastValue(0) {} void operator = (uint64 val) override { lastValue = val; } operator uint64 () override { return lastValue; } }; class QATTelemetryVirtualGeneralConfigRegister : public HWRegister { friend class QATTelemetryVirtualCounterRegister; int domain, b, d, f; PCM::IDX_OPERATION operation; PCM::IDX_STATE state; std::unordered_map data_cache; //data cache public: QATTelemetryVirtualGeneralConfigRegister(int domain_, int b_, int d_, int f_) : domain(domain_), b(b_), d(d_), f(f_), operation(PCM::QAT_TLM_STOP), state(PCM::IDX_STATE_OFF) { } void operator = (uint64 val) override { operation = PCM::IDX_OPERATION(val); #ifdef __linux__ std::ostringstream sysfs_path(std::ostringstream::out); std::string telemetry_filename; switch (operation) { case PCM::QAT_TLM_START: //enable state = PCM::IDX_STATE_ON; // falls through case PCM::QAT_TLM_STOP: //disable if (state == PCM::IDX_STATE_ON) { DBG(3, "QAT telemetry operation = ", operation, "."); sysfs_path << std::string("/sys/bus/pci/devices/") << std::hex << std::setw(4) << std::setfill('0') << domain << ":" << std::hex << std::setw(2) << std::setfill('0') << b << ":" << std::hex << std::setw(2) << std::setfill('0') << d << "." << std::hex << f << "/telemetry/control"; /*check telemetry for out-of tree driver*/ telemetry_filename = readSysFS(sysfs_path.str().c_str(), true); if(!telemetry_filename.size()){ /*is not oot driver, check telemetry for in tree driver (since kernel 6.8)*/ sysfs_path.str(""); sysfs_path << std::string("/sys/kernel/debug/qat_4xxx_") << std::hex << std::setw(4) << std::setfill('0') << domain << ":" << std::hex << std::setw(2) << std::setfill('0') << b << ":" << std::hex << std::setw(2) << std::setfill('0') << d << "." << std::hex << f << "/telemetry/control"; } if (writeSysFS(sysfs_path.str().c_str(), (operation == PCM::QAT_TLM_START ? "1" : "0")) == false) { std::cerr << "Linux sysfs: Error on control QAT telemetry operation = " << operation << ".\n"; } } break; case PCM::QAT_TLM_REFRESH: //refresh data if (state == PCM::IDX_STATE_ON) { DBG(3, "QAT telemetry operation = ", operation, "."); sysfs_path << std::string("/sys/bus/pci/devices/") << std::hex << std::setw(4) << std::setfill('0') << domain << ":" << std::hex << std::setw(2) << std::setfill('0') << b << ":" << std::hex << std::setw(2) << std::setfill('0') << d << "." << std::hex << f << "/telemetry/device_data"; /*check telemetry for out-of tree driver*/ telemetry_filename = readSysFS(sysfs_path.str().c_str(), true); if(!telemetry_filename.size()){ /*is not oot driver, check telemetry for in tree driver (since kernel 6.8)*/ sysfs_path.str(""); sysfs_path << std::string("/sys/kernel/debug/qat_4xxx_") << std::hex << std::setw(4) << std::setfill('0') << domain << ":" << std::hex << std::setw(2) << std::setfill('0') << b << ":" << std::hex << std::setw(2) << std::setfill('0') << d << "." << std::hex << f << "/telemetry/device_data"; } data_cache.clear(); readMapFromSysFS(sysfs_path.str().c_str(), data_cache); } break; default: break; } #endif } operator uint64 () override { return operation; } ~QATTelemetryVirtualGeneralConfigRegister() { try { DBG(3, "~QATTelemetryVirtualGeneralConfigRegister."); } catch (...) { // disallow throwing exceptions from the destructor because it will this will result in a call to terminate() } } }; class QATTelemetryVirtualControlRegister : public HWRegister { friend class QATTelemetryVirtualCounterRegister; uint64 event; public: QATTelemetryVirtualControlRegister() : event(0x0) { } void operator = (uint64 val) override { event = extract_bits(val, 32, 59); } operator uint64 () override { return event; } }; class QATTelemetryVirtualCounterRegister : public HWRegister { std::shared_ptr gConfigReg; std::shared_ptr controlReg; const int ctr_id; public: QATTelemetryVirtualCounterRegister( std::shared_ptr gConfigReg_, std::shared_ptr controlReg_, const int ctr_id_) : gConfigReg(gConfigReg_), controlReg(controlReg_), ctr_id(ctr_id_) { } void operator = (uint64 /* val */) override { // no-op } operator uint64 () override { uint64 result = 0; uint32 eventsel = controlReg->event; std::string key; if (eventsel < qat_evtsel_mapping.size()) { key = qat_evtsel_mapping[eventsel]; if (gConfigReg->data_cache.find(key) != gConfigReg->data_cache.end()) { result = gConfigReg->data_cache.at(key); } } DBG(3, std::hex, "QAT-CTR(0x", ctr_id, "), key=", key, ", val=0x", std::hex, result, ".", std::dec); return result; } }; bool PCM::discoverSystemTopology() { typedef std::map socketIdMap_type; socketIdMap_type socketIdMap; PCM_CPUID_INFO cpuid_args; uint32 smtMaskWidth = 0; uint32 coreMaskWidth = 0; uint32 l2CacheMaskShift = 0; struct domain { TopologyEntry::DomainTypeID type = TopologyEntry::DomainTypeID::InvalidDomainTypeID; unsigned levelShift = 0, nextLevelShift = 0, width = 0; }; std::unordered_map topologyDomainMap; { TemporalThreadAffinity aff0(0); if (initCoreMasks(smtMaskWidth, coreMaskWidth, l2CacheMaskShift) == false) { std::cerr << "ERROR: Major problem? No leaf 0 under cpuid function 11.\n"; return false; } int subleaf = 0; std::vector topologyDomains; if (max_cpuid >= 0x1F) { subleaf = 0; do { pcm_cpuid(0x1F, subleaf, cpuid_args); domain d; d.type = (TopologyEntry::DomainTypeID)extract_bits_32(cpuid_args.reg.ecx, 8, 15); if (d.type == TopologyEntry::DomainTypeID::InvalidDomainTypeID) { break; } d.nextLevelShift = extract_bits_32(cpuid_args.reg.eax, 0, 4); d.levelShift = topologyDomains.empty() ? 0 : topologyDomains.back().nextLevelShift; d.width = d.nextLevelShift - d.levelShift; topologyDomains.push_back(d); ++subleaf; } while (true); if (topologyDomains.size()) { domain d; d.type = TopologyEntry::DomainTypeID::SocketPackageDomain; d.levelShift = topologyDomains.back().nextLevelShift; d.nextLevelShift = 32; d.width = d.nextLevelShift - d.levelShift; topologyDomains.push_back(d); } for (size_t l = 0; l < topologyDomains.size(); ++l) { topologyDomainMap[topologyDomains[l].type] = topologyDomains[l]; #if 0 std::cerr << "Topology level: " << l << " type: " << topologyDomains[l].type << " (" << TopologyEntry::getDomainTypeStr(topologyDomains[l].type) << ")" << " width: " << topologyDomains[l].width << " levelShift: " << topologyDomains[l].levelShift << " nextLevelShift: " << topologyDomains[l].nextLevelShift << "\n"; #endif } } } #ifndef __APPLE__ auto populateEntry = [&topologyDomainMap,&smtMaskWidth, &coreMaskWidth, &l2CacheMaskShift](TopologyEntry& entry) { auto getAPICID = [&](const uint32 leaf) { PCM_CPUID_INFO cpuid_args; #if defined(__FreeBSD__) || defined(__DragonFly__) pcm_cpuid_bsd(leaf, cpuid_args, entry.os_id); #else pcm_cpuid(leaf, 0x0, cpuid_args); #endif return cpuid_args.array[3]; }; if (topologyDomainMap.size()) { auto getID = [&topologyDomainMap](const int apic_id, const TopologyEntry::DomainTypeID t) { const auto di = topologyDomainMap.find(t); if (di != topologyDomainMap.end()) { const auto & d = di->second; return extract_bits_32(apic_id, d.levelShift, d.nextLevelShift - 1); } return 0U; }; entry.tile_id = extract_bits_32(getAPICID(0xb), l2CacheMaskShift, 31); const int apic_id = getAPICID(0x1F); entry.thread_id = getID(apic_id, TopologyEntry::DomainTypeID::LogicalProcessorDomain); entry.core_id = getID(apic_id, TopologyEntry::DomainTypeID::CoreDomain); entry.module_id = getID(apic_id, TopologyEntry::DomainTypeID::ModuleDomain); if (entry.tile_id == 0) { entry.tile_id = getID(apic_id, TopologyEntry::DomainTypeID::TileDomain); } entry.die_id = getID(apic_id, TopologyEntry::DomainTypeID::DieDomain); entry.die_grp_id = getID(apic_id, TopologyEntry::DomainTypeID::DieGrpDomain); entry.socket_id = getID(apic_id, TopologyEntry::DomainTypeID::SocketPackageDomain); auto getDomain = [&topologyDomainMap](const TopologyEntry::DomainTypeID t) { auto di = topologyDomainMap.find(t); if (di != topologyDomainMap.end()) { return di->second; } throw std::runtime_error("DomainType not found"); }; domain d1 = getDomain( TopologyEntry::DomainTypeID::CoreDomain ); domain d2 = getDomain( TopologyEntry::DomainTypeID::SocketPackageDomain ); entry.socket_unique_core_id = extract_bits_32( apic_id, d1.levelShift, d2.levelShift - 1 ); } else { fillEntry(entry, smtMaskWidth, coreMaskWidth, l2CacheMaskShift, getAPICID(0xb)); } }; #endif auto populateHybridEntry = [this](TopologyEntry& entry, int core) -> bool { if (hybrid == false) return true; PCM_CPUID_INFO cpuid_args; #if defined(__FreeBSD__) || defined(__DragonFly__) pcm_cpuid_bsd(0x1a, cpuid_args, core); #elif defined (_MSC_VER) || defined(__linux__) pcm_cpuid(0x1a, 0x0, cpuid_args); (void)core; #else std::cerr << "PCM Error: Hybrid processors are not supported for your OS\n"; (void)core; return false; #endif entry.native_cpu_model = extract_bits_32(cpuid_args.reg.eax, 0, 23); entry.core_type = (TopologyEntry::CoreType) extract_bits_32(cpuid_args.reg.eax, 24, 31); return true; }; #ifdef _MSC_VER // version for Windows 7 and later version char * slpi = new char[sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)]; DWORD len = (DWORD)sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX); BOOL res = GetLogicalProcessorInformationEx(RelationAll, (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)slpi, &len); while (res == FALSE) { deleteAndNullifyArray(slpi); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { slpi = new char[len]; res = GetLogicalProcessorInformationEx(RelationAll, (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)slpi, &len); } else { tcerr << "Error in Windows function 'GetLogicalProcessorInformationEx': " << GetLastError() << " "; const TCHAR * strError = _com_error(GetLastError()).ErrorMessage(); if (strError) tcerr << strError; tcerr << "\n"; return false; } } char * base_slpi = slpi; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pi = NULL; for ( ; slpi < base_slpi + len; slpi += (DWORD)pi->Size) { pi = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)slpi; if (pi->Relationship == RelationProcessorCore) { const auto current_threads_per_core = (pi->Processor.Flags == LTP_PC_SMT) ? 2 : 1; DBG(3, "thr per core: " , current_threads_per_core ); num_cores += current_threads_per_core; } } num_online_cores = num_cores; if (num_cores != GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)) { std::cerr << "Error in processor group size counting: " << num_cores << "!=" << GetActiveProcessorCount(ALL_PROCESSOR_GROUPS) << "\n"; std::cerr << "Make sure your binary is compiled for 64-bit: using 'x64' platform configuration.\n"; return false; } for (int i = 0; i < (int)num_cores; i++) { ThreadGroupTempAffinity affinity(i); TopologyEntry entry; entry.os_id = i; populateEntry(entry); if (populateHybridEntry(entry, i) == false) { return false; } topology.push_back(entry); socketIdMap[entry.socket_id] = 0; } deleteAndNullifyArray(base_slpi); #else // for Linux, Mac OS, FreeBSD and DragonFlyBSD TopologyEntry entry; #ifdef __linux__ num_cores = readMaxFromSysFS("/sys/devices/system/cpu/present"); if(num_cores == -1) { std::cerr << "Cannot read number of present cores\n"; return false; } ++num_cores; // open /proc/cpuinfo FILE * f_cpuinfo = fopen("/proc/cpuinfo", "r"); if (!f_cpuinfo) { std::cerr << "Cannot open /proc/cpuinfo file.\n"; return false; } // map with key=pkg_apic_id (not necessarily zero based or sequential) and // associated value=socket_id that should be 0 based and sequential std::map found_pkg_ids; topology.resize(num_cores); char buffer[1024]; while (0 != fgets(buffer, 1024, f_cpuinfo)) { if (strncmp(buffer, "processor", sizeof("processor") - 1) == 0) { pcm_sscanf(buffer) >> s_expect("processor\t: ") >> entry.os_id; DBG(3, "os_core_id: " , entry.os_id ); try { TemporalThreadAffinity _(entry.os_id); populateEntry(entry); if (populateHybridEntry(entry, entry.os_id) == false) { return false; } topology[entry.os_id] = entry; socketIdMap[entry.socket_id] = 0; ++num_online_cores; } catch (std::exception &) { std::cerr << "Marking core " << entry.os_id << " offline\n"; } } } fclose(f_cpuinfo); #elif defined(__FreeBSD__) || defined(__DragonFly__) size_t size = sizeof(num_cores); if(0 != sysctlbyname("hw.ncpu", &num_cores, &size, NULL, 0)) { std::cerr << "Unable to get hw.ncpu from sysctl.\n"; return false; } num_online_cores = num_cores; if (modfind("cpuctl") == -1) { std::cerr << "cpuctl(4) not loaded.\n"; return false; } for (int i = 0; i < num_cores; i++) { entry.os_id = i; populateEntry(entry); if (populateHybridEntry(entry, i) == false) { return false; } topology.push_back(entry); socketIdMap[entry.socket_id] = 0; } #else // Getting processor info for Mac OS #define SAFE_SYSCTLBYNAME(message, ret_value) \ { \ size_t size; \ char *pParam; \ if(0 != sysctlbyname(message, NULL, &size, NULL, 0)) \ { \ std::cerr << "Unable to determine size of " << message << " sysctl return type.\n"; \ return false; \ } \ if(NULL == (pParam = (char *)malloc(size))) \ { \ std::cerr << "Unable to allocate memory for " << message << "\n"; \ return false; \ } \ if(0 != sysctlbyname(message, (void*)pParam, &size, NULL, 0)) \ { \ std::cerr << "Unable to get " << message << " from sysctl.\n"; \ return false; \ } \ ret_value = convertUnknownToInt(size, pParam); \ freeAndNullify(pParam); \ } // End SAFE_SYSCTLBYNAME // Using OSXs sysctl to get the number of CPUs right away SAFE_SYSCTLBYNAME("hw.logicalcpu", num_cores) num_online_cores = num_cores; #undef SAFE_SYSCTLBYNAME // The OSX version needs the MSR handle earlier so that it can build the CPU topology. // This topology functionality should potentially go into a different KEXT for(int i = 0; i < num_cores; i++) { MSR.push_back(std::make_shared(i)); } assert(num_cores > 0); TopologyEntry entries[num_cores]; if (MSR[0]->buildTopology(num_cores, entries) != 0) { std::cerr << "Unable to build CPU topology" << std::endl; return false; } for(int i = 0; i < num_cores; i++){ socketIdMap[entries[i].socket_id] = 0; if(entries[i].os_id >= 0) { if (populateHybridEntry(entries[i], i) == false) { return false; } topology.push_back(entries[i]); } } // End of OSX specific code #endif #endif //end of ifdef _MSC_VER if(num_cores == 0) { num_cores = (int32)topology.size(); } if(num_sockets == 0) { num_sockets = (int32)(std::max)(socketIdMap.size(), (size_t)1); DBG(1, " num_sockets = ", num_sockets); } socketIdMap_type::iterator s = socketIdMap.begin(); for (uint32 sid = 0; s != socketIdMap.end(); ++s) { s->second = sid++; } // use map to change apic socket id to the logical socket id for (int i = 0; (i < (int)num_cores) && (!socketIdMap.empty()); ++i) { DBG(2, "socket_id: ", topology[i].socket_id, ", socketIdMap tells me: ", socketIdMap[topology[i].socket_id]); if(isCoreOnline((int32)i)) topology[i].socket_id = socketIdMap[topology[i].socket_id]; } #if 0 std::cerr << "Number of socket ids: " << socketIdMap.size() << "\n"; std::cerr << "Topology:\nsocket os_id core_id\n"; for (int i = 0; i < num_cores; ++i) { std::cerr << topology[i].socket_id << " " << topology[i].os_id << " " << topology[i].core_id << "\n"; } #endif if (threads_per_core == 0) { for (int i = 0; i < (int)num_cores; ++i) { if (topology[i].isSameCore( topology[0] )) ++threads_per_core; } assert(threads_per_core != 0); } if(num_phys_cores_per_socket == 0 && num_cores == num_online_cores) num_phys_cores_per_socket = num_cores / num_sockets / threads_per_core; if(num_online_cores == 0) num_online_cores = num_cores; s = socketIdMap.begin(); for (; s != socketIdMap.end(); ++s) { systemTopology->addSocket( s->second ); } for (int32 cid = 0; cid < num_cores; ++cid) { DBG(3, "Cid: ", cid); systemTopology->addThread( cid, topology[cid] ); } // All threads are here now so we can set the refCore for a socket for ( auto& socket : systemTopology->sockets() ) socket->setRefCore(); int32 i = 0; socketRefCore.resize(num_sockets, -1); for(i = 0; i < num_cores; ++i) { if(isCoreOnline(i)) { socketRefCore[topology[i].socket_id] = i; } } num_online_sockets = 0; for(i = 0; i < num_sockets; ++i) { if(isSocketOnline(i)) { ++num_online_sockets; } } FrontendBoundSlots.resize(num_cores, 0); BadSpeculationSlots.resize(num_cores, 0); BackendBoundSlots.resize(num_cores, 0); RetiringSlots.resize(num_cores, 0); AllSlotsRaw.resize(num_cores, 0); MemBoundSlots.resize(num_cores, 0); FetchLatSlots.resize(num_cores, 0); BrMispredSlots.resize(num_cores, 0); HeavyOpsSlots.resize(num_cores, 0); #if 0 std::cerr << "Socket reference cores:\n"; for(int32 i=0; i< num_sockets;++i) { std::cerr << "socketRefCore[" << i << "]=" << socketRefCore[i] << "\n"; } #endif return true; } void PCM::printSystemTopology() const { const bool all_cores_online_no_hybrid = (num_cores == num_online_cores && hybrid == false); if (all_cores_online_no_hybrid) { std::cerr << "Number of physical cores: " << (num_cores/threads_per_core) << "\n"; } std::cerr << "Number of logical cores: " << num_cores << "\n"; std::cerr << "Number of online logical cores: " << num_online_cores << "\n"; if (all_cores_online_no_hybrid) { std::cerr << "Threads (logical cores) per physical core: " << threads_per_core << "\n"; } else { std::cerr << "Threads (logical cores) per physical core: " << threads_per_core << " (maybe imprecise due to core offlining/hybrid CPU)\n"; std::cerr << "Offlined cores: "; for (int i = 0; i < (int)num_cores; ++i) if(isCoreOnline((int32)i) == false) std::cerr << i << " "; std::cerr << "\n"; } std::cerr << "Num sockets: " << num_sockets << "\n"; if (all_cores_online_no_hybrid) { std::cerr << "Physical cores per socket: " << num_phys_cores_per_socket << "\n"; } else { std::cerr << "Physical cores per socket: " << num_cores / num_sockets / threads_per_core << " (maybe imprecise due to core offlining/hybrid CPU)\n"; } if (hybrid == false) { // TODO: deprecate this output and move it to uncore PMU section (use getMaxNumOfUncorePMUs(CBO_PMU_ID) ) std::cerr << "Last level cache slices per socket: " << getMaxNumOfCBoxesInternal() << "\n"; } std::cerr << "Core PMU (perfmon) version: " << perfmon_version << "\n"; std::cerr << "Number of core PMU generic (programmable) counters: " << core_gen_counter_num_max << "\n"; std::cerr << "Width of generic (programmable) counters: " << core_gen_counter_width << " bits\n"; if (perfmon_version > 0) { std::cerr << "Number of core PMU fixed counters: " << core_fixed_counter_num_max << "\n"; std::cerr << "Width of fixed counters: " << core_fixed_counter_width << " bits\n"; } if (perfmon_version < 2 && vm == true) { std::cerr << "Warning: detected an unsupported virtualized environment: the hypervisor has limited the core PMU (perfmon) version to " << perfmon_version << "\n"; } } bool PCM::initMSR() { #ifdef __APPLE__ for (size_t i=0; i < MSR.size(); ++i) { systemTopology->addMSRHandleToOSThread(MSR[i], (uint32)i); } #else try { for (int i = 0; i < (int)num_cores; ++i) { if ( isCoreOnline( (int32)i ) ) { MSR.push_back(std::make_shared(i)); systemTopology->addMSRHandleToOSThread( MSR.back(), (uint32)i ); } else { // the core is offlined, assign an invalid MSR handle MSR.push_back(std::make_shared()); systemTopology->addMSRHandleToOSThread( MSR.back(), (uint32)i ); } } } catch (...) { // failed MSR.clear(); std::cerr << "Can not access CPUs Model Specific Registers (MSRs).\n"; #ifdef _MSC_VER std::cerr << "You must have signed msr.sys driver in your current directory and have administrator rights to run this program.\n"; #elif defined(__linux__) std::cerr << "execute 'modprobe msr' as root user, then execute pcm as root user.\n"; #elif defined(__FreeBSD__) || defined(__DragonFly__) std::cerr << "Ensure cpuctl module is loaded and that you have read and write\n"; std::cerr << "permissions for /dev/cpuctl* devices (the 'chown' command can help).\n"; #endif return false; } #endif return true; } bool PCM::detectNominalFrequency() { if (MSR.size()) { if (max_cpuid >= 0x16) { PCM_CPUID_INFO cpuinfo; pcm_cpuid(0x16, cpuinfo); nominal_frequency = uint64(extract_bits_32(cpuinfo.reg.eax, 0, 15)) * 1000000ULL;; } if (!nominal_frequency) { uint64 freq = 0; MSR[socketRefCore[0]]->read(PLATFORM_INFO_ADDR, &freq); const uint64 bus_freq = ( cpu_family_model == SANDY_BRIDGE || cpu_family_model == JAKETOWN || cpu_family_model == IVYTOWN || cpu_family_model == HASWELLX || cpu_family_model == BDX_DE || cpu_family_model == BDX || cpu_family_model == IVY_BRIDGE || cpu_family_model == HASWELL || cpu_family_model == BROADWELL || cpu_family_model == AVOTON || cpu_family_model == APOLLO_LAKE || cpu_family_model == GEMINI_LAKE || cpu_family_model == DENVERTON || useSKLPath() || cpu_family_model == SNOWRIDGE || cpu_family_model == ELKHART_LAKE || cpu_family_model == JASPER_LAKE || cpu_family_model == KNL || cpu_family_model == ADL || cpu_family_model == RPL || cpu_family_model == MTL || cpu_family_model == LNL || cpu_family_model == ARL || cpu_family_model == SKX || cpu_family_model == ICX || cpu_family_model == SPR || cpu_family_model == EMR || cpu_family_model == GNR || cpu_family_model == GNR_D || cpu_family_model == SRF || cpu_family_model == GRR ) ? (100000000ULL) : (133333333ULL); nominal_frequency = ((freq >> 8) & 255) * bus_freq; } if(!nominal_frequency) nominal_frequency = get_frequency_from_cpuid(); if(!nominal_frequency) { computeNominalFrequency(); } if(!nominal_frequency) { std::cerr << "Error: Can not detect core frequency.\n"; destroyMSR(); return false; } #ifndef PCM_SILENT std::cerr << "Nominal core frequency: " << nominal_frequency << " Hz\n"; #endif } return true; } void PCM::initEnergyMonitoring() { if(packageEnergyMetricsAvailable() && MSR.size()) { uint64 rapl_power_unit = 0; MSR[socketRefCore[0]]->read(MSR_RAPL_POWER_UNIT,&rapl_power_unit); uint64 energy_status_unit = extract_bits(rapl_power_unit,8,12); if (cpu_family_model == PCM::CHERRYTRAIL || cpu_family_model == PCM::BAYTRAIL) joulesPerEnergyUnit = double(1ULL << energy_status_unit)/1000000.; // (2)^energy_status_unit microJoules else joulesPerEnergyUnit = 1./double(1ULL<read(MSR_PKG_POWER_INFO,&package_power_info); pkgThermalSpecPower = (int32) (double(extract_bits(package_power_info, 0, 14))*wattsPerPowerUnit); pkgMinimumPower = (int32) (double(extract_bits(package_power_info, 16, 30))*wattsPerPowerUnit); pkgMaximumPower = (int32) (double(extract_bits(package_power_info, 32, 46))*wattsPerPowerUnit); #ifndef PCM_SILENT std::cerr << "Package thermal spec power: " << pkgThermalSpecPower << " Watt; "; std::cerr << "Package minimum power: " << pkgMinimumPower << " Watt; "; std::cerr << "Package maximum power: " << pkgMaximumPower << " Watt;\n"; #endif int i = 0; if(energy_status.empty()) for (i = 0; i < (int)num_sockets; ++i) energy_status.push_back( std::make_shared( new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[i]], MSR_PKG_ENERGY_STATUS), 32, 10000)); if(dramEnergyMetricsAvailable() && dram_energy_status.empty()) for (i = 0; i < (int)num_sockets; ++i) dram_energy_status.push_back( std::make_shared( new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[i]], MSR_DRAM_ENERGY_STATUS), 32, 10000)); } if (ppEnergyMetricsAvailable() && MSR.size() && num_sockets == 1 && pp_energy_status.empty()) { pp_energy_status.push_back(std::make_shared( new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[0]], MSR_PP0_ENERGY_STATUS), 32, 10000)); pp_energy_status.push_back(std::make_shared( new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[0]], MSR_PP1_ENERGY_STATUS), 32, 10000)); } if (systemEnergyMetricAvailable() && MSR.size() && (system_energy_status.get() == nullptr)) { system_energy_status = std::make_shared( new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[0]], MSR_SYS_ENERGY_STATUS, 0x00000000FFFFFFFF), 32, 10000); } } static const uint32 UBOX0_DEV_IDS[] = { 0x3451, 0x3251 }; std::vector > socket2UBOX0bus; void initSocket2Bus(std::vector > & socket2bus, uint32 device, uint32 function, const uint32 DEV_IDS[], uint32 devIdsSize); void initSocket2Ubox0Bus() { initSocket2Bus(socket2UBOX0bus, SERVER_UBOX0_REGISTER_DEV_ADDR, SERVER_UBOX0_REGISTER_FUNC_ADDR, UBOX0_DEV_IDS, (uint32)sizeof(UBOX0_DEV_IDS) / sizeof(UBOX0_DEV_IDS[0])); } bool initRootBusMap(std::map &rootbus_map) { bool mapped = false; static const uint32 MSM_DEV_IDS[] = { SPR_MSM_DEV_ID }; std::vector > socket2MSMbus; initSocket2Bus(socket2MSMbus, SPR_MSM_DEV_ADDR, SPR_MSM_FUNC_ADDR, MSM_DEV_IDS, (uint32)sizeof(MSM_DEV_IDS) / sizeof(MSM_DEV_IDS[0])); for (auto & s2bus : socket2MSMbus) { uint32 cpuBusValid = 0x0; int cpuBusPackageId; std::vector cpuBusNo; if (get_cpu_bus(s2bus.first, s2bus.second, SPR_MSM_DEV_ADDR, SPR_MSM_FUNC_ADDR, cpuBusValid, cpuBusNo, cpuBusPackageId) == false) return false; for (int cpuBusId = 0; cpuBusId < SPR_MSM_CPUBUSNO_MAX; ++cpuBusId) { if (!((cpuBusValid >> cpuBusId) & 0x1)) { DBG(2, "CPU bus " , cpuBusId, " is disabled on package " , cpuBusPackageId); continue; } int rootBus = (cpuBusNo[(int)(cpuBusId / 4)] >> ((cpuBusId % 4) * 8)) & 0xff; rootbus_map[((s2bus.first << 8) | rootBus)] = cpuBusPackageId; DBG(2, "Mapped CPU bus #" , std::dec , cpuBusId , std::hex , " (domain=0x" , s2bus.first , " bus=0x" , rootBus , ") to " , std::dec , "package" , cpuBusPackageId); } mapped = true; } return mapped; } #define SPR_IDX_ACCEL_COUNTER_MAX_NUM (8) #define SPR_QAT_ACCEL_COUNTER_MAX_NUM (16) struct idx_accel_dev_info { uint64 mem_bar; uint32 numa_node; uint32 socket_id; uint32 domain; uint32 bus; uint32 dev; uint32 func; }; bool getIDXDevBAR(std::vector > & socket2bus, uint32 dev, uint32 func, std::map &bus2socket, std::vector &idx_devs) { uint64 memBar = 0x0; uint32 pciCmd = 0x0, pmCsr= 0x0; uint32 numaNode = 0xff; struct idx_accel_dev_info idx_dev; for (auto & s2bus : socket2bus) { memBar = 0x0; pciCmd = 0x0; PciHandleType IDXHandle(s2bus.first, s2bus.second, dev, func); IDXHandle.read64(SPR_IDX_ACCEL_BAR0_OFFSET, &memBar); IDXHandle.read32(SPR_IDX_ACCEL_PCICMD_OFFSET, &pciCmd); IDXHandle.read32(SPR_IDX_ACCEL_PMCSR_OFFSET, &pmCsr); if (memBar == 0x0 || (pciCmd & 0x02) == 0x0) //Check BAR0 is valid or NOT. { std::cerr << "Warning: IDX - BAR0 of B:0x" << std::hex << s2bus.second << ",D:0x" << std::hex << dev << ",F:0x" << std::hex << func << " is invalid(memBar=0x" << std::hex << memBar << ", pciCmd=0x" << std::hex << pciCmd <<"), skipped." << std::dec << std::endl; continue; } if ((pmCsr & 0x03) == 0x3) //Check power state { std::cout << "Warning: IDX - Power state of B:0x" << std::hex << s2bus.second << ",D:0x" << std::hex << dev << ",F:0x" << std::hex << func \ << " is off, skipped." << std::endl; continue; } numaNode = 0xff; #ifdef __linux__ std::ostringstream devNumaNodePath(std::ostringstream::out); devNumaNodePath << std::string("/sys/bus/pci/devices/") << std::hex << std::setw(4) << std::setfill('0') << s2bus.first << ":" << std::hex << std::setw(2) << std::setfill('0') << s2bus.second << ":" << std::hex << std::setw(2) << std::setfill('0') << dev << "." << std::hex << func << "/numa_node"; const std::string devNumaNodeStr = readSysFS(devNumaNodePath.str().c_str(), true); if (devNumaNodeStr.size()) { numaNode = std::atoi(devNumaNodeStr.c_str()); if (numaNode == (std::numeric_limits::max)()) { numaNode = 0xff; //translate to special value for numa disable case. } } DBG(2, "IDX DEBUG: numa node file path=" , devNumaNodePath.str().c_str() , ", value=" , numaNode); #endif idx_dev.mem_bar = memBar; idx_dev.numa_node = numaNode; idx_dev.socket_id = 0xff; idx_dev.domain = s2bus.first; idx_dev.bus = s2bus.second; idx_dev.dev = dev; idx_dev.func = func; if (bus2socket.find(((s2bus.first << 8 ) | s2bus.second)) != bus2socket.end()) { idx_dev.socket_id = bus2socket.at(((s2bus.first << 8 ) | s2bus.second)); } idx_devs.push_back(idx_dev); } return true; } void PCM::initUncoreObjects() { if (hasPCICFGUncore() && MSR.size()) { int i = 0; bool failed = false; try { for (i = 0; i < (int)num_sockets; ++i) { serverUncorePMUs.push_back(std::make_shared(i, this)); } } catch (std::runtime_error & e) { std::cerr << e.what() << "\n"; failed = true; } catch (...) { failed = true; } if (failed) { serverUncorePMUs.clear(); std::cerr << "Can not access server uncore PCI configuration space. Access to uncore counters (memory and QPI bandwidth) is disabled.\n"; #ifdef _MSC_VER std::cerr << "You must have signed msr.sys driver in your current directory and have administrator rights to run this program.\n"; #else DBG(1, "you must have read and write permissions for /proc/bus/pci/7f/10.* and /proc/bus/pci/ff/10.* devices (the 'chown' command can help)."); DBG(1, "you must have read and write permissions for /dev/mem device (the 'chown' command can help)."); DBG(1, "you must have read permission for /sys/firmware/acpi/tables/MCFG device (the 'chmod' command can help)."); std::cerr << "You must be root to access server uncore counters in PCM.\n"; #endif } } else if(hasClientMCCounters() && MSR.size()) { // initialize memory bandwidth counting try { switch (cpu_family_model) { case TGL: case ADL: // TGLClientBW works fine for ADL case RPL: // TGLClientBW works fine for RPL case MTL: // TGLClientBW works fine for MTL case LNL: // TGLClientBW works fine for LNL case ARL: // TGLClientBW works fine for ARL clientBW = std::make_shared(); break; /* Disabled since ADLClientBW requires 2x multiplier for BW on top case ADL: case RPL: clientBW = std::make_shared(); break; */ default: clientBW = std::make_shared(); } clientImcReads = std::make_shared( new CounterWidthExtender::ClientImcReadsCounter(clientBW), 32, 10000); clientImcWrites = std::make_shared( new CounterWidthExtender::ClientImcWritesCounter(clientBW), 32, 10000); clientGtRequests = std::make_shared( new CounterWidthExtender::ClientGtRequestsCounter(clientBW), 32, 10000); clientIaRequests = std::make_shared( new CounterWidthExtender::ClientIaRequestsCounter(clientBW), 32, 10000); clientIoRequests = std::make_shared( new CounterWidthExtender::ClientIoRequestsCounter(clientBW), 32, 10000); } catch(...) { std::cerr << "Can not read memory controller counter information from PCI configuration space. Access to memory bandwidth counters is not possible.\n"; #ifdef _MSC_VER // TODO: add message here #endif #ifdef __linux__ std::cerr << "You must be root to access these SandyBridge/IvyBridge/Haswell counters in PCM. \n"; #endif } } switch (cpu_family_model) { case ICX: case SNOWRIDGE: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: { bool failed = false; try { initSocket2Ubox0Bus(); } catch (std::exception& e) { std::cerr << e.what() << "\n"; failed = true; } catch (...) { failed = true; } if (failed) { std::cerr << "Can not read PCI configuration space bus mapping. Access to uncore counters is disabled.\n"; } } break; } if (cpu_family_model == ICX || cpu_family_model == SNOWRIDGE) { for (size_t s = 0; s < (size_t)num_sockets && s < socket2UBOX0bus.size() && s < serverUncorePMUs.size(); ++s) { serverBW.push_back(std::make_shared(serverUncorePMUs[s]->getNumMC(), socket2UBOX0bus[s].first, socket2UBOX0bus[s].second)); DBG(2, " Added serverBW object serverUncorePMUs[s]->getNumMC() = " , serverUncorePMUs[s]->getNumMC()); } if (socket2UBOX0bus.size() != (size_t)num_sockets) { std::cerr << "PCM warning: found " << socket2UBOX0bus.size() << " uboxes. Expected " << num_sockets << std::endl; } } if (useLinuxPerfForUncore()) { initUncorePMUsPerf(); } else { initUncorePMUsDirect(); } //TPMIHandle::setVerbose(true); try { if (isServerCPU() && TPMIHandle::getNumInstances() == (size_t)num_sockets) { DBG(1, "TPMIHandle::getNumInstances(): ", TPMIHandle::getNumInstances()); UFSStatus.resize(num_sockets); for (uint32 s = 0; s < (uint32)num_sockets; ++s) { try { TPMIHandle h(s, UFS_ID, UFS_FABRIC_CLUSTER_OFFSET * sizeof(uint64)); DBG(1, "Socket ", s, " dies: ", h.getNumEntries()); for (size_t die = 0; die < h.getNumEntries(); ++die) { const auto clusterOffset = extract_bits(h.read64(die), 0, 7); UFSStatus[s].push_back(std::make_shared(s, UFS_ID, (clusterOffset + UFS_STATUS)* sizeof(uint64))); } } catch (std::exception & e) { std::cerr << "ERROR: Could not open UFS TPMI register on socket " << s << ". Uncore frequency metrics will be unavailable. Exception details: " << e.what() << "\n"; } } } } catch (std::exception & e) { std::cerr << "ERROR: Could not initialize TPMI. Uncore frequency metrics will be unavailable. Exception details: " << e.what() << "\n"; } for (uint32 s = 0; s < (uint32)num_sockets; ++s) { std::cerr << "Socket " << s << ":" << " " << getMaxNumOfUncorePMUs(PCU_PMU_ID, s) << " PCU units detected." " " << ((s < iioPMUs.size()) ? iioPMUs[s].size() : 0) << " IIO units detected." " " << ((s < irpPMUs.size()) ? irpPMUs[s].size() : 0) << " IRP units detected." " " << getMaxNumOfUncorePMUs(CBO_PMU_ID, s) << " CHA/CBO units detected." " " << getMaxNumOfUncorePMUs(MDF_PMU_ID, s) << " MDF units detected." " " << getMaxNumOfUncorePMUs(UBOX_PMU_ID, s) << " UBOX units detected." " " << ((s < cxlPMUs.size()) ? cxlPMUs[s].size() : 0) << " CXL units detected." " " << getMaxNumOfUncorePMUs(PCIE_GEN5x16_PMU_ID, s) << " PCIE_GEN5x16 units detected." " " << getMaxNumOfUncorePMUs(PCIE_GEN5x8_PMU_ID, s) << " PCIE_GEN5x8 units detected." "\n"; } } void PCM::globalFreezeUncoreCounters() { globalFreezeUncoreCountersInternal(1ULL); } void PCM::globalUnfreezeUncoreCounters() { globalFreezeUncoreCountersInternal(0ULL); } // 1 : freeze // 0 : unfreeze void PCM::globalFreezeUncoreCountersInternal(const unsigned long long int freeze) { for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto& handle = MSR[socketRefCore[s]]; switch (cpu_family_model) { case SPR: case EMR: handle->write(SPR_MSR_UNCORE_PMON_GLOBAL_CTL, freeze); break; case SKX: case ICX: handle->write(MSR_UNCORE_PMON_GLOBAL_CTL, (1ULL - freeze) << 61ULL); break; case HASWELLX: case BDX: handle->write(MSR_UNCORE_PMON_GLOBAL_CTL, (1ULL - freeze) << 29ULL); break; case IVYTOWN: handle->write(IVT_MSR_UNCORE_PMON_GLOBAL_CTL, (1ULL - freeze) << 29ULL); break; } } } void PCM::initUncorePMUsDirect() { uncorePMUs.resize(num_sockets); for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; // unfreeze uncore PMUs globalUnfreezeUncoreCounters(); switch (cpu_family_model) { case IVYTOWN: case JAKETOWN: uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ std::make_shared(handle, JKTIVT_UBOX_MSR_PMON_CTL0_ADDR), std::make_shared(handle, JKTIVT_UBOX_MSR_PMON_CTL1_ADDR) }, CounterValueRegs{ std::make_shared(handle, JKTIVT_UBOX_MSR_PMON_CTR0_ADDR), std::make_shared(handle, JKTIVT_UBOX_MSR_PMON_CTR1_ADDR) }; uncorePMUs[s][0][UBOX_PMU_ID].push_back( std::make_shared( std::shared_ptr(), CounterControlRegs, CounterValueRegs, std::make_shared(handle, JKTIVT_UCLK_FIXED_CTL_ADDR), std::make_shared(handle, JKTIVT_UCLK_FIXED_CTR_ADDR) ) ); } break; case SPR: case EMR: uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ std::make_shared(handle, SPR_UBOX_MSR_PMON_CTL0_ADDR), std::make_shared(handle, SPR_UBOX_MSR_PMON_CTL1_ADDR) }, CounterValueRegs{ std::make_shared(handle, SPR_UBOX_MSR_PMON_CTR0_ADDR), std::make_shared(handle, SPR_UBOX_MSR_PMON_CTR1_ADDR) }; uncorePMUs[s][0][UBOX_PMU_ID].push_back( std::make_shared( std::make_shared(handle, SPR_UBOX_MSR_PMON_BOX_CTL_ADDR), CounterControlRegs, CounterValueRegs, std::make_shared(handle, SPR_UCLK_FIXED_CTL_ADDR), std::make_shared(handle, SPR_UCLK_FIXED_CTR_ADDR) ) ); } break; case SRF: case GNR: case GNR_D: uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ std::make_shared(handle, BHS_UBOX_MSR_PMON_CTL0_ADDR), std::make_shared(handle, BHS_UBOX_MSR_PMON_CTL1_ADDR) }, CounterValueRegs{ std::make_shared(handle, BHS_UBOX_MSR_PMON_CTR0_ADDR), std::make_shared(handle, BHS_UBOX_MSR_PMON_CTR1_ADDR), }; uncorePMUs[s][0][UBOX_PMU_ID].push_back( std::make_shared( std::make_shared(handle, BHS_UBOX_MSR_PMON_BOX_CTL_ADDR), CounterControlRegs, CounterValueRegs, std::make_shared(handle, BHS_UCLK_FIXED_CTL_ADDR), std::make_shared(handle, BHS_UCLK_FIXED_CTR_ADDR) ) ); } break; case GRR: uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ std::make_shared(handle, GRR_UBOX_MSR_PMON_CTL0_ADDR), std::make_shared(handle, GRR_UBOX_MSR_PMON_CTL1_ADDR) }, CounterValueRegs{ std::make_shared(handle, GRR_UBOX_MSR_PMON_CTR0_ADDR), std::make_shared(handle, GRR_UBOX_MSR_PMON_CTR1_ADDR) }; uncorePMUs[s][0][UBOX_PMU_ID].push_back( std::make_shared( std::make_shared(handle, GRR_UBOX_MSR_PMON_BOX_CTL_ADDR), CounterControlRegs, CounterValueRegs, std::make_shared(handle, GRR_UCLK_FIXED_CTL_ADDR), std::make_shared(handle, GRR_UCLK_FIXED_CTR_ADDR) ) ); } break; default: if (isServerCPU() && hasPCICFGUncore()) { uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ std::make_shared(handle, UBOX_MSR_PMON_CTL0_ADDR), std::make_shared(handle, UBOX_MSR_PMON_CTL1_ADDR), }, CounterValueRegs{ std::make_shared(handle, UBOX_MSR_PMON_CTR0_ADDR), std::make_shared(handle, UBOX_MSR_PMON_CTR1_ADDR), }; uncorePMUs[s][0][UBOX_PMU_ID].push_back( std::make_shared( std::shared_ptr(), CounterControlRegs, CounterValueRegs, std::make_shared(handle, UCLK_FIXED_CTL_ADDR), std::make_shared(handle, UCLK_FIXED_CTR_ADDR) ) ); } } } auto addPMUsFromDiscoveryRef = [this, &handle, &s](std::vector& out, const unsigned int pmuType, const int filter0 = -1) { if (uncorePMUDiscovery.get()) { for (size_t box = 0; box < uncorePMUDiscovery->getNumBoxes(pmuType, s); ++box) { if (uncorePMUDiscovery->getBoxAccessType(pmuType, s, box) == UncorePMUDiscovery::accessTypeEnum::MSR && uncorePMUDiscovery->getBoxNumRegs(pmuType, s, box) >= 4) { out.push_back( std::make_shared( std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(pmuType, s, box)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(pmuType, s, box, 0)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(pmuType, s, box, 1)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(pmuType, s, box, 2)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(pmuType, s, box, 3)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtrAddr(pmuType, s, box, 0)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtrAddr(pmuType, s, box, 1)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtrAddr(pmuType, s, box, 2)), std::make_shared(handle, uncorePMUDiscovery->getBoxCtrAddr(pmuType, s, box, 3)), std::shared_ptr(), std::shared_ptr(), (filter0 < 0) ? std::shared_ptr() : std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(pmuType, s, box) + filter0) // filters not supported by discovery ) ); } } } }; switch (cpu_family_model) { case IVYTOWN: case JAKETOWN: uncorePMUs[s].resize(1); uncorePMUs[s][0][PCU_PMU_ID].push_back( std::make_shared( std::make_shared(handle, JKTIVT_PCU_MSR_PMON_BOX_CTL_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTL0_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTL1_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTL2_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTL3_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTR0_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTR1_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTR2_ADDR), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_CTR3_ADDR), std::shared_ptr(), std::shared_ptr(), std::make_shared(handle, JKTIVT_PCU_MSR_PMON_BOX_FILTER_ADDR) ) ); break; case BDX_DE: case BDX: case KNL: case HASWELLX: case SKX: case ICX: uncorePMUs[s].resize(1); uncorePMUs[s][0][PCU_PMU_ID].push_back( std::make_shared( std::make_shared(handle, HSX_PCU_MSR_PMON_BOX_CTL_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTL0_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTL1_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTL2_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTL3_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTR0_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTR1_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTR2_ADDR), std::make_shared(handle, HSX_PCU_MSR_PMON_CTR3_ADDR), std::shared_ptr(), std::shared_ptr(), std::make_shared(handle, HSX_PCU_MSR_PMON_BOX_FILTER_ADDR) ) ); break; case SPR: case EMR: case GNR: case GNR_D: case SRF: uncorePMUs[s].resize(1); addPMUsFromDiscoveryRef(uncorePMUs[s][0][PCU_PMU_ID], SPR_PCU_BOX_TYPE, 0xE); if (uncorePMUs[s][0][PCU_PMU_ID].empty()) { std::cerr << "WARNING: PCU PMU not found\n"; } break; } // add MDF PMUs auto addMDFPMUs = [&](const unsigned int boxType) { uncorePMUs[s].resize(1); addPMUsFromDiscoveryRef(uncorePMUs[s][0][MDF_PMU_ID], boxType); if (uncorePMUs[s][0][MDF_PMU_ID].empty()) { std::cerr << "WARNING: MDF PMU not found\n"; } }; switch (cpu_family_model) { case SPR: case EMR: addMDFPMUs(SPR_MDF_BOX_TYPE); break; case GNR: case GNR_D: case SRF: addMDFPMUs(BHS_MDF_BOX_TYPE); break; } auto addPCICFGPMUsFromDiscoveryRef = [this, &s](std::vector& out, const unsigned int BoxType) { getPCICFGPMUsFromDiscovery(BoxType, s, [&out](const UncorePMU& pmu) { out.push_back(std::make_shared(pmu)); }); }; auto addPCICFGPMUsFallback = [&s](std::vector& out, const std::vector & DIDs, const char * info = nullptr) { if (s == 0) { if (info) { #ifndef PCM_SILENT std::cerr << info; #endif } forAllIntelDevices([&DIDs, &out](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 device_id) { for (const auto & did: DIDs) { if (device_id == did) { auto handle = std::make_shared(group, bus, device, function); const size_t n_regs = 4; std::vector > CounterControlRegs, CounterValueRegs; for (size_t r = 0; r < n_regs; ++r) { CounterControlRegs.push_back(std::make_shared(handle, BHS_PCIE_GEN5_PCI_PMON_CTL0_ADDR + sizeof(uint64)*r)); CounterValueRegs.push_back(std::make_shared(handle, BHS_PCIE_GEN5_PCI_PMON_CTR0_ADDR + sizeof(uint64)*r)); } auto boxCtlRegister = std::make_shared(handle, BHS_PCIE_GEN5_PCI_PMON_BOX_CTL_ADDR); out.push_back(std::make_shared(boxCtlRegister, CounterControlRegs, CounterValueRegs)); } } }); } }; switch (cpu_family_model) { case GNR: case GNR_D: case GRR: case SRF: uncorePMUs[s].resize(1); if (safe_getenv("PCM_NO_PCIE_GEN5_DISCOVERY") == std::string("1")) { addPCICFGPMUsFallback(uncorePMUs[s][0][PCIE_GEN5x16_PMU_ID], { 0x0DB0, 0x0DB1, 0x0DB2, 0x0DB3 }, "Info: PCM_NO_PCIE_GEN5_DISCOVERY=1 is set, detecting PCIE_GEN5 x16 PMUs manually and mapping them to socket 0.\n"); addPCICFGPMUsFallback(uncorePMUs[s][0][PCIE_GEN5x8_PMU_ID], { 0x0DB6, 0x0DB7, 0x0DB8, 0x0DB9 }, "Info: PCM_NO_PCIE_GEN5_DISCOVERY=1 is set, detecting PCIE_GEN5 x8 PMUs manually and mapping them to socket 0.\n"); } else { addPCICFGPMUsFromDiscoveryRef(uncorePMUs[s][0][PCIE_GEN5x16_PMU_ID], BHS_PCIE_GEN5x16_TYPE); addPCICFGPMUsFromDiscoveryRef(uncorePMUs[s][0][PCIE_GEN5x8_PMU_ID], BHS_PCIE_GEN5x8_TYPE); } break; } } // init IIO addresses iioPMUs.resize(num_sockets); switch (cpu_family_model) { case PCM::SKX: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < SKX_IIO_STACK_COUNT; ++unit) { iioPMUs[s][unit] = UncorePMU( std::make_shared(handle, SKX_IIO_CBDMA_UNIT_CTL + SKX_IIO_PM_REG_STEP * unit), std::make_shared(handle, SKX_IIO_CBDMA_CTL0 + SKX_IIO_PM_REG_STEP * unit + 0), std::make_shared(handle, SKX_IIO_CBDMA_CTL0 + SKX_IIO_PM_REG_STEP * unit + 1), std::make_shared(handle, SKX_IIO_CBDMA_CTL0 + SKX_IIO_PM_REG_STEP * unit + 2), std::make_shared(handle, SKX_IIO_CBDMA_CTL0 + SKX_IIO_PM_REG_STEP * unit + 3), std::make_shared(handle, SKX_IIO_CBDMA_CTR0 + SKX_IIO_PM_REG_STEP * unit + 0), std::make_shared(handle, SKX_IIO_CBDMA_CTR0 + SKX_IIO_PM_REG_STEP * unit + 1), std::make_shared(handle, SKX_IIO_CBDMA_CTR0 + SKX_IIO_PM_REG_STEP * unit + 2), std::make_shared(handle, SKX_IIO_CBDMA_CTR0 + SKX_IIO_PM_REG_STEP * unit + 3) ); } } break; case PCM::ICX: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < ICX_IIO_STACK_COUNT; ++unit) { iioPMUs[s][unit] = UncorePMU( std::make_shared(handle, ICX_IIO_UNIT_CTL[unit]), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTL_REG_OFFSET + 0), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTL_REG_OFFSET + 1), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTL_REG_OFFSET + 2), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTL_REG_OFFSET + 3), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTR_REG_OFFSET + 0), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTR_REG_OFFSET + 1), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTR_REG_OFFSET + 2), std::make_shared(handle, ICX_IIO_UNIT_CTL[unit] + ICX_IIO_CTR_REG_OFFSET + 3) ); } } break; case PCM::SNOWRIDGE: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < SNR_IIO_STACK_COUNT; ++unit) { iioPMUs[s][unit] = UncorePMU( std::make_shared(handle, SNR_IIO_CBDMA_UNIT_CTL + SNR_IIO_PM_REG_STEP * unit), std::make_shared(handle, SNR_IIO_CBDMA_CTL0 + SNR_IIO_PM_REG_STEP * unit + 0), std::make_shared(handle, SNR_IIO_CBDMA_CTL0 + SNR_IIO_PM_REG_STEP * unit + 1), std::make_shared(handle, SNR_IIO_CBDMA_CTL0 + SNR_IIO_PM_REG_STEP * unit + 2), std::make_shared(handle, SNR_IIO_CBDMA_CTL0 + SNR_IIO_PM_REG_STEP * unit + 3), std::make_shared(handle, SNR_IIO_CBDMA_CTR0 + SNR_IIO_PM_REG_STEP * unit + 0), std::make_shared(handle, SNR_IIO_CBDMA_CTR0 + SNR_IIO_PM_REG_STEP * unit + 1), std::make_shared(handle, SNR_IIO_CBDMA_CTR0 + SNR_IIO_PM_REG_STEP * unit + 2), std::make_shared(handle, SNR_IIO_CBDMA_CTR0 + SNR_IIO_PM_REG_STEP * unit + 3) ); } } break; case PCM::SPR: case PCM::EMR: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < SPR_M2IOSF_NUM; ++unit) { iioPMUs[s][unit] = UncorePMU( std::make_shared(handle, SPR_M2IOSF_IIO_UNIT_CTL + SPR_M2IOSF_REG_STEP * unit), std::make_shared(handle, SPR_M2IOSF_IIO_CTL0 + SPR_M2IOSF_REG_STEP * unit + 0), std::make_shared(handle, SPR_M2IOSF_IIO_CTL0 + SPR_M2IOSF_REG_STEP * unit + 1), std::make_shared(handle, SPR_M2IOSF_IIO_CTL0 + SPR_M2IOSF_REG_STEP * unit + 2), std::make_shared(handle, SPR_M2IOSF_IIO_CTL0 + SPR_M2IOSF_REG_STEP * unit + 3), std::make_shared(handle, SPR_M2IOSF_IIO_CTR0 + SPR_M2IOSF_REG_STEP * unit + 0), std::make_shared(handle, SPR_M2IOSF_IIO_CTR0 + SPR_M2IOSF_REG_STEP * unit + 1), std::make_shared(handle, SPR_M2IOSF_IIO_CTR0 + SPR_M2IOSF_REG_STEP * unit + 2), std::make_shared(handle, SPR_M2IOSF_IIO_CTR0 + SPR_M2IOSF_REG_STEP * unit + 3) ); } } break; case PCM::GNR: case PCM::GNR_D: case PCM::SRF: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < BHS_M2IOSF_NUM; ++unit) { iioPMUs[s][unit] = UncorePMU( std::make_shared(handle, BHS_M2IOSF_IIO_UNIT_CTL + BHS_M2IOSF_REG_STEP * unit), std::make_shared(handle, BHS_M2IOSF_IIO_CTL0 + BHS_M2IOSF_REG_STEP * unit + 0), std::make_shared(handle, BHS_M2IOSF_IIO_CTL0 + BHS_M2IOSF_REG_STEP * unit + 1), std::make_shared(handle, BHS_M2IOSF_IIO_CTL0 + BHS_M2IOSF_REG_STEP * unit + 2), std::make_shared(handle, BHS_M2IOSF_IIO_CTL0 + BHS_M2IOSF_REG_STEP * unit + 3), std::make_shared(handle, BHS_M2IOSF_IIO_CTR0 + BHS_M2IOSF_REG_STEP * unit + 0), std::make_shared(handle, BHS_M2IOSF_IIO_CTR0 + BHS_M2IOSF_REG_STEP * unit + 1), std::make_shared(handle, BHS_M2IOSF_IIO_CTR0 + BHS_M2IOSF_REG_STEP * unit + 2), std::make_shared(handle, BHS_M2IOSF_IIO_CTR0 + BHS_M2IOSF_REG_STEP * unit + 3) ); } } break; case PCM::GRR: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < GRR_M2IOSF_NUM; ++unit) { iioPMUs[s][unit] = UncorePMU( std::make_shared(handle, GRR_M2IOSF_IIO_UNIT_CTL + GRR_M2IOSF_REG_STEP * unit), std::make_shared(handle, GRR_M2IOSF_IIO_CTL0 + GRR_M2IOSF_REG_STEP * unit + 0), std::make_shared(handle, GRR_M2IOSF_IIO_CTL0 + GRR_M2IOSF_REG_STEP * unit + 1), std::make_shared(handle, GRR_M2IOSF_IIO_CTL0 + GRR_M2IOSF_REG_STEP * unit + 2), std::make_shared(handle, GRR_M2IOSF_IIO_CTL0 + GRR_M2IOSF_REG_STEP * unit + 3), std::make_shared(handle, GRR_M2IOSF_IIO_CTR0 + GRR_M2IOSF_REG_STEP * unit + 0), std::make_shared(handle, GRR_M2IOSF_IIO_CTR0 + GRR_M2IOSF_REG_STEP * unit + 1), std::make_shared(handle, GRR_M2IOSF_IIO_CTR0 + GRR_M2IOSF_REG_STEP * unit + 2), std::make_shared(handle, GRR_M2IOSF_IIO_CTR0 + GRR_M2IOSF_REG_STEP * unit + 3) ); } } break; } //init the IDX accelerator auto createIDXPMU = [](const size_t addr, const size_t mapSize, const size_t numaNode, const size_t socketId) -> IDX_PMU { const auto alignedAddr = addr & ~4095ULL; auto handle = std::make_shared(alignedAddr, mapSize, false); auto pmon_offset = (handle->read64(SPR_IDX_ACCEL_PMON_BASE_OFFSET) & SPR_IDX_ACCEL_PMON_BASE_MASK)*SPR_IDX_ACCEL_PMON_BASE_RATIO; const auto n_regs = SPR_IDX_ACCEL_COUNTER_MAX_NUM; std::vector > CounterControlRegs, CounterValueRegs, CounterFilterWQRegs, CounterFilterENGRegs; std::vector > CounterFilterTCRegs, CounterFilterPGSZRegs, CounterFilterXFERSZRegs; for (size_t r = 0; r < n_regs; ++r) { CounterControlRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_CTL_OFFSET(r) + pmon_offset))); CounterValueRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_CTR_OFFSET(r) + pmon_offset))); CounterFilterWQRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_FILTER_WQ_OFFSET(r) + pmon_offset))); CounterFilterENGRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_FILTER_ENG_OFFSET(r) + pmon_offset))); CounterFilterTCRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_FILTER_TC_OFFSET(r) + pmon_offset))); CounterFilterPGSZRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_FILTER_PGSZ_OFFSET(r) + pmon_offset))); CounterFilterXFERSZRegs.push_back(std::make_shared(handle, (SPR_IDX_PMON_FILTER_XFERSZ_OFFSET(r) + pmon_offset))); } return IDX_PMU( false, numaNode, socketId, std::make_shared(handle, SPR_IDX_PMON_RESET_CTL_OFFSET + pmon_offset), std::make_shared(handle, SPR_IDX_PMON_FREEZE_CTL_OFFSET + pmon_offset), std::make_shared(), CounterControlRegs, CounterValueRegs, CounterFilterWQRegs, CounterFilterENGRegs, CounterFilterTCRegs, CounterFilterPGSZRegs, CounterFilterXFERSZRegs ); }; //init the QAT accelerator auto createQATPMU = [](const size_t numaNode, const size_t socketId, const size_t domain, const size_t bus, const size_t dev, const size_t func) -> IDX_PMU { const auto n_regs = SPR_QAT_ACCEL_COUNTER_MAX_NUM; auto GlobalConfigReg= std::make_shared(domain, bus, dev, func); std::vector > CounterControlRegs, CounterValueRegs, CounterFilterWQRegs, CounterFilterENGRegs; std::vector > CounterFilterTCRegs, CounterFilterPGSZRegs, CounterFilterXFERSZRegs; for (size_t r = 0; r < n_regs; ++r) { auto CounterControlReg= std::make_shared(); CounterControlRegs.push_back(CounterControlReg); CounterValueRegs.push_back(std::make_shared(GlobalConfigReg, CounterControlReg, r)); CounterFilterWQRegs.push_back(std::make_shared()); //dummy CounterFilterENGRegs.push_back(std::make_shared()); //dummy CounterFilterTCRegs.push_back(std::make_shared()); //dummy CounterFilterPGSZRegs.push_back(std::make_shared()); //dummy CounterFilterXFERSZRegs.push_back(std::make_shared()); //dummy } return IDX_PMU( false, numaNode, socketId, std::make_shared(), std::make_shared(), GlobalConfigReg, CounterControlRegs, CounterValueRegs, CounterFilterWQRegs, CounterFilterENGRegs, CounterFilterTCRegs, CounterFilterPGSZRegs, CounterFilterXFERSZRegs ); }; if (supportIDXAccelDev() == true) { static const uint32 IAA_DEV_IDS[] = { 0x0CFE }; static const uint32 DSA_DEV_IDS[] = { 0x0B25 }; static const uint32 QAT_DEV_IDS[] = { 0x4940, 0x4942, 0x4944, 0x4946, 0x578a }; std::vector > socket2IAAbus; std::vector > socket2DSAbus; std::vector > socket2QATbus; std::map rootbusMap; //Enumurate IDX devices by PCIe bus scan initSocket2Bus(socket2IAAbus, SPR_IDX_IAA_REGISTER_DEV_ADDR, SPR_IDX_IAA_REGISTER_FUNC_ADDR, IAA_DEV_IDS, (uint32)sizeof(IAA_DEV_IDS) / sizeof(IAA_DEV_IDS[0])); initSocket2Bus(socket2DSAbus, SPR_IDX_DSA_REGISTER_DEV_ADDR, SPR_IDX_DSA_REGISTER_FUNC_ADDR, DSA_DEV_IDS, (uint32)sizeof(DSA_DEV_IDS) / sizeof(DSA_DEV_IDS[0])); initSocket2Bus(socket2QATbus, SPR_IDX_QAT_REGISTER_DEV_ADDR, SPR_IDX_QAT_REGISTER_FUNC_ADDR, QAT_DEV_IDS, (uint32)sizeof(QAT_DEV_IDS) / sizeof(QAT_DEV_IDS[0])); #ifndef PCM_SILENT std::cerr << "Info: IDX - Detected " << socket2IAAbus.size() << " IAA devices, " << socket2DSAbus.size() << " DSA devices, " << socket2QATbus.size() << " QAT devices. \n"; #endif initRootBusMap(rootbusMap); idxPMUs.resize(IDX_MAX); idxPMUs[IDX_IAA].clear(); if (socket2IAAbus.size()) { std::vector devInfos; getIDXDevBAR(socket2IAAbus, SPR_IDX_IAA_REGISTER_DEV_ADDR, SPR_IDX_IAA_REGISTER_FUNC_ADDR, rootbusMap, devInfos); for (auto & devInfo : devInfos) { idxPMUs[IDX_IAA].push_back(createIDXPMU(devInfo.mem_bar, SPR_IDX_ACCEL_BAR0_SIZE, devInfo.numa_node, devInfo.socket_id)); } } idxPMUs[IDX_DSA].clear(); if (socket2DSAbus.size()) { std::vector devInfos; getIDXDevBAR(socket2DSAbus, SPR_IDX_DSA_REGISTER_DEV_ADDR, SPR_IDX_DSA_REGISTER_FUNC_ADDR, rootbusMap, devInfos); for (auto & devInfo : devInfos) { idxPMUs[IDX_DSA].push_back(createIDXPMU(devInfo.mem_bar, SPR_IDX_ACCEL_BAR0_SIZE, devInfo.numa_node, devInfo.socket_id)); } } idxPMUs[IDX_QAT].clear(); #ifdef __linux__ if (socket2QATbus.size()) { std::vector devInfos; getIDXDevBAR(socket2QATbus, SPR_IDX_QAT_REGISTER_DEV_ADDR, SPR_IDX_QAT_REGISTER_FUNC_ADDR, rootbusMap, devInfos); for (auto & devInfo : devInfos) { std::ostringstream qat_TLMCTL_sysfs_path(std::ostringstream::out); /*parse telemetry follow rule of out of tree driver*/ qat_TLMCTL_sysfs_path << std::string("/sys/bus/pci/devices/") << std::hex << std::setw(4) << std::setfill('0') << devInfo.domain << ":" << std::hex << std::setw(2) << std::setfill('0') << devInfo.bus << ":" << std::hex << std::setw(2) << std::setfill('0') << devInfo.dev << "." << std::hex << devInfo.func << "/telemetry/control"; std::string qatTLMCTLStr = readSysFS(qat_TLMCTL_sysfs_path.str().c_str(), true); if (!qatTLMCTLStr.size()) //check TLM feature available or NOT. { qat_TLMCTL_sysfs_path.str(""); /*parse telemetry follow rule of in tree driver*/ qat_TLMCTL_sysfs_path << std::string("/sys/kernel/debug/qat_4xxx_") << std::hex << std::setw(4) << std::setfill('0') << devInfo.domain << ":" << std::hex << std::setw(2) << std::setfill('0') << devInfo.bus << ":" << std::hex << std::setw(2) << std::setfill('0') << devInfo.dev << "." << std::hex << devInfo.func << "/telemetry/control"; qatTLMCTLStr = readSysFS(qat_TLMCTL_sysfs_path.str().c_str(), true); if(!qatTLMCTLStr.size()){ std::cerr << "Warning: IDX - QAT telemetry feature of B:0x" << std::hex << devInfo.bus << ",D:0x" << devInfo.dev << ",F:0x" << devInfo.func \ << " is NOT available, skipped." << std::dec << std::endl; continue; } } idxPMUs[IDX_QAT].push_back(createQATPMU(devInfo.numa_node, devInfo.socket_id, devInfo.domain , devInfo.bus, devInfo.dev , devInfo.func)); } } #endif } // init IRP PMU int irpStacks = 0; size_t IRP_CTL_REG_OFFSET = 0; size_t IRP_CTR_REG_OFFSET = 0; const uint32* IRP_UNIT_CTL = nullptr; switch (getCPUFamilyModel()) { case SKX: irpStacks = SKX_IIO_STACK_COUNT; IRP_CTL_REG_OFFSET = SKX_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = SKX_IRP_CTR_REG_OFFSET; IRP_UNIT_CTL = SKX_IRP_UNIT_CTL; break; case ICX: irpStacks = ICX_IIO_STACK_COUNT; IRP_CTL_REG_OFFSET = ICX_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = ICX_IRP_CTR_REG_OFFSET; IRP_UNIT_CTL = ICX_IRP_UNIT_CTL; break; case SNOWRIDGE: irpStacks = SNR_IIO_STACK_COUNT; IRP_CTL_REG_OFFSET = SNR_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = SNR_IRP_CTR_REG_OFFSET; IRP_UNIT_CTL = SNR_IRP_UNIT_CTL; break; case SPR: case EMR: irpStacks = SPR_M2IOSF_NUM; IRP_CTL_REG_OFFSET = SPR_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = SPR_IRP_CTR_REG_OFFSET; IRP_UNIT_CTL = SPR_IRP_UNIT_CTL; break; case GNR: case GNR_D: case SRF: irpStacks = BHS_M2IOSF_NUM; IRP_CTL_REG_OFFSET = BHS_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = BHS_IRP_CTR_REG_OFFSET; IRP_UNIT_CTL = BHS_IRP_UNIT_CTL; break; case GRR: irpStacks = GRR_M2IOSF_NUM; IRP_CTL_REG_OFFSET = GRR_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = GRR_IRP_CTR_REG_OFFSET; IRP_UNIT_CTL = GRR_IRP_UNIT_CTL; break; } irpPMUs.resize(num_sockets); if (IRP_UNIT_CTL) { for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto& handle = MSR[socketRefCore[s]]; for (int unit = 0; unit < irpStacks; ++unit) { irpPMUs[s][unit] = UncorePMU( std::make_shared(handle, IRP_UNIT_CTL[unit]), std::make_shared(handle, IRP_UNIT_CTL[unit] + IRP_CTL_REG_OFFSET + 0), std::make_shared(handle, IRP_UNIT_CTL[unit] + IRP_CTL_REG_OFFSET + 1), std::shared_ptr(), std::shared_ptr(), std::make_shared(handle, IRP_UNIT_CTL[unit] + IRP_CTR_REG_OFFSET + 0), std::make_shared(handle, IRP_UNIT_CTL[unit] + IRP_CTR_REG_OFFSET + 1), std::shared_ptr(), std::shared_ptr() ); } } } #if 0 auto findPCICFGPMU = [](const uint32 did, const int s, const uint32 CtlOffset, const std::vector & CounterControlOffsets, const std::vector & CounterValueOffsets) { int found = 0; UncorePMU out; forAllIntelDevices([&](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 device_id) { if (device_id == did) { if (s == found) { auto handle = std::make_shared(group, bus, device, function); const size_t n_regs = 4; std::vector > CounterControlRegs, CounterValueRegs; for (size_t r = 0; r < n_regs; ++r) { CounterControlRegs.push_back(std::make_shared(handle, CounterControlOffsets[r])); CounterValueRegs.push_back(std::make_shared(handle, CounterValueOffsets[r])); } auto boxCtlRegister = std::make_shared(handle, CtlOffset); DBG(2, "socket ", std::hex , s , " device " , device_id , " " , group , ":" , bus , ":" , device , "@" , function , "\n" , std::dec); out = UncorePMU(boxCtlRegister, CounterControlRegs, CounterValueRegs); } ++found; } }); return out; }; for (uint32 s = 0; s < (uint32)num_sockets; ++s) { switch (cpu_family_model) { case BDX: irpPMUs[s][0] = findPCICFGPMU(0x6f39, s, 0xF4, {0xD8, 0xDC, 0xE0, 0xE4}, {0xA0, 0xB0, 0xB8, 0xC0}); iioPMUs[s][0] = findPCICFGPMU(0x6f34, s, 0xF4, {0xD8, 0xDC, 0xE0, 0xE4}, {0xA0, 0xA8, 0xB0, 0xB8}); break; } } #endif if (hasPCICFGUncore() && MSR.size()) { for (uint32 s = 0; s < (uint32)num_sockets; ++s) { uncorePMUs[s].resize(1); auto & handle = MSR[socketRefCore[s]]; for (uint32 cbo = 0; cbo < getMaxNumOfCBoxesInternal(); ++cbo) { assert(CX_MSR_PMON_BOX_CTL(cbo)); const auto filter1MSR = CX_MSR_PMON_BOX_FILTER1(cbo); std::shared_ptr filter1MSRHandle = filter1MSR ? std::make_shared(handle, filter1MSR) : std::shared_ptr(); uncorePMUs[s][0][CBO_PMU_ID].push_back(std::make_shared( std::make_shared(handle, CX_MSR_PMON_BOX_CTL(cbo)), std::make_shared(handle, CX_MSR_PMON_CTLY(cbo, 0)), std::make_shared(handle, CX_MSR_PMON_CTLY(cbo, 1)), std::make_shared(handle, CX_MSR_PMON_CTLY(cbo, 2)), std::make_shared(handle, CX_MSR_PMON_CTLY(cbo, 3)), std::make_shared( std::make_shared(new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[s]], CX_MSR_PMON_CTRY(cbo, 0)), 48, 5555)), std::make_shared( std::make_shared(new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[s]], CX_MSR_PMON_CTRY(cbo, 1)), 48, 5555)), std::make_shared( std::make_shared(new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[s]], CX_MSR_PMON_CTRY(cbo, 2)), 48, 5555)), std::make_shared( std::make_shared(new CounterWidthExtender::MsrHandleCounter(MSR[socketRefCore[s]], CX_MSR_PMON_CTRY(cbo, 3)), 48, 5555)), std::shared_ptr(), std::shared_ptr(), std::make_shared(handle, CX_MSR_PMON_BOX_FILTER(cbo)), filter1MSRHandle ) ); } } } if (1) { cxlPMUs.resize(num_sockets); for (uint32 s = 0; s < (uint32)num_sockets; ++s) { if (uncorePMUDiscovery.get()) { auto createCXLPMU = [this](const uint32 s, const unsigned BoxType, const size_t pos) -> UncorePMU { std::vector > CounterControlRegs, CounterValueRegs; const auto n_regs = uncorePMUDiscovery->getBoxNumRegs(BoxType, s, pos); const auto unitControlAddr = uncorePMUDiscovery->getBoxCtlAddr(BoxType, s, pos); const auto unitControlAddrAligned = unitControlAddr & ~4095ULL; auto handle = std::make_shared(unitControlAddrAligned, CXL_PMON_SIZE, false); for (size_t r = 0; r < n_regs; ++r) { CounterControlRegs.push_back(std::make_shared(handle, uncorePMUDiscovery->getBoxCtlAddr(BoxType, s, pos, r) - unitControlAddrAligned)); CounterValueRegs.push_back(std::make_shared(handle, uncorePMUDiscovery->getBoxCtrAddr(BoxType, s, pos, r) - unitControlAddrAligned)); } return UncorePMU(std::make_shared(handle, unitControlAddr - unitControlAddrAligned), CounterControlRegs, CounterValueRegs); }; switch (getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::GNR_D: case PCM::SRF: { const auto n_units = (std::min)(uncorePMUDiscovery->getNumBoxes(SPR_CXLCM_BOX_TYPE, s), uncorePMUDiscovery->getNumBoxes(SPR_CXLDP_BOX_TYPE, s)); for (size_t pos = 0; pos < n_units; ++pos) { try { cxlPMUs[s].push_back(std::make_pair(createCXLPMU(s, SPR_CXLCM_BOX_TYPE, pos), createCXLPMU(s, SPR_CXLDP_BOX_TYPE, pos))); } catch (const std::exception& e) { std::cerr << "CXL PMU initialization for socket " << s << " at position " << pos << " failed: " << e.what() << std::endl; } } } break; } } } } } #ifdef PCM_USE_PERF std::vector enumeratePerfPMUs(const std::string & type, int max_id); void populatePerfPMUs(unsigned socket_, const std::vector & ids, std::vector & pmus, bool fixed, bool filter0 = false, bool filter1 = false); void populatePerfPMUs(unsigned socket_, const std::vector& ids, std::vector& pmus, bool fixed, bool filter0 = false, bool filter1 = false); std::vector > enumerateIDXPerfPMUs(const std::string & type, int max_id); void populateIDXPerfPMUs(unsigned socket_, const std::vector > & ids, std::vector & pmus); #endif void PCM::initUncorePMUsPerf() { #ifdef PCM_USE_PERF uncorePMUs.resize(num_sockets); iioPMUs.resize(num_sockets); irpPMUs.resize(num_sockets); for (uint32 s = 0; s < (uint32)num_sockets; ++s) { uncorePMUs[s].resize(1); populatePerfPMUs(s, enumeratePerfPMUs("pcu", 100), uncorePMUs[s][0][PCU_PMU_ID], false, true); populatePerfPMUs(s, enumeratePerfPMUs("ubox", 100), uncorePMUs[s][0][UBOX_PMU_ID], true); populatePerfPMUs(s, enumeratePerfPMUs("cbox", 100), uncorePMUs[s][0][CBO_PMU_ID], false, true, true); populatePerfPMUs(s, enumeratePerfPMUs("cha", 200), uncorePMUs[s][0][CBO_PMU_ID], false, true, true); populatePerfPMUs(s, enumeratePerfPMUs("mdf", 200), uncorePMUs[s][0][MDF_PMU_ID], false, true, true); auto populateMapPMUs = [&s](const std::string& type, std::vector > & out) { std::vector PMUVector; populatePerfPMUs(s, enumeratePerfPMUs(type, 100), PMUVector, false); for (size_t i = 0; i < PMUVector.size(); ++i) { out[s][i] = PMUVector[i]; } }; populateMapPMUs("iio", iioPMUs); populateMapPMUs("irp", irpPMUs); } if (supportIDXAccelDev() == true) { idxPMUs.resize(IDX_MAX); idxPMUs[IDX_IAA].clear(); idxPMUs[IDX_DSA].clear(); idxPMUs[IDX_QAT].clear(); //QAT NOT support perf driver mode. populateIDXPerfPMUs(0, enumerateIDXPerfPMUs("iax", 100), idxPMUs[IDX_IAA]); populateIDXPerfPMUs(0, enumerateIDXPerfPMUs("dsa", 100), idxPMUs[IDX_DSA]); #ifndef PCM_SILENT std::cerr << "Info: IDX - Detected " << idxPMUs[IDX_IAA].size() << " IAA devices, " << idxPMUs[IDX_DSA].size() << " DSA devices.\n"; std::cerr << "Warning: IDX - QAT device NOT support perf driver mode.\n"; #endif } #endif } #ifdef __linux__ const char * keepNMIWatchdogEnabledEnvStr = "PCM_KEEP_NMI_WATCHDOG"; bool keepNMIWatchdogEnabled() { static int keep = -1; if (keep < 0) { keep = (safe_getenv(keepNMIWatchdogEnabledEnvStr) == std::string("1")) ? 1 : 0; } return keep == 1; } #define PCM_NMI_WATCHDOG_PATH "/proc/sys/kernel/nmi_watchdog" bool isNMIWatchdogEnabled(const bool silent) { const auto watchdog = readSysFS(PCM_NMI_WATCHDOG_PATH, silent); if (watchdog.length() == 0) { return false; } return (std::atoi(watchdog.c_str()) == 1); } void disableNMIWatchdog(const bool silent) { if (!silent) { std::cerr << " Disabling NMI watchdog since it consumes one hw-PMU counter. To keep NMI watchdog set environment variable " << keepNMIWatchdogEnabledEnvStr << "=1 (this reduces the core metrics set)\n"; } writeSysFS(PCM_NMI_WATCHDOG_PATH, "0"); } void enableNMIWatchdog(const bool silent) { if (!silent) std::cerr << " Re-enabling NMI watchdog.\n"; writeSysFS(PCM_NMI_WATCHDOG_PATH, "1"); } #endif class CoreTaskQueue { std::queue > wQueue; std::mutex m; std::condition_variable condVar; std::thread worker; CoreTaskQueue() = delete; CoreTaskQueue(CoreTaskQueue &) = delete; CoreTaskQueue & operator = (CoreTaskQueue &) = delete; public: CoreTaskQueue(int32 core) : worker([=]() { try { TemporalThreadAffinity tempThreadAffinity(core, false); std::unique_lock lock(m); while (1) { while (wQueue.empty()) { condVar.wait(lock); } while (!wQueue.empty()) { wQueue.front()(); wQueue.pop(); } } } catch (const std::exception & e) { std::cerr << "PCM Error. Exception in CoreTaskQueue worker function: " << e.what() << "\n"; } }) {} void push(std::packaged_task & task) { std::unique_lock lock(m); wQueue.push(std::move(task)); condVar.notify_one(); } }; std::ofstream* PCM::outfile = nullptr; // output file stream std::streambuf* PCM::backup_ofile = nullptr; // backup of original output = cout std::streambuf* PCM::backup_ofile_cerr = nullptr; // backup of original output = cerr #ifdef __linux__ void increaseULimit() { rlimit lim{}; if (getrlimit(RLIMIT_NOFILE, &lim) == 0) { const rlim_t recommendedLimit = 1000000; DBG(2, "file open limit: " , lim.rlim_cur , "," , lim.rlim_max ); if (lim.rlim_cur < recommendedLimit || lim.rlim_max < recommendedLimit) { lim.rlim_cur = lim.rlim_max = recommendedLimit; if (setrlimit(RLIMIT_NOFILE, &lim) != 0) { std::cerr << "PCM Info: setrlimit for file limit " << recommendedLimit << " failed with error " << strerror(errno) << "\n"; } } } else { std::cerr << "PCM Info: getrlimit for file limit failed with error " << strerror(errno) << "\n"; } } #endif PCM::PCM() : cpu_family(-1), cpu_model_private(-1), cpu_family_model(-1), cpu_stepping(-1), cpu_microcode_level(-1), max_cpuid(0), threads_per_core(0), num_cores(0), num_sockets(0), num_phys_cores_per_socket(0), num_online_cores(0), num_online_sockets(0), accel(0), accel_counters_num_max(0), core_gen_counter_num_max(0), core_gen_counter_num_used(0), // 0 means no core gen counters used core_gen_counter_width(0), core_fixed_counter_num_max(0), core_fixed_counter_num_used(0), core_fixed_counter_width(0), uncore_gen_counter_num_max(8), uncore_gen_counter_num_used(0), uncore_gen_counter_width(48), uncore_fixed_counter_num_max(1), uncore_fixed_counter_num_used(0), uncore_fixed_counter_width(48), perfmon_version(0), perfmon_config_anythread(1), nominal_frequency(0), max_qpi_speed(0), L3ScalingFactor(0), pkgThermalSpecPower(-1), pkgMinimumPower(-1), pkgMaximumPower(-1), systemTopology(new SystemRoot(this)), joulesPerEnergyUnit(0), #ifdef __linux__ resctrl(*this), #endif useResctrl(false), disable_JKT_workaround(false), blocked(false), coreCStateMsr(NULL), pkgCStateMsr(NULL), L2CacheHitRatioAvailable(false), L3CacheHitRatioAvailable(false), L3CacheMissesAvailable(false), L2CacheMissesAvailable(false), L2CacheHitsAvailable(false), L3CacheHitsNoSnoopAvailable(false), L3CacheHitsSnoopAvailable(false), L3CacheHitsAvailable(false), forceRTMAbortMode(false), mode(INVALID_MODE), canUsePerf(false), run_state(1), needToRestoreNMIWatchdog(false) { #ifdef __linux__ increaseULimit(); #endif #ifdef _MSC_VER // WARNING: This driver code (msr.sys) is only for testing purposes, not for production use Driver drv(Driver::msrLocalPath()); // drv.stop(); // restart driver (usually not needed) if (!drv.start()) { tcerr << "Cannot access CPU counters\n"; tcerr << "You must have a signed driver at " << drv.driverPath() << " and have administrator rights to run this program\n"; return; } #endif if(!detectModel()) return; if(!checkModel()) return; initCStateSupportTables(); if(!discoverSystemTopology()) return; if(!initMSR()) return; readCoreCounterConfig(true); #ifndef PCM_SILENT printSystemTopology(); #endif if(!detectNominalFrequency()) return; showSpecControlMSRs(); #ifndef PCM_DEBUG_TOPOLOGY if (safe_getenv("PCM_PRINT_TOPOLOGY") == "1") #endif { printDetailedSystemTopology(1); } initEnergyMonitoring(); #ifndef PCM_SILENT std::cerr << "\n"; #endif if (isServerCPU()) { uncorePMUDiscovery = std::make_shared(); } initUncoreObjects(); initRDT(); readCPUMicrocodeLevel(); #ifdef PCM_USE_PERF canUsePerf = true; perfEventHandle.resize(num_cores, std::vector(PERF_MAX_COUNTERS, -1)); std::fill(perfTopDownPos.begin(), perfTopDownPos.end(), 0); #endif for (int32 i = 0; i < num_cores; ++i) { coreTaskQueues.push_back(std::make_shared(i)); } #ifndef PCM_SILENT std::cerr << "\n"; #endif } void PCM::printDetailedSystemTopology(const int detailLevel) { // produce debug output similar to Intel MPI cpuinfo if (true) { std::cerr << "\n===== Processor topology =====\n"; std::cerr << "OS_Processor Thread_Id Core_Id "; if (detailLevel > 0) std::cerr << "Module_Id "; std::cerr << "Tile_Id "; if (detailLevel > 0) std::cerr << "Die_Id Die_Group_Id "; std::cerr << "Package_Id Core_Type Native_CPU_Model\n"; std::map > os_id_by_core, os_id_by_tile, core_id_by_socket; size_t counter = 0; for (auto it = topology.begin(); it != topology.end(); ++it) { std::cerr << std::left << std::setfill(' ') << std::setw(16) << ((it->os_id >= 0) ? it->os_id : counter) << std::setw(16) << it->thread_id << std::setw(16) << it->core_id; if (detailLevel > 0) std::cerr << std::setw(16) << it->module_id; std::cerr << std::setw(16) << it->tile_id; if (detailLevel > 0) std::cerr << std::setw(16) << it->die_id << std::setw(16) << it->die_grp_id; std::cerr << std::setw(16) << it->socket_id << std::setw(16) << it->getCoreTypeStr() << std::setw(16) << it->native_cpu_model << "\n"; if (std::find(core_id_by_socket[it->socket_id].begin(), core_id_by_socket[it->socket_id].end(), it->core_id) == core_id_by_socket[it->socket_id].end()) core_id_by_socket[it->socket_id].push_back(it->core_id); // add socket offset to distinguish cores and tiles from different sockets os_id_by_core[(it->socket_id << 15) + it->core_id].push_back(it->os_id); os_id_by_tile[(it->socket_id << 15) + it->tile_id].push_back(it->os_id); ++counter; } std::cerr << "===== Placement on packages =====\n"; std::cerr << "Package Id. Core Id. Processors\n"; for (auto pkg = core_id_by_socket.begin(); pkg != core_id_by_socket.end(); ++pkg) { auto core_id = pkg->second.begin(); std::cerr << std::left << std::setfill(' ') << std::setw(15) << pkg->first << *core_id; for (++core_id; core_id != pkg->second.end(); ++core_id) { std::cerr << "," << *core_id; } std::cerr << "\n"; } std::cerr << "\n===== Core/Tile sharing =====\n"; std::cerr << "Level Processors\nCore "; for (auto core = os_id_by_core.begin(); core != os_id_by_core.end(); ++core) { auto os_id = core->second.begin(); std::cerr << "(" << *os_id; for (++os_id; os_id != core->second.end(); ++os_id) { std::cerr << "," << *os_id; } std::cerr << ")"; } std::cerr << "\nTile / L2$ "; for (auto core = os_id_by_tile.begin(); core != os_id_by_tile.end(); ++core) { auto os_id = core->second.begin(); std::cerr << "(" << *os_id; for (++os_id; os_id != core->second.end(); ++os_id) { std::cerr << "," << *os_id; } std::cerr << ")"; } std::cerr << "\n"; std::cerr << "\n"; } } void PCM::enableJKTWorkaround(bool enable) { if(disable_JKT_workaround) return; std::cerr << "Using PCM on your system might have a performance impact as per http://software.intel.com/en-us/articles/performance-impact-when-sampling-certain-llc-events-on-snb-ep-with-vtune\n"; std::cerr << "You can avoid the performance impact by using the option --noJKTWA, however the cache metrics might be wrong then.\n"; if(MSR.size()) { for(int32 i = 0; i < num_cores; ++i) { uint64 val64 = 0; MSR[i]->read(0x39C, &val64); if(enable) val64 |= 1ULL; else val64 &= (~1ULL); MSR[i]->write(0x39C, val64); } } for (size_t i = 0; i < (size_t)serverUncorePMUs.size(); ++i) { if(serverUncorePMUs[i].get()) serverUncorePMUs[i]->enableJKTWorkaround(enable); } } void PCM::showSpecControlMSRs() { PCM_CPUID_INFO cpuinfo; pcm_cpuid(7, 0, cpuinfo); if (MSR.size()) { if ((cpuinfo.reg.edx & (1 << 26)) || (cpuinfo.reg.edx & (1 << 27))) { uint64 val64 = 0; MSR[0]->read(MSR_IA32_SPEC_CTRL, &val64); std::cerr << "IBRS enabled in the kernel : " << ((val64 & 1) ? "yes" : "no") << "\n"; std::cerr << "STIBP enabled in the kernel : " << ((val64 & 2) ? "yes" : "no") << "\n"; } if (cpuinfo.reg.edx & (1 << 29)) { uint64 val64 = 0; MSR[0]->read(MSR_IA32_ARCH_CAPABILITIES, &val64); std::cerr << "The processor is not susceptible to Rogue Data Cache Load: " << ((val64 & 1) ? "yes" : "no") << "\n"; std::cerr << "The processor supports enhanced IBRS : " << ((val64 & 2) ? "yes" : "no") << "\n"; } } } bool PCM::isCoreOnline(int32 os_core_id) const { return (topology[os_core_id].os_id != -1) && (topology[os_core_id].core_id != -1) && (topology[os_core_id].socket_id != -1); } bool PCM::isSocketOnline(int32 socket_id) const { return socketRefCore[socket_id] != -1; } bool PCM::isCPUModelSupported(const int model_) { return ( model_ == NEHALEM_EP || model_ == NEHALEM_EX || model_ == WESTMERE_EP || model_ == WESTMERE_EX || isAtom(model_) || model_ == SNOWRIDGE || model_ == ELKHART_LAKE || model_ == JASPER_LAKE || model_ == CLARKDALE || model_ == SANDY_BRIDGE || model_ == JAKETOWN || model_ == IVY_BRIDGE || model_ == HASWELL || model_ == IVYTOWN || model_ == HASWELLX || model_ == BDX_DE || model_ == BDX || model_ == BROADWELL || model_ == KNL || model_ == SKL || model_ == SKL_UY || model_ == KBL || model_ == KBL_1 || model_ == CML || model_ == ICL || model_ == RKL || model_ == TGL || model_ == ADL || model_ == RPL || model_ == MTL || model_ == LNL || model_ == ARL || model_ == SKX || model_ == ICX || model_ == SPR || model_ == EMR || model_ == GNR || model_ == GNR_D || model_ == GRR || model_ == SRF ); } bool PCM::checkModel() { switch (cpu_family_model) { case NEHALEM: cpu_family_model = NEHALEM_EP; break; case ATOM_2: cpu_family_model = ATOM; break; case HASWELL_ULT: case HASWELL_2: cpu_family_model = HASWELL; break; case BROADWELL_XEON_E3: cpu_family_model = BROADWELL; break; case ICX_D: cpu_family_model = ICX; break; case CML_1: cpu_family_model = CML; break; case ARL_1: cpu_family_model = ARL; break; case ICL_1: cpu_family_model = ICL; break; case TGL_1: cpu_family_model = TGL; break; case ADL_1: cpu_family_model = ADL; break; case RPL_1: case RPL_2: case RPL_3: cpu_family_model = RPL; break; } if(!isCPUModelSupported((int)cpu_family_model)) { std::cerr << getUnsupportedMessage() << " CPU family " << cpu_family << " model number " << cpu_model_private << " Brand: \"" << getCPUBrandString().c_str() << "\"\n"; /* FOR TESTING PURPOSES ONLY */ #ifdef PCM_TEST_FALLBACK_TO_ATOM std::cerr << "Fall back to ATOM functionality.\n"; cpu_family_model = ATOM; return true; #endif return false; } return true; } void PCM::destroyMSR() { MSR.clear(); } PCM::~PCM() { deleteAndNullify(systemTopology); if (instance) { destroyMSR(); instance = NULL; } } bool PCM::good() { return !MSR.empty(); } #ifdef PCM_USE_PERF perf_event_attr PCM_init_perf_event_attr(bool group = true) { perf_event_attr e; bzero(&e,sizeof(perf_event_attr)); e.type = -1; // must be set up later e.size = sizeof(e); e.config = -1; // must be set up later e.sample_period = 0; e.sample_type = 0; e.read_format = group ? PERF_FORMAT_GROUP : 0; /* PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID | PERF_FORMAT_GROUP ; */ e.disabled = 0; e.inherit = 0; e.pinned = 0; e.exclusive = 0; e.exclude_user = 0; e.exclude_kernel = 0; e.exclude_hv = 0; e.exclude_idle = 0; e.mmap = 0; e.comm = 0; e.freq = 0; e.inherit_stat = 0; e.enable_on_exec = 0; e.task = 0; e.watermark = 0; e.wakeup_events = 0; return e; } #endif PCM::ErrorCode PCM::program(const PCM::ProgramMode mode_, const void * parameter_, const bool silent, const int pid) { #ifdef __linux__ if (isNMIWatchdogEnabled(silent) && (keepNMIWatchdogEnabled() == false)) { disableNMIWatchdog(silent); needToRestoreNMIWatchdog = true; } #endif if (MSR.empty()) return PCM::MSRAccessDenied; ExtendedCustomCoreEventDescription * pExtDesc = (ExtendedCustomCoreEventDescription *)parameter_; #ifdef PCM_USE_PERF closePerfHandles(silent); if (!silent) std::cerr << "Trying to use Linux perf events...\n"; const char * no_perf_env = std::getenv("PCM_NO_PERF"); if (no_perf_env != NULL && std::string(no_perf_env) == std::string("1")) { canUsePerf = false; if (!silent) std::cerr << "Usage of Linux perf events is disabled through PCM_NO_PERF environment variable. Using direct PMU programming...\n"; } /* if(num_online_cores < num_cores) { canUsePerf = false; std::cerr << "PCM does not support using Linux perf API on systems with offlined cores. Falling-back to direct PMU programming.\n"; } */ else if(PERF_COUNT_HW_MAX <= PCM_PERF_COUNT_HW_REF_CPU_CYCLES) { canUsePerf = false; if (!silent) std::cerr << "Can not use Linux perf because your Linux kernel does not support PERF_COUNT_HW_REF_CPU_CYCLES event. Falling-back to direct PMU programming.\n"; } else if(EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && pExtDesc->fixedCfg && (pExtDesc->fixedCfg->value & 0x444)) { canUsePerf = false; if (!silent) { std::cerr << "Can not use Linux perf because \"any_thread\" fixed counter configuration requested (0x" << std::hex << pExtDesc->fixedCfg->value << std::dec << ") =\n" << *(pExtDesc->fixedCfg) << "\nFalling-back to direct PMU programming.\n\n"; } } else if(EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && (pExtDesc->OffcoreResponseMsrValue[0] || pExtDesc->OffcoreResponseMsrValue[1])) { const std::string offcore_rsp_format = readSysFS("/sys/bus/event_source/devices/cpu/format/offcore_rsp"); if (offcore_rsp_format != "config1:0-63\n") { canUsePerf = false; if (!silent) std::cerr << "Can not use Linux perf because OffcoreResponse usage is not supported. Falling-back to direct PMU programming.\n"; } } if (isHWTMAL1Supported() == true && perfSupportsTopDown() == false && pid == -1) { canUsePerf = false; if (!silent) std::cerr << "Installed Linux kernel perf does not support hardware top-down level-1 counters. Using direct PMU programming instead.\n"; } if (canUsePerf && (cpu_family_model == ADL || cpu_family_model == RPL || cpu_family_model == MTL || cpu_family_model == LNL || cpu_family_model == ARL )) { canUsePerf = false; if (!silent) std::cerr << "Linux kernel perf rejects an architectural event on your platform. Using direct PMU programming instead.\n"; } if (canUsePerf == false && noMSRMode()) { std::cerr << "ERROR: can not use perf driver and no-MSR mode is enabled\n" ; return PCM::UnknownError; } #endif if (programmed_core_pmu == false) { if((canUsePerf == false) && PMUinUse()) { return PCM::PMUBusy; } } mode = mode_; // copy custom event descriptions if (mode == CUSTOM_CORE_EVENTS) { if (!parameter_) { std::cerr << "PCM Internal Error: data structure for custom event not initialized\n"; return PCM::UnknownError; } CustomCoreEventDescription * pDesc = (CustomCoreEventDescription *)parameter_; coreEventDesc[0] = pDesc[0]; coreEventDesc[1] = pDesc[1]; if (isAtom() == false && cpu_family_model != KNL) { coreEventDesc[2] = pDesc[2]; core_gen_counter_num_used = 3; if (core_gen_counter_num_max > 3) { coreEventDesc[3] = pDesc[3]; core_gen_counter_num_used = 4; } } else core_gen_counter_num_used = 2; } else if (mode != EXT_CUSTOM_CORE_EVENTS) { auto LLCArchEventInit = [](CustomCoreEventDescription * evt) { evt[0].event_number = ARCH_LLC_MISS_EVTNR; evt[0].umask_value = ARCH_LLC_MISS_UMASK; evt[1].event_number = ARCH_LLC_REFERENCE_EVTNR; evt[1].umask_value = ARCH_LLC_REFERENCE_UMASK; }; if (isAtom() || cpu_family_model == KNL) { LLCArchEventInit(coreEventDesc); L2CacheHitRatioAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; core_gen_counter_num_used = 2; } else if (memoryEventErrata()) { LLCArchEventInit(coreEventDesc); L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 2; if (HASWELLX == cpu_family_model || HASWELL == cpu_family_model) { coreEventDesc[BasicCounterState::HSXL2MissPos].event_number = HSX_L2_RQSTS_MISS_EVTNR; coreEventDesc[BasicCounterState::HSXL2MissPos].umask_value = HSX_L2_RQSTS_MISS_UMASK; coreEventDesc[BasicCounterState::HSXL2RefPos].event_number = HSX_L2_RQSTS_REFERENCES_EVTNR; coreEventDesc[BasicCounterState::HSXL2RefPos].umask_value = HSX_L2_RQSTS_REFERENCES_UMASK; L2CacheHitRatioAvailable = true; L2CacheHitsAvailable = true; core_gen_counter_num_used = 4; } } else switch (cpu_family_model) { case ADL: case RPL: case MTL: case LNL: case ARL: LLCArchEventInit(hybridAtomEventDesc); hybridAtomEventDesc[2].event_number = SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR; hybridAtomEventDesc[2].umask_value = SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK; hybridAtomEventDesc[3].event_number = SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR; hybridAtomEventDesc[3].umask_value = SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK; LLCArchEventInit(coreEventDesc); coreEventDesc[2].event_number = SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR; coreEventDesc[2].umask_value = SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK; coreEventDesc[3].event_number = SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK; L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; break; case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: LLCArchEventInit(coreEventDesc); coreEventDesc[2].event_number = SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR; coreEventDesc[2].umask_value = SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK; coreEventDesc[3].event_number = SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK; L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; break; case GRR: case SRF: LLCArchEventInit(coreEventDesc); coreEventDesc[2].event_number = CMT_MEM_LOAD_RETIRED_L2_MISS_EVTNR; coreEventDesc[2].umask_value = CMT_MEM_LOAD_RETIRED_L2_MISS_UMASK; coreEventDesc[3].event_number = CMT_MEM_LOAD_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = CMT_MEM_LOAD_RETIRED_L2_HIT_UMASK; L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; break; PCM_SKL_PATH_CASES case SKX: case ICX: case SPR: case EMR: case GNR: case GNR_D: assert(useSkylakeEvents()); coreEventDesc[0].event_number = SKL_MEM_LOAD_RETIRED_L3_MISS_EVTNR; coreEventDesc[0].umask_value = SKL_MEM_LOAD_RETIRED_L3_MISS_UMASK; coreEventDesc[1].event_number = SKL_MEM_LOAD_RETIRED_L3_HIT_EVTNR; coreEventDesc[1].umask_value = SKL_MEM_LOAD_RETIRED_L3_HIT_UMASK; coreEventDesc[2].event_number = SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR; coreEventDesc[2].umask_value = SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK; coreEventDesc[3].event_number = SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK; if (core_gen_counter_num_max == 2) { L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 2; break; } else if (core_gen_counter_num_max == 3) { L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 3; break; } L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; break; case SANDY_BRIDGE: case JAKETOWN: case IVYTOWN: case IVY_BRIDGE: case HASWELL: case HASWELLX: case BROADWELL: case BDX_DE: case BDX: coreEventDesc[0].event_number = ARCH_LLC_MISS_EVTNR; coreEventDesc[0].umask_value = ARCH_LLC_MISS_UMASK; coreEventDesc[1].event_number = MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_NONE_EVTNR; coreEventDesc[1].umask_value = MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_NONE_UMASK; coreEventDesc[2].event_number = MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_EVTNR; coreEventDesc[2].umask_value = MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_UMASK; coreEventDesc[3].event_number = MEM_LOAD_UOPS_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = MEM_LOAD_UOPS_RETIRED_L2_HIT_UMASK; if (core_gen_counter_num_max == 3) { L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L3CacheHitsNoSnoopAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 3; break; } L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsNoSnoopAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; break; case NEHALEM_EP: case WESTMERE_EP: case CLARKDALE: coreEventDesc[0].event_number = MEM_LOAD_RETIRED_L3_MISS_EVTNR; coreEventDesc[0].umask_value = MEM_LOAD_RETIRED_L3_MISS_UMASK; coreEventDesc[1].event_number = MEM_LOAD_RETIRED_L3_UNSHAREDHIT_EVTNR; coreEventDesc[1].umask_value = MEM_LOAD_RETIRED_L3_UNSHAREDHIT_UMASK; coreEventDesc[2].event_number = MEM_LOAD_RETIRED_L2_HITM_EVTNR; coreEventDesc[2].umask_value = MEM_LOAD_RETIRED_L2_HITM_UMASK; coreEventDesc[3].event_number = MEM_LOAD_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = MEM_LOAD_RETIRED_L2_HIT_UMASK; L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsNoSnoopAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; break; default: assert(!useSkylakeEvents()); coreEventDesc[0].event_number = ARCH_LLC_MISS_EVTNR; coreEventDesc[0].umask_value = ARCH_LLC_MISS_UMASK; coreEventDesc[1].event_number = MEM_LOAD_RETIRED_L3_UNSHAREDHIT_EVTNR; coreEventDesc[1].umask_value = MEM_LOAD_RETIRED_L3_UNSHAREDHIT_UMASK; coreEventDesc[2].event_number = MEM_LOAD_RETIRED_L2_HITM_EVTNR; coreEventDesc[2].umask_value = MEM_LOAD_RETIRED_L2_HITM_UMASK; coreEventDesc[3].event_number = MEM_LOAD_RETIRED_L2_HIT_EVTNR; coreEventDesc[3].umask_value = MEM_LOAD_RETIRED_L2_HIT_UMASK; L2CacheHitRatioAvailable = true; L3CacheHitRatioAvailable = true; L3CacheMissesAvailable = true; L2CacheMissesAvailable = true; L2CacheHitsAvailable = true; L3CacheHitsNoSnoopAvailable = true; L3CacheHitsSnoopAvailable = true; L3CacheHitsAvailable = true; core_gen_counter_num_used = 4; } } core_fixed_counter_num_used = 3; if(EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && (pExtDesc->gpCounterCfg || pExtDesc->gpCounterHybridAtomCfg)) { core_gen_counter_num_used = pExtDesc->nGPCounters; } if(cpu_family_model == JAKETOWN) { bool enableWA = false; for(uint32 i = 0; i< core_gen_counter_num_used; ++i) { if(coreEventDesc[i].event_number == MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_EVTNR) enableWA = true; } enableJKTWorkaround(enableWA); // this has a performance penalty on memory access } if (core_gen_counter_num_used > core_gen_counter_num_max) { std::cerr << "PCM ERROR: Trying to program " << core_gen_counter_num_used << " general purpose counters with only " << core_gen_counter_num_max << " available\n"; return PCM::UnknownError; } if (core_fixed_counter_num_used > core_fixed_counter_num_max) { std::cerr << "PCM ERROR: Trying to program " << core_fixed_counter_num_used << " fixed counters with only " << core_fixed_counter_num_max << " available\n"; return PCM::UnknownError; } if (pid != -1 && canUsePerf == false) { std::cerr << "PCM ERROR: pid monitoring is only supported with Linux perf_event driver\n"; return PCM::UnknownError; } #ifdef __linux__ if (isNMIWatchdogEnabled(silent) && (canUsePerf == false)) { std::cerr << "PCM ERROR: Unsupported mode. NMI watchdog is enabled and Linux perf_event driver is not used\n"; return PCM::UnknownError; } #endif std::vector tids{}; #ifdef PCM_USE_PERF if (pid != -1) { const auto strDir = std::string("/proc/") + std::to_string(pid) + "/task/"; DIR * tidDir = opendir(strDir.c_str()); if (tidDir) { struct dirent * entry{nullptr}; while ((entry = readdir(tidDir)) != nullptr) { assert(entry->d_name); const auto tid = atoi(entry->d_name); if (tid) { tids.push_back(tid); DBG(2, "Detected task " , tids.back()); } } closedir(tidDir); } else { std::cerr << "ERROR: Can't open " << strDir << "\n"; return PCM::UnknownError; } } if (tids.empty() == false) { if (isHWTMAL1Supported()) { if (!silent) std::cerr << "INFO: TMA L1 metrics are not supported in PID collection mode\n"; } if (!silent) std::cerr << "INFO: collecting core metrics for " << tids.size() << " threads in process " << pid << "\n"; PerfEventHandleContainer _1(num_cores, std::vector(PERF_MAX_COUNTERS, -1)); perfEventTaskHandle.resize(tids.size(), _1); } #endif lastProgrammedCustomCounters.clear(); lastProgrammedCustomCounters.resize(num_cores); core_global_ctrl_value = 0ULL; isHWTMAL1Supported(); // ínit value to prevent MT races std::vector > asyncCoreResults; std::vector programmingStatuses(num_cores, PCM::Success); for (int i = 0; i < (int)num_cores; ++i) { if (isCoreOnline(i) == false) continue; std::packaged_task task([this, i, mode_, pExtDesc, &programmingStatuses, &tids]() -> void { TemporalThreadAffinity tempThreadAffinity(i, false); // speedup trick for Linux programmingStatuses[i] = programCoreCounters(i, mode_, pExtDesc, lastProgrammedCustomCounters[i], tids); }); asyncCoreResults.push_back(task.get_future()); coreTaskQueues[i]->push(task); } for (auto& ar : asyncCoreResults) ar.wait(); for (const auto& status : programmingStatuses) { if (status != PCM::Success) { return status; } } programmed_core_pmu = true; if (canUsePerf && !silent) { std::cerr << "Successfully programmed on-core PMU using Linux perf\n"; } if (EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && pExtDesc->defaultUncoreProgramming == false) { return PCM::Success; } if (hasPCICFGUncore()) // program uncore counters { std::vector> qpi_speeds; for (size_t i = 0; i < (size_t)serverUncorePMUs.size(); ++i) { serverUncorePMUs[i]->program(); qpi_speeds.push_back(std::async(std::launch::async, &ServerUncorePMUs::computeQPISpeed, serverUncorePMUs[i].get(), socketRefCore[i], cpu_family_model)); } for (size_t i = 0; i < (size_t)serverUncorePMUs.size(); ++i) { max_qpi_speed = (std::max)(qpi_speeds[i].get(), max_qpi_speed); } programCbo(); } // program uncore counters on old CPU arch else if (cpu_family_model == NEHALEM_EP || cpu_family_model == WESTMERE_EP || cpu_family_model == CLARKDALE) { for (int i = 0; i < (int)num_cores; ++i) { if (isCoreOnline(i) == false) continue; TemporalThreadAffinity tempThreadAffinity(i, false); // speedup trick for Linux programNehalemEPUncore(i); } } else if (hasBecktonUncore()) { for (int i = 0; i < (int)num_cores; ++i) { if (isCoreOnline(i) == false) continue; TemporalThreadAffinity tempThreadAffinity(i, false); // speedup trick for Linux programBecktonUncore(i); } } if (!silent) reportQPISpeed(); return PCM::Success; } void PCM::checkStatus(const PCM::ErrorCode status) { switch (status) { case pcm::PCM::Success: { break; } case pcm::PCM::MSRAccessDenied: throw std::system_error(pcm::PCM::MSRAccessDenied, std::generic_category(), "Access to Intel(r) Performance Counter Monitor has denied (no MSR or PCI CFG space access)."); case pcm::PCM::PMUBusy: throw std::system_error(pcm::PCM::PMUBusy, std::generic_category(), "Access to Intel(r) Performance Counter Monitor has denied (Performance Monitoring Unit" " is occupied by other application). Try to stop the application that uses PMU," " or reset PMU configuration from PCM application itself"); default: throw std::system_error(pcm::PCM::UnknownError, std::generic_category(), "Access to Intel(r) Performance Counter Monitor has denied (Unknown error)."); } } void PCM::checkError(const PCM::ErrorCode code) { try { checkStatus(code); } catch (const std::system_error &e) { switch (e.code().value()) { case PCM::PMUBusy: std::cerr << e.what() << "\n" << "You can try to reset PMU configuration now. Try to reset? (y/n)" << std::endl; char yn; std::cin >> yn; if ('y' == yn) { resetPMU(); std::cerr << "PMU configuration has been reset. Try to rerun the program again." << std::endl; } exit(EXIT_FAILURE); case PCM::MSRAccessDenied: default: std::cerr << e.what() << std::endl; exit(EXIT_FAILURE); } } } std::mutex printErrorMutex; PCM::ErrorCode PCM::programCoreCounters(const int i /* core */, const PCM::ProgramMode mode_, const ExtendedCustomCoreEventDescription * pExtDesc, std::vector & result, const std::vector & tids) { (void) tids; // to silence uused param warning on non Linux OS // program core counters result.clear(); FixedEventControlRegister ctrl_reg; auto initFixedCtrl = [&](const bool & enableCtr3) { if (EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && pExtDesc->fixedCfg) { ctrl_reg = *(pExtDesc->fixedCfg); } else { ctrl_reg.value = 0; ctrl_reg.fields.os0 = 1; ctrl_reg.fields.usr0 = 1; ctrl_reg.fields.os1 = 1; ctrl_reg.fields.usr1 = 1; ctrl_reg.fields.os2 = 1; ctrl_reg.fields.usr2 = 1; if (enableCtr3 && isFixedCounterSupported(3)) { ctrl_reg.fields.os3 = 1; ctrl_reg.fields.usr3 = 1; } } }; #ifdef PCM_USE_PERF int leader_counter = -1; auto programPerfEvent = [this, &leader_counter, &i, &tids](perf_event_attr e, const int eventPos, const std::string & eventName) -> bool { auto programPerfEventHelper = [&i]( PerfEventHandleContainer & perfEventHandle, perf_event_attr & e, const int eventPos, const std::string & eventName, const int leader_counter, const int tid) -> bool { if (i == 0) { DBG(3, "programming event ", std::hex , e.config , std::dec); } if ((perfEventHandle[i][eventPos] = syscall(SYS_perf_event_open, &e, tid, i /* core id */, leader_counter /* group leader */, 0)) <= 0) { std::lock_guard _(printErrorMutex); std::cerr << "Linux Perf: Error when programming " << eventName << ", error: " << strerror(errno) << " with config 0x" << std::hex << e.config << " config1 0x" << e.config1 << std::dec << " for tid " << tid << " leader " << leader_counter << "\n"; if (24 == errno) { std::cerr << PCM_ULIMIT_RECOMMENDATION; } else { std::cerr << "try running with environment variable PCM_NO_PERF=1\n"; } return false; } return true; }; if (tids.empty() == false) { e.inherit = 1; e.exclude_kernel = 1; e.exclude_hv = 1; e.read_format = 0; // 'inherit' does not work for combinations of read format (e.g. PERF_FORMAT_GROUP) auto handleIt = perfEventTaskHandle.begin(); for (const auto & tid: tids) { if (handleIt == perfEventTaskHandle.end()) { break; } if (programPerfEventHelper(*handleIt, e, eventPos, eventName, -1, tid) == false) { return false; } ++handleIt; } return true; } return programPerfEventHelper(perfEventHandle, e, eventPos, eventName, leader_counter, -1); }; if (canUsePerf) { initFixedCtrl(false); perf_event_attr e = PCM_init_perf_event_attr(); e.type = PERF_TYPE_HARDWARE; e.config = PERF_COUNT_HW_INSTRUCTIONS; e.exclude_kernel = 1 - ctrl_reg.fields.os0; e.exclude_hv = e.exclude_kernel; e.exclude_user = 1 - ctrl_reg.fields.usr0; if (programPerfEvent(e, PERF_INST_RETIRED_POS, "INST_RETIRED") == false) { return PCM::UnknownError; } leader_counter = perfEventHandle[i][PERF_INST_RETIRED_POS]; e.config = PERF_COUNT_HW_CPU_CYCLES; e.exclude_kernel = 1 - ctrl_reg.fields.os1; e.exclude_hv = e.exclude_kernel; e.exclude_user = 1 - ctrl_reg.fields.usr1; if (programPerfEvent(e, PERF_CPU_CLK_UNHALTED_THREAD_POS, "CPU_CLK_UNHALTED_THREAD") == false) { return PCM::UnknownError; } e.config = PCM_PERF_COUNT_HW_REF_CPU_CYCLES; e.exclude_kernel = 1 - ctrl_reg.fields.os2; e.exclude_hv = e.exclude_kernel; e.exclude_user = 1 - ctrl_reg.fields.usr2; if (programPerfEvent(e, PERF_CPU_CLK_UNHALTED_REF_POS, "CPU_CLK_UNHALTED_REF") == false) { return PCM::UnknownError; } } else #endif { // disable counters while programming MSR[i]->write(IA32_CR_PERF_GLOBAL_CTRL, 0); MSR[i]->read(IA32_CR_FIXED_CTR_CTRL, &ctrl_reg.value); initFixedCtrl(true); MSR[i]->write(INST_RETIRED_ADDR, 0); MSR[i]->write(CPU_CLK_UNHALTED_THREAD_ADDR, 0); MSR[i]->write(CPU_CLK_UNHALTED_REF_ADDR, 0); MSR[i]->write(IA32_CR_FIXED_CTR_CTRL, ctrl_reg.value); } if (EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc) { if (pExtDesc->OffcoreResponseMsrValue[0]) // still need to do also if perf API is used due to a bug in perf MSR[i]->write(MSR_OFFCORE_RSP0, pExtDesc->OffcoreResponseMsrValue[0]); if (pExtDesc->OffcoreResponseMsrValue[1]) MSR[i]->write(MSR_OFFCORE_RSP1, pExtDesc->OffcoreResponseMsrValue[1]); if (pExtDesc->LoadLatencyMsrValue != ExtendedCustomCoreEventDescription::invalidMsrValue()) { MSR[i]->write(MSR_LOAD_LATENCY, pExtDesc->LoadLatencyMsrValue); } if (pExtDesc->FrontendMsrValue != ExtendedCustomCoreEventDescription::invalidMsrValue()) { MSR[i]->write(MSR_FRONTEND, pExtDesc->FrontendMsrValue); } } auto setEvent = [] (EventSelectRegister & reg, const uint64 event, const uint64 umask) { reg.fields.event_select = event; reg.fields.umask = umask; reg.fields.usr = 1; reg.fields.os = 1; reg.fields.edge = 0; reg.fields.pin_control = 0; reg.fields.apic_int = 0; reg.fields.any_thread = 0; reg.fields.enable = 1; reg.fields.invert = 0; reg.fields.cmask = 0; reg.fields.in_tx = 0; reg.fields.in_txcp = 0; }; EventSelectRegister event_select_reg; uint64 PEBSEnable = 0ULL; for (uint32 j = 0; j < core_gen_counter_num_used; ++j) { if (hybrid == false || (hybrid == true && topology[i].core_type == TopologyEntry::Core)) { if (EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && pExtDesc->gpCounterCfg) { event_select_reg = pExtDesc->gpCounterCfg[j]; event_select_reg.fields.enable = 1; } else { MSR[i]->read(IA32_PERFEVTSEL0_ADDR + j, &event_select_reg.value); // read-only also safe for perf setEvent(event_select_reg, coreEventDesc[j].event_number, coreEventDesc[j].umask_value); } } else if (hybrid == true && topology[i].core_type == TopologyEntry::Atom) { if (EXT_CUSTOM_CORE_EVENTS == mode_ && pExtDesc && pExtDesc->gpCounterHybridAtomCfg) { event_select_reg = pExtDesc->gpCounterHybridAtomCfg[j]; event_select_reg.fields.enable = 1; } else { MSR[i]->read(IA32_PERFEVTSEL0_ADDR + j, &event_select_reg.value); // read-only also safe for perf setEvent(event_select_reg, hybridAtomEventDesc[j].event_number, hybridAtomEventDesc[j].umask_value); } } result.push_back(event_select_reg); if (pExtDesc != nullptr && event_select_reg.fields.event_select == LOAD_LATENCY_EVTNR && event_select_reg.fields.umask == LOAD_LATENCY_UMASK) { PEBSEnable |= (1ULL << j); } #ifdef PCM_USE_PERF if (canUsePerf) { perf_event_attr e = PCM_init_perf_event_attr(); e.type = PERF_TYPE_RAW; e.config = (1ULL << 63ULL) + event_select_reg.value; if (pExtDesc != nullptr) { if (event_select_reg.fields.event_select == getOCREventNr(0, i).first && event_select_reg.fields.umask == getOCREventNr(0, i).second) e.config1 = pExtDesc->OffcoreResponseMsrValue[0]; if (event_select_reg.fields.event_select == getOCREventNr(1, i).first && event_select_reg.fields.umask == getOCREventNr(1, i).second) e.config1 = pExtDesc->OffcoreResponseMsrValue[1]; if (event_select_reg.fields.event_select == LOAD_LATENCY_EVTNR && event_select_reg.fields.umask == LOAD_LATENCY_UMASK) { e.config1 = pExtDesc->LoadLatencyMsrValue; } if (event_select_reg.fields.event_select == FRONTEND_EVTNR && event_select_reg.fields.umask == FRONTEND_UMASK) { e.config1 = pExtDesc->FrontendMsrValue; } } if (programPerfEvent(e, PERF_GEN_EVENT_0_POS + j, std::string("generic event #") + std::to_string(j) + std::string(" on core #") + std::to_string(i)) == false) { return PCM::UnknownError; } } else #endif { MSR[i]->write(IA32_PMC0 + j, 0); MSR[i]->write(IA32_PERFEVTSEL0_ADDR + j, event_select_reg.value); } } if (!canUsePerf) { // start counting, enable all (4 programmable + 3 fixed) counters uint64 value = (1ULL << 0) + (1ULL << 1) + (1ULL << 2) + (1ULL << 3) + (1ULL << 32) + (1ULL << 33) + (1ULL << 34); if (isFixedCounterSupported(3)) { value |= (1ULL << 35); MSR[i]->write(TOPDOWN_SLOTS_ADDR, 0); } if (isHWTMAL1Supported()) { value |= (1ULL << 48); MSR[i]->write(PERF_METRICS_ADDR, 0); } if (isAtom() || cpu_family_model == KNL) // KNL and Atom have 3 fixed + only 2 programmable counters value = (1ULL << 0) + (1ULL << 1) + (1ULL << 32) + (1ULL << 33) + (1ULL << 34); for (uint32 j = 0; j < core_gen_counter_num_used; ++j) { value |= (1ULL << j); // enable all custom counters (if > 4) } if (core_global_ctrl_value) { assert(core_global_ctrl_value == value); } else { core_global_ctrl_value = value; } MSR[i]->write(IA32_PERF_GLOBAL_OVF_CTRL, value); MSR[i]->write(IA32_CR_PERF_GLOBAL_CTRL, value); } #ifdef PCM_USE_PERF else { if (isFixedCounterSupported(3) && isHWTMAL1Supported() && perfSupportsTopDown()) { std::vector > topDownEvents = { std::make_pair(perfSlotsPath, PERF_TOPDOWN_SLOTS_POS), std::make_pair(perfBadSpecPath, PERF_TOPDOWN_BADSPEC_POS), std::make_pair(perfBackEndPath, PERF_TOPDOWN_BACKEND_POS), std::make_pair(perfFrontEndPath, PERF_TOPDOWN_FRONTEND_POS), std::make_pair(perfRetiringPath, PERF_TOPDOWN_RETIRING_POS)}; if (isHWTMAL2Supported()) { topDownEvents.push_back(std::make_pair(perfMemBound, PERF_TOPDOWN_MEM_BOUND_POS)); topDownEvents.push_back(std::make_pair(perfFetchLat, PERF_TOPDOWN_FETCH_LAT_POS)); topDownEvents.push_back(std::make_pair(perfBrMispred, PERF_TOPDOWN_BR_MISPRED_POS)); topDownEvents.push_back(std::make_pair(perfHeavyOps, PERF_TOPDOWN_HEAVY_OPS_POS)); } int readPos = core_fixed_counter_num_used + core_gen_counter_num_used; leader_counter = -1; for (const auto & event : topDownEvents) { uint64 eventSel = 0, umask = 0; const auto eventDesc = readSysFS(event.first); const auto tokens = split(eventDesc, ','); for (const auto & token : tokens) { if (match(token, "event=", &eventSel)) { // found and matched event, wrote value to 'eventSel' } else if (match(token, "umask=", &umask)) { // found and matched umask, wrote value to 'umask' } else { std::lock_guard _(printErrorMutex); std::cerr << "ERROR: unknown token " << token << " in event description \"" << eventDesc << "\" from " << event.first << "\n"; return PCM::UnknownError; } } EventSelectRegister reg; reg.fields.event_select = eventSel; reg.fields.umask = umask; perf_event_attr e = PCM_init_perf_event_attr(); e.type = PERF_TYPE_RAW; e.config = reg.value; DBG(3, "Programming perf event " , std::hex , e.config , std::dec); if (programPerfEvent(e, event.second, std::string("event ") + event.first + " " + eventDesc) == false) { return PCM::UnknownError; } leader_counter = perfEventHandle[i][PERF_TOPDOWN_SLOTS_POS]; perfTopDownPos[event.second] = readPos++; } } } #endif if (PEBSEnable) { cleanupPEBS = true; MSR[i]->write(IA32_PEBS_ENABLE_ADDR, PEBSEnable); } return PCM::Success; } void PCM::reportQPISpeed() const { if (!max_qpi_speed) return; if (hasPCICFGUncore()) { for (size_t i = 0; i < (size_t)serverUncorePMUs.size(); ++i) { std::cerr << "Socket " << i << "\n"; if(serverUncorePMUs[i].get()) serverUncorePMUs[i]->reportQPISpeed(); } } else { std::cerr << "Max " << xPI() << " speed: " << max_qpi_speed / (1e9) << " GBytes/second (" << max_qpi_speed / (1e9*getBytesPerLinkTransfer()) << " GT/second)\n"; } } void PCM::programNehalemEPUncore(int32 core) { #define CPUCNT_INIT_THE_REST_OF_EVTCNT \ unc_event_select_reg.fields.occ_ctr_rst = 1; \ unc_event_select_reg.fields.edge = 0; \ unc_event_select_reg.fields.enable_pmi = 0; \ unc_event_select_reg.fields.enable = 1; \ unc_event_select_reg.fields.invert = 0; \ unc_event_select_reg.fields.cmask = 0; uncore_gen_counter_num_used = 8; UncoreEventSelectRegister unc_event_select_reg; MSR[core]->read(MSR_UNCORE_PERFEVTSEL0_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QMC_WRITES_FULL_ANY_EVTNR; unc_event_select_reg.fields.umask = UNC_QMC_WRITES_FULL_ANY_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL0_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL1_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QMC_NORMAL_READS_ANY_EVTNR; unc_event_select_reg.fields.umask = UNC_QMC_NORMAL_READS_ANY_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL1_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL2_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QHL_REQUESTS_EVTNR; unc_event_select_reg.fields.umask = UNC_QHL_REQUESTS_IOH_READS_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL2_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL3_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QHL_REQUESTS_EVTNR; unc_event_select_reg.fields.umask = UNC_QHL_REQUESTS_IOH_WRITES_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL3_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL4_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QHL_REQUESTS_EVTNR; unc_event_select_reg.fields.umask = UNC_QHL_REQUESTS_REMOTE_READS_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL4_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL5_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QHL_REQUESTS_EVTNR; unc_event_select_reg.fields.umask = UNC_QHL_REQUESTS_REMOTE_WRITES_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL5_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL6_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QHL_REQUESTS_EVTNR; unc_event_select_reg.fields.umask = UNC_QHL_REQUESTS_LOCAL_READS_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL6_ADDR, unc_event_select_reg.value); MSR[core]->read(MSR_UNCORE_PERFEVTSEL7_ADDR, &unc_event_select_reg.value); unc_event_select_reg.fields.event_select = UNC_QHL_REQUESTS_EVTNR; unc_event_select_reg.fields.umask = UNC_QHL_REQUESTS_LOCAL_WRITES_UMASK; CPUCNT_INIT_THE_REST_OF_EVTCNT MSR[core]->write(MSR_UNCORE_PERFEVTSEL7_ADDR, unc_event_select_reg.value); #undef CPUCNT_INIT_THE_REST_OF_EVTCNT // start uncore counting uint64 value = 255 + (1ULL << 32); // enable all counters MSR[core]->write(MSR_UNCORE_PERF_GLOBAL_CTRL_ADDR, value); // synchronise counters MSR[core]->write(MSR_UNCORE_PMC0, 0); MSR[core]->write(MSR_UNCORE_PMC1, 0); MSR[core]->write(MSR_UNCORE_PMC2, 0); MSR[core]->write(MSR_UNCORE_PMC3, 0); MSR[core]->write(MSR_UNCORE_PMC4, 0); MSR[core]->write(MSR_UNCORE_PMC5, 0); MSR[core]->write(MSR_UNCORE_PMC6, 0); MSR[core]->write(MSR_UNCORE_PMC7, 0); } void PCM::programBecktonUncore(int32 core) { // program Beckton uncore if (core == socketRefCore[0]) computeQPISpeedBeckton((int)core); uint64 value = 1 << 29ULL; // reset all counters MSR[core]->write(U_MSR_PMON_GLOBAL_CTL, value); BecktonUncorePMUZDPCTLFVCRegister FVCreg; FVCreg.value = 0; if (cpu_family_model == NEHALEM_EX) { FVCreg.fields.bcmd = 0; // rd_bcmd FVCreg.fields.resp = 0; // ack_resp FVCreg.fields.evnt0 = 5; // bcmd_match FVCreg.fields.evnt1 = 6; // resp_match FVCreg.fields.pbox_init_err = 0; } else { FVCreg.fields_wsm.bcmd = 0; // rd_bcmd FVCreg.fields_wsm.resp = 0; // ack_resp FVCreg.fields_wsm.evnt0 = 5; // bcmd_match FVCreg.fields_wsm.evnt1 = 6; // resp_match FVCreg.fields_wsm.pbox_init_err = 0; } MSR[core]->write(MB0_MSR_PMU_ZDP_CTL_FVC, FVCreg.value); MSR[core]->write(MB1_MSR_PMU_ZDP_CTL_FVC, FVCreg.value); BecktonUncorePMUCNTCTLRegister CNTCTLreg; CNTCTLreg.value = 0; CNTCTLreg.fields.en = 1; CNTCTLreg.fields.pmi_en = 0; CNTCTLreg.fields.count_mode = 0; CNTCTLreg.fields.storage_mode = 0; CNTCTLreg.fields.wrap_mode = 1; CNTCTLreg.fields.flag_mode = 0; CNTCTLreg.fields.inc_sel = 0x0d; // FVC_EV0 MSR[core]->write(MB0_MSR_PMU_CNT_CTL_0, CNTCTLreg.value); MSR[core]->write(MB1_MSR_PMU_CNT_CTL_0, CNTCTLreg.value); CNTCTLreg.fields.inc_sel = 0x0e; // FVC_EV1 MSR[core]->write(MB0_MSR_PMU_CNT_CTL_1, CNTCTLreg.value); MSR[core]->write(MB1_MSR_PMU_CNT_CTL_1, CNTCTLreg.value); value = 1 + ((0x0C) << 1ULL); // enable bit + (event select IMT_INSERTS_WR) MSR[core]->write(BB0_MSR_PERF_CNT_CTL_1, value); MSR[core]->write(BB1_MSR_PERF_CNT_CTL_1, value); MSR[core]->write(MB0_MSR_PERF_GLOBAL_CTL, 3); // enable two counters MSR[core]->write(MB1_MSR_PERF_GLOBAL_CTL, 3); // enable two counters MSR[core]->write(BB0_MSR_PERF_GLOBAL_CTL, 2); // enable second counter MSR[core]->write(BB1_MSR_PERF_GLOBAL_CTL, 2); // enable second counter // program R-Box to monitor QPI traffic // enable counting on all counters on the left side (port 0-3) MSR[core]->write(R_MSR_PMON_GLOBAL_CTL_7_0, 255); // ... on the right side (port 4-7) MSR[core]->write(R_MSR_PMON_GLOBAL_CTL_15_8, 255); // pick the event value = (1 << 7ULL) + (1 << 6ULL) + (1 << 2ULL); // count any (incoming) data responses MSR[core]->write(R_MSR_PORT0_IPERF_CFG0, value); MSR[core]->write(R_MSR_PORT1_IPERF_CFG0, value); MSR[core]->write(R_MSR_PORT4_IPERF_CFG0, value); MSR[core]->write(R_MSR_PORT5_IPERF_CFG0, value); // pick the event value = (1ULL << 30ULL); // count null idle flits sent MSR[core]->write(R_MSR_PORT0_IPERF_CFG1, value); MSR[core]->write(R_MSR_PORT1_IPERF_CFG1, value); MSR[core]->write(R_MSR_PORT4_IPERF_CFG1, value); MSR[core]->write(R_MSR_PORT5_IPERF_CFG1, value); // choose counter 0 to monitor R_MSR_PORT0_IPERF_CFG0 MSR[core]->write(R_MSR_PMON_CTL0, 1 + 2 * (0)); // choose counter 1 to monitor R_MSR_PORT1_IPERF_CFG0 MSR[core]->write(R_MSR_PMON_CTL1, 1 + 2 * (6)); // choose counter 8 to monitor R_MSR_PORT4_IPERF_CFG0 MSR[core]->write(R_MSR_PMON_CTL8, 1 + 2 * (0)); // choose counter 9 to monitor R_MSR_PORT5_IPERF_CFG0 MSR[core]->write(R_MSR_PMON_CTL9, 1 + 2 * (6)); // choose counter 2 to monitor R_MSR_PORT0_IPERF_CFG1 MSR[core]->write(R_MSR_PMON_CTL2, 1 + 2 * (1)); // choose counter 3 to monitor R_MSR_PORT1_IPERF_CFG1 MSR[core]->write(R_MSR_PMON_CTL3, 1 + 2 * (7)); // choose counter 10 to monitor R_MSR_PORT4_IPERF_CFG1 MSR[core]->write(R_MSR_PMON_CTL10, 1 + 2 * (1)); // choose counter 11 to monitor R_MSR_PORT5_IPERF_CFG1 MSR[core]->write(R_MSR_PMON_CTL11, 1 + 2 * (7)); // enable uncore TSC counter (fixed one) MSR[core]->write(W_MSR_PMON_GLOBAL_CTL, 1ULL << 31ULL); MSR[core]->write(W_MSR_PMON_FIXED_CTR_CTL, 1ULL); value = (1 << 28ULL) + 1; // enable all counters MSR[core]->write(U_MSR_PMON_GLOBAL_CTL, value); } uint64 RDTSC(); void PCM::computeNominalFrequency() { const int ref_core = 0; const uint64 before = getInvariantTSC_Fast(ref_core); MySleepMs(100); const uint64 after = getInvariantTSC_Fast(ref_core); nominal_frequency = 10ULL*(after-before); std::cerr << "WARNING: Core nominal frequency has to be estimated\n"; } std::string PCM::getCPUBrandString() { char buffer[sizeof(int)*4*3+1]; PCM_CPUID_INFO * info = (PCM_CPUID_INFO *) buffer; pcm_cpuid(0x80000002, *info); ++info; pcm_cpuid(0x80000003, *info); ++info; pcm_cpuid(0x80000004, *info); buffer[sizeof(int)*4*3] = 0; std::string result(buffer); while(result[0]==' ') result.erase(0,1); std::string::size_type i; while((i = result.find(" ")) != std::string::npos) result.replace(i,2," "); // remove duplicate spaces return result; } std::string PCM::getCPUFamilyModelString() { return getCPUFamilyModelString(cpu_family, cpu_model_private, cpu_stepping); } std::string PCM::getCPUFamilyModelString(const uint32 cpu_family_, const uint32 internal_cpu_model_, const uint32 cpu_stepping_) { char buffer[sizeof(int)*4*3+6]; std::fill(buffer, buffer + sizeof(buffer), 0); std::snprintf(buffer,sizeof(buffer),"GenuineIntel-%d-%2X-%X", cpu_family_, internal_cpu_model_, cpu_stepping_); std::string result(buffer); return result; } void PCM::enableForceRTMAbortMode(const bool silent) { DBG(2, "enableForceRTMAbortMode(): forceRTMAbortMode=" , forceRTMAbortMode ); if (!forceRTMAbortMode) { if (isForceRTMAbortModeAvailable() && (core_gen_counter_num_max < 4)) { for (auto& m : MSR) { const auto res = m->write(MSR_TSX_FORCE_ABORT, 1); if (res != sizeof(uint64)) { std::cerr << "Warning: writing 1 to MSR_TSX_FORCE_ABORT failed with error " << res << " on core " << m->getCoreId() << "\n"; } } readCoreCounterConfig(true); // re-read core_gen_counter_num_max from CPUID if (!silent) std::cerr << "The number of custom counters is now " << core_gen_counter_num_max << "\n"; if (core_gen_counter_num_max < 4) { std::cerr << "PCM Warning: the number of custom counters did not increase (" << core_gen_counter_num_max << ")\n"; } forceRTMAbortMode = true; } } } bool PCM::isForceRTMAbortModeEnabled() const { return forceRTMAbortMode; } void PCM::disableForceRTMAbortMode(const bool silent) { DBG(2, "disableForceRTMAbortMode(): forceRTMAbortMode=" , forceRTMAbortMode ); if (forceRTMAbortMode) { for (auto& m : MSR) { const auto res = m->write(MSR_TSX_FORCE_ABORT, 0); if (res != sizeof(uint64)) { std::cerr << "Warning: writing 0 to MSR_TSX_FORCE_ABORT failed with error " << res << " on core " << m->getCoreId() << "\n"; } } readCoreCounterConfig(true); // re-read core_gen_counter_num_max from CPUID if (!silent) std::cerr << "The number of custom counters is now " << core_gen_counter_num_max << "\n"; if (core_gen_counter_num_max != 3) { std::cerr << "PCM Warning: the number of custom counters is not 3 (" << core_gen_counter_num_max << ")\n"; } forceRTMAbortMode = false; } } bool PCM::isForceRTMAbortModeAvailable() { PCM_CPUID_INFO info; pcm_cpuid(7, 0, info); // leaf 7, subleaf 0 return (info.reg.edx & (0x1 << 13)) ? true : false; } uint64 get_frequency_from_cpuid() // from Pat Fay (Intel) { double speed=0; std::string brand = PCM::getCPUBrandString(); if (brand.length() > std::string::size_type(0)) { std::string::size_type unitsg = brand.find("GHz"); if(unitsg != std::string::npos) { std::string::size_type atsign = brand.rfind(' ', unitsg); if(atsign != std::string::npos) { std::istringstream(brand.substr(atsign)) >> speed; speed *= 1000; } } else { std::string::size_type unitsg = brand.find("MHz"); if(unitsg != std::string::npos) { std::string::size_type atsign = brand.rfind(' ', unitsg); if(atsign != std::string::npos) { std::istringstream(brand.substr(atsign)) >> speed; } } } } return (uint64)(speed * 1000. * 1000.); } std::string PCM::getSupportedUarchCodenames() const { std::ostringstream ostr; for(int32 i=0; i < static_cast(PCM::END_OF_MODEL_LIST) ; ++i) if(isCPUModelSupported((int)i)) ostr << getUArchCodename(i) << ", "; return std::string(ostr.str().substr(0, ostr.str().length() - 2)); } std::string PCM::getUnsupportedMessage() const { std::ostringstream ostr; ostr << "Error: unsupported processor. Only Intel(R) processors are supported (Atom(R) and microarchitecture codename " << getSupportedUarchCodenames() << ")."; return std::string(ostr.str()); } void PCM::computeQPISpeedBeckton(int core_nr) { uint64 startFlits = 0; // reset all counters MSR[core_nr]->write(U_MSR_PMON_GLOBAL_CTL, 1 << 29ULL); // enable counting on all counters on the left side (port 0-3) MSR[core_nr]->write(R_MSR_PMON_GLOBAL_CTL_7_0, 255); // disable on the right side (port 4-7) MSR[core_nr]->write(R_MSR_PMON_GLOBAL_CTL_15_8, 0); // count flits sent MSR[core_nr]->write(R_MSR_PORT0_IPERF_CFG0, 1ULL << 31ULL); // choose counter 0 to monitor R_MSR_PORT0_IPERF_CFG0 MSR[core_nr]->write(R_MSR_PMON_CTL0, 1 + 2 * (0)); // enable all counters MSR[core_nr]->write(U_MSR_PMON_GLOBAL_CTL, (1 << 28ULL) + 1); MSR[core_nr]->read(R_MSR_PMON_CTR0, &startFlits); const uint64 timerGranularity = 1000000ULL; // mks uint64 startTSC = getTickCount(timerGranularity, (uint32) core_nr); uint64 endTSC; do { endTSC = getTickCount(timerGranularity, (uint32) core_nr); } while (endTSC - startTSC < 200000ULL); // spin for 200 ms uint64 endFlits = 0; MSR[core_nr]->read(R_MSR_PMON_CTR0, &endFlits); max_qpi_speed = (endFlits - startFlits) * 8ULL * timerGranularity / (endTSC - startTSC); } uint32 PCM::checkCustomCoreProgramming(std::shared_ptr msr) { const auto core = msr->getCoreId(); if (size_t(core) >= lastProgrammedCustomCounters.size() || canUsePerf) { // checking 'canUsePerf'because corruption detection currently works // only if perf is not used, see https://github.com/opcm/pcm/issues/106 return 0; } uint32 corruptedCountersMask = 0; for (size_t ctr = 0; ctr < lastProgrammedCustomCounters[core].size(); ++ctr) { EventSelectRegister current; if (msr->read(IA32_PERFEVTSEL0_ADDR + ctr, ¤t.value) != sizeof(current.value)) { std::cerr << "PCM Error: can not read MSR 0x" << std::hex << (IA32_PERFEVTSEL0_ADDR + ctr) << " on core " << std::dec << core << "\n"; continue; } if (canUsePerf) { current.fields.apic_int = 0; // perf sets this bit } if (current.value != lastProgrammedCustomCounters[core][ctr].value) { std::cerr << "PCM Error: someone has corrupted custom counter " << ctr << " on core " << core << " expected value " << lastProgrammedCustomCounters[core][ctr].value << " value read " << current.value << "\n"; corruptedCountersMask |= (1<= 4) { MSR[i]->read(MSR_PERF_GLOBAL_INUSE, &value); for (uint32 j = 0; j < core_gen_counter_num_max; ++j) { if (value & (1ULL << j)) { std::cerr << "WARNING: Custom counter " << j << " is in use. MSR_PERF_GLOBAL_INUSE on core " << i << ": 0x" << std::hex << value << std::dec << "\n"; /* Testing MSR_PERF_GLOBAL_INUSE mechanism for a moment. At a later point in time will report BUSY. return true; */ } } } MSR[i]->read(IA32_CR_PERF_GLOBAL_CTRL, &value); DBG(3, "Core " , i , " IA32_CR_PERF_GLOBAL_CTRL is " , std::hex , value , std::dec); EventSelectRegister event_select_reg; event_select_reg.value = 0xFFFFFFFFFFFFFFFF; for (uint32 j = 0; j < core_gen_counter_num_max; ++j) { const auto count = MSR[i]->read(IA32_PERFEVTSEL0_ADDR + j, &event_select_reg.value); if (count && (event_select_reg.fields.event_select != 0 || event_select_reg.fields.apic_int != 0)) { std::cerr << "WARNING: Core " << i <<" IA32_PERFEVTSEL" << j << "_ADDR is not zeroed " << event_select_reg.value << "\n"; if (needToRestoreNMIWatchdog == true && event_select_reg.fields.event_select == 0x3C && event_select_reg.fields.umask == 0) { // NMI watchdog did not clear its event, ignore it continue; } return true; } } FixedEventControlRegister ctrl_reg; ctrl_reg.value = 0xffffffffffffffff; const auto count = MSR[i]->read(IA32_CR_FIXED_CTR_CTRL, &ctrl_reg.value); // Check if someone has installed pmi handler on counter overflow. // If so, that agent might potentially need to change counter value // for the "sample after"-mode messing up PCM measurements if (count && (ctrl_reg.fields.enable_pmi0 || ctrl_reg.fields.enable_pmi1 || ctrl_reg.fields.enable_pmi2)) { std::cerr << "WARNING: Core " << i << " fixed ctrl:" << ctrl_reg.value << "\n"; if (needToRestoreNMIWatchdog == false) // if NMI watchdog did not clear the fields, ignore it { return true; } } #if 0 // either os=0,usr=0 (not running) or os=1,usr=1 (fits PCM modus) are ok, other combinations are not if(ctrl_reg.fields.os0 != ctrl_reg.fields.usr0 || ctrl_reg.fields.os1 != ctrl_reg.fields.usr1 || ctrl_reg.fields.os2 != ctrl_reg.fields.usr2) { std::cerr << "WARNING: Core " << i << " fixed ctrl:" << ctrl_reg.value << "\n"; return true; } #endif } #ifdef _MSC_VER // try to check if PMU is reserved using MSR driver auto hDriver = openMSRDriver(); if (hDriver != INVALID_HANDLE_VALUE) { DWORD reslength = 0; uint64 result = 0; BOOL status = DeviceIoControl(hDriver, IO_CTL_PMU_ALLOC_SUPPORT, NULL, 0, &result, sizeof(uint64), &reslength, NULL); if (status == TRUE && reslength == sizeof(uint64) && result == 1) { status = DeviceIoControl(hDriver, IO_CTL_PMU_ALLOC, NULL, 0, &result, sizeof(uint64), &reslength, NULL); if (status == FALSE) { std::cerr << "PMU can not be allocated with msr.sys driver. Error code is " << ((reslength == sizeof(uint64)) ? std::to_string(result) : "unknown") << " \n"; CloseHandle(hDriver); return true; } else { DBG(1, "Successfully allocated PMU through msr.sys"); } } CloseHandle(hDriver); } #endif return false; } const char * PCM::getUArchCodename(const int32 cpu_family_model_param) const { auto cpu_family_model_ = cpu_family_model_param; if(cpu_family_model_ < 0) cpu_family_model_ = this->cpu_family_model; switch(cpu_family_model_) { case CENTERTON: return "Centerton"; case BAYTRAIL: return "Baytrail"; case AVOTON: return "Avoton"; case CHERRYTRAIL: return "Cherrytrail"; case APOLLO_LAKE: return "Apollo Lake"; case GEMINI_LAKE: return "Gemini Lake"; case DENVERTON: return "Denverton"; case SNOWRIDGE: return "Snowridge"; case ELKHART_LAKE: return "Elkhart Lake"; case JASPER_LAKE: return "Jasper Lake"; case NEHALEM_EP: case NEHALEM: return "Nehalem/Nehalem-EP"; case ATOM: return "Atom(tm)"; case CLARKDALE: return "Westmere/Clarkdale"; case WESTMERE_EP: return "Westmere-EP"; case NEHALEM_EX: return "Nehalem-EX"; case WESTMERE_EX: return "Westmere-EX"; case SANDY_BRIDGE: return "Sandy Bridge"; case JAKETOWN: return "Sandy Bridge-EP/Jaketown"; case IVYTOWN: return "Ivy Bridge-EP/EN/EX/Ivytown"; case HASWELLX: return "Haswell-EP/EN/EX"; case BDX_DE: return "Broadwell-DE"; case BDX: return "Broadwell-EP/EX"; case KNL: return "Knights Landing"; case IVY_BRIDGE: return "Ivy Bridge"; case HASWELL: return "Haswell"; case BROADWELL: return "Broadwell"; case SKL: return "Skylake"; case SKL_UY: return "Skylake U/Y"; case KBL: return "Kabylake"; case KBL_1: return "Kabylake/Whiskey Lake"; case CML: return "Comet Lake"; case ICL: return "Icelake"; case RKL: return "Rocket Lake"; case TGL: return "Tiger Lake"; case ADL: return "Alder Lake"; case RPL: return "Raptor Lake"; case MTL: return "Meteor Lake"; case LNL: return "Lunar Lake"; case ARL: return "Arrow Lake"; case SKX: if (cpu_family_model_param >= 0) { // query for specified cpu_family_model_param, stepping not provided return "Skylake-SP, Cascade Lake-SP"; } if (isCLX()) { return "Cascade Lake-SP"; } if (isCPX()) { return "Cooper Lake"; } return "Skylake-SP"; case ICX: return "Icelake-SP"; case SPR: return "Sapphire Rapids-SP"; case EMR: return "Emerald Rapids-SP"; case GNR: return "Granite Rapids-SP"; case GNR_D: return "Granite Rapids-D"; case GRR: return "Grand Ridge"; case SRF: return "Sierra Forest"; } return "unknown"; } #ifdef PCM_USE_PERF void PCM::closePerfHandles(const bool silent) { if (canUsePerf) { auto cleanOne = [this](PerfEventHandleContainer & cont) { for (int i = 0; i < num_cores; ++i) { for(int c = 0; c < PERF_MAX_COUNTERS; ++c) { auto & h = cont[i][c]; if (h != -1) ::close(h); h = -1; } } }; cleanOne(perfEventHandle); for (auto & cont : perfEventTaskHandle) { cleanOne(cont); } perfEventTaskHandle.clear(); if (!silent) std::cerr << " Closed perf event handles\n"; } } #endif void PCM::cleanupPMU(const bool silent) { programmed_core_pmu = false; #ifdef PCM_USE_PERF closePerfHandles(silent); if (canUsePerf) { return; } #endif // follow the "Performance Monitoring Unit Sharing Guide" by P. Irelan and Sh. Kuo for (int i = 0; i < (int)num_cores; ++i) { // disable generic counters and continue free running counting for fixed counters MSR[i]->write(IA32_CR_PERF_GLOBAL_CTRL, (1ULL << 32) + (1ULL << 33) + (1ULL << 34)); for (uint32 j = 0; j < core_gen_counter_num_max; ++j) { MSR[i]->write(IA32_PERFEVTSEL0_ADDR + j, 0); } if (cleanupPEBS) { MSR[i]->write(IA32_PEBS_ENABLE_ADDR, 0ULL); } } cleanupPEBS = false; if(cpu_family_model == JAKETOWN) enableJKTWorkaround(false); #ifndef PCM_SILENT if (!silent) std::cerr << " Zeroed PMU registers\n"; #endif } #ifdef PCM_SILENT #pragma GCC diagnostic ignored "-Wunused-parameter" #endif void PCM::cleanupUncorePMUs(const bool silent) { for (auto & sPMUs : iioPMUs) { for (auto & pmu : sPMUs) { pmu.second.cleanup(); } } for (auto & sPMUs : idxPMUs) { for (auto & pmu : sPMUs) { pmu.cleanup(); } } for (auto& sPMUs : irpPMUs) { for (auto& pmu : sPMUs) { pmu.second.cleanup(); } } forAllUncorePMUs([](UncorePMU & p) { p.cleanup(); }); for (auto& sPMUs : cxlPMUs) { for (auto& pmus : sPMUs) { pmus.first.cleanup(); pmus.second.cleanup(); } } for (auto & uncore : serverUncorePMUs) { uncore->cleanupPMUs(); } #ifndef PCM_SILENT if (!silent) std::cerr << " Zeroed uncore PMU registers\n"; #endif } void PCM::resetPMU() { for (int i = 0; i < (int)MSR.size(); ++i) { // disable all counters MSR[i]->write(IA32_CR_PERF_GLOBAL_CTRL, 0); for (uint32 j = 0; j < core_gen_counter_num_max; ++j) { MSR[i]->write(IA32_PERFEVTSEL0_ADDR + j, 0); } FixedEventControlRegister ctrl_reg; ctrl_reg.value = 0xffffffffffffffff; MSR[i]->read(IA32_CR_FIXED_CTR_CTRL, &ctrl_reg.value); if ((ctrl_reg.fields.os0 || ctrl_reg.fields.usr0 || ctrl_reg.fields.enable_pmi0 || ctrl_reg.fields.os1 || ctrl_reg.fields.usr1 || ctrl_reg.fields.enable_pmi1 || ctrl_reg.fields.os2 || ctrl_reg.fields.usr2 || ctrl_reg.fields.enable_pmi2) != 0) MSR[i]->write(IA32_CR_FIXED_CTR_CTRL, 0); } #ifndef PCM_SILENT std::cerr << " Zeroed PMU registers\n"; #endif } void PCM::cleanupRDT(const bool silent) { if(!(QOSMetricAvailable() && L3QOSMetricAvailable())) { return; } #ifdef __linux__ if (useResctrl) { resctrl.cleanup(); return; } #endif for(int32 core = 0; core < num_cores; core ++ ) { if(!isCoreOnline(core)) continue; uint64 msr_pqr_assoc = 0 ; uint64 msr_qm_evtsel = 0; int32 rmid = 0; int32 event = 0; //Read 0xC8F MSR for each core MSR[core]->read(IA32_PQR_ASSOC, &msr_pqr_assoc); msr_pqr_assoc &= 0xffffffff00000000ULL; //Write 0xC8F MSR with RMID 0 MSR[core]->write(IA32_PQR_ASSOC,msr_pqr_assoc); msr_qm_evtsel = rmid & ((1ULL<<10)-1ULL) ; msr_qm_evtsel <<= 32 ; msr_qm_evtsel |= event & ((1ULL<<8)-1ULL); //Write Event Id as 0 and RMID 0 to the MSR for each core MSR[core]->write(IA32_QM_EVTSEL,msr_qm_evtsel); } if (!silent) std::cerr << " Freeing up all RMIDs\n"; } void PCM::setOutput(const std::string filename, const bool cerrToo) { const auto pos = filename.find_last_of("/"); if (pos != std::string::npos) { const std::string dir_name = filename.substr(0, pos); struct stat info; if (stat(dir_name.c_str(), &info) != 0) { std::cerr << "Output directory: " << dir_name << " doesn't exist\n"; exit(EXIT_FAILURE); } } outfile = new std::ofstream(filename.c_str()); backup_ofile = std::cout.rdbuf(); std::cout.rdbuf(outfile->rdbuf()); if (cerrToo) { backup_ofile_cerr = std::cerr.rdbuf(); std::cerr.rdbuf(outfile->rdbuf()); } } void PCM::restoreOutput() { // restore cout back to what it was originally if(backup_ofile) std::cout.rdbuf(backup_ofile); if (backup_ofile_cerr) std::cerr.rdbuf(backup_ofile_cerr); // close output file if(outfile) outfile->close(); } void PCM::cleanup(const bool silent) { if (MSR.empty()) return; if (!silent) std::cerr << "Cleaning up\n"; cleanupPMU(silent); disableForceRTMAbortMode(silent); cleanupUncorePMUs(silent); cleanupRDT(silent); #ifdef __linux__ if (needToRestoreNMIWatchdog) { enableNMIWatchdog(silent); needToRestoreNMIWatchdog = false; } #endif #ifdef _MSC_VER // free PMU using MSR driver auto hDriver = openMSRDriver(); if (hDriver != INVALID_HANDLE_VALUE) { DWORD reslength = 0; uint64 result = 0; BOOL status = DeviceIoControl(hDriver, IO_CTL_PMU_ALLOC_SUPPORT, NULL, 0, &result, sizeof(uint64), &reslength, NULL); if (status == TRUE && reslength == sizeof(uint64) && result == 1) { status = DeviceIoControl(hDriver, IO_CTL_PMU_FREE, NULL, 0, &result, sizeof(uint64), &reslength, NULL); if (status == FALSE) { std::cerr << "PMU can not be freed with msr.sys driver. Error code is " << ((reslength == sizeof(uint64)) ? std::to_string(result) : "unknown") << " \n"; } } CloseHandle(hDriver); } #endif } // hle is only available when cpuid has this: // HLE: CPUID.07H.EBX.HLE [bit 4] = 1 bool PCM::supportsHLE() const { PCM_CPUID_INFO info; pcm_cpuid(7, 0, info); // leaf 7, subleaf 0 return (info.reg.ebx & (0x1 << 4)) ? true : false; } // rtm is only available when cpuid has this: // RTM: CPUID.07H.EBX.RTM [bit 11] = 1 bool PCM::supportsRTM() const { PCM_CPUID_INFO info; pcm_cpuid(7, 0, info); // leaf 7, subleaf 0 return (info.reg.ebx & (0x1 << 11)) ? true : false; } bool PCM::supportsRDTSCP() const { static int supports = -1; if (supports < 0) { PCM_CPUID_INFO info; pcm_cpuid(0x80000001, info); supports = (info.reg.edx & (0x1 << 27)) ? 1 : 0; } return 1 == supports; } #ifdef __APPLE__ int convertUnknownToInt(size_t size, char* value) { if(sizeof(int) == size) { return *(int*)value; } else if(sizeof(long) == size) { return *(long *)value; } else if(sizeof(long long) == size) { return *(long long *)value; } else { // In this case, we don't know what it is so we guess int return *(int *)value; } } #endif uint64 PCM::getTickCount(uint64 multiplier, uint32 core) { return (multiplier * getInvariantTSC_Fast(core)) / getNominalFrequency(); } uint64 PCM::getInvariantTSC_Fast(uint32 core) { if (supportsRDTSCP()) { TemporalThreadAffinity aff(core); return RDTSCP(); } else if (core < MSR.size()) { uint64 cInvariantTSC = 0; MSR[core]->read(IA32_TIME_STAMP_COUNTER, &cInvariantTSC); if (cInvariantTSC) return cInvariantTSC; } std::cerr << "ERROR: cannot read time stamp counter\n"; return 0ULL; } SystemCounterState getSystemCounterState() { PCM * inst = PCM::getInstance(); SystemCounterState result; if (inst) result = inst->getSystemCounterState(); return result; } SocketCounterState getSocketCounterState(uint32 socket) { PCM * inst = PCM::getInstance(); SocketCounterState result; if (inst) result = inst->getSocketCounterState(socket); return result; } CoreCounterState getCoreCounterState(uint32 core) { PCM * inst = PCM::getInstance(); CoreCounterState result; if (inst) result = inst->getCoreCounterState(core); return result; } #ifdef PCM_USE_PERF void PCM::readPerfData(uint32 core, std::vector & outData) { if (perfEventTaskHandle.empty() == false) { std::fill(outData.begin(), outData.end(), 0); for (const auto & handleArray : perfEventTaskHandle) { for (size_t ctr = 0; ctr < PERF_MAX_COUNTERS; ++ctr) { const int fd = handleArray[core][ctr]; if (fd != -1) { uint64 result{0ULL}; const int status = ::read(fd, &result, sizeof(result)); if (status != sizeof(result)) { std::cerr << "PCM Error: failed to read from Linux perf handle " << fd << "\n"; } else { outData[ctr] += result; } } } } return; } auto readPerfDataHelper = [this](const uint32 core, std::vector& outData, const uint32 leader, const uint32 num_counters) { if (perfEventHandle[core][leader] < 0) { std::fill(outData.begin(), outData.end(), 0); return; } uint64 data[1 + PERF_MAX_COUNTERS]; const int32 bytes2read = sizeof(uint64) * (1 + num_counters); assert(num_counters <= PERF_MAX_COUNTERS); int result = ::read(perfEventHandle[core][leader], data, bytes2read); // data layout: nr counters; counter 0, counter 1, counter 2,... if (result != bytes2read) { std::cerr << "Error while reading perf data. Result is " << result << "\n"; std::cerr << "Check if you run other competing Linux perf clients.\n"; } else if (data[0] != num_counters) { std::cerr << "Number of counters read from perf is wrong. Elements read: " << data[0] << "\n"; } else { /* if (core == 0) { std::unique_lock _(instanceCreationMutex); std::cerr << "DEBUG: perf raw: " << std::dec; for (uint32 p=0; p < (1 + num_counters) ; ++p) std::cerr << data[p] << " "; std::cerr << "\n"; } */ // copy all counters, they start from position 1 in data std::copy((data + 1), (data + 1) + data[0], outData.begin()); } }; readPerfDataHelper(core, outData, PERF_GROUP_LEADER_COUNTER, core_fixed_counter_num_used + core_gen_counter_num_used); if (isHWTMAL1Supported() && perfSupportsTopDown()) { std::vector outTopDownData(outData.size(), 0); const auto topdownCtrNum = isHWTMAL2Supported() ? PERF_TOPDOWN_COUNTERS : PERF_TOPDOWN_COUNTERS_L1; readPerfDataHelper(core, outTopDownData, PERF_TOPDOWN_GROUP_LEADER_COUNTER, topdownCtrNum); std::copy(outTopDownData.begin(), outTopDownData.begin() + topdownCtrNum, outData.begin() + core_fixed_counter_num_used + core_gen_counter_num_used); } } #endif void BasicCounterState::readAndAggregateTSC(std::shared_ptr msr) { uint64 cInvariantTSC = 0; PCM * m = PCM::getInstance(); const auto cpu_family_model = m->getCPUFamilyModel(); if (m->isAtom() == false || cpu_family_model == PCM::AVOTON) { cInvariantTSC = m->getInvariantTSC_Fast(msr->getCoreId()); MSRValues[IA32_TIME_STAMP_COUNTER] = cInvariantTSC; } else { #ifdef _MSC_VER cInvariantTSC = ((static_cast(GetTickCount()/1000ULL)))*m->getNominalFrequency(); #else struct timeval tp; gettimeofday(&tp, NULL); cInvariantTSC = (double(tp.tv_sec) + tp.tv_usec / 1000000.)*m->getNominalFrequency(); #endif } InvariantTSC += cInvariantTSC; } void BasicCounterState::readAndAggregate(std::shared_ptr msr) { assert(msr.get()); uint64 cInstRetiredAny = 0, cCpuClkUnhaltedThread = 0, cCpuClkUnhaltedRef = 0; uint64 cL3Occupancy = 0; uint64 cCustomEvents[PERF_MAX_CUSTOM_COUNTERS] = {0ULL, 0ULL, 0ULL, 0ULL, 0ULL, 0ULL, 0ULL, 0ULL }; uint64 cCStateResidency[PCM::MAX_C_STATE + 1]; std::fill(cCStateResidency, cCStateResidency + PCM::MAX_C_STATE + 1, 0); uint64 thermStatus = 0; uint64 cSMICount = 0; uint64 cFrontendBoundSlots = 0; uint64 cBadSpeculationSlots = 0; uint64 cBackendBoundSlots = 0; uint64 cRetiringSlots = 0; uint64 cAllSlotsRaw = 0; uint64 cMemBoundSlots = 0; uint64 cFetchLatSlots = 0; uint64 cBrMispredSlots = 0; uint64 cHeavyOpsSlots = 0; const int32 core_id = msr->getCoreId(); TemporalThreadAffinity tempThreadAffinity(core_id); // speedup trick for Linux PCM * m = PCM::getInstance(); assert(m); const auto core_global_ctrl_value = m->core_global_ctrl_value; const bool freezeUnfreeze = m->canUsePerf == false && core_global_ctrl_value != 0ULL; if (freezeUnfreeze) { msr->write(IA32_CR_PERF_GLOBAL_CTRL, 0ULL); // freeze } const int32 core_gen_counter_num_max = m->getMaxCustomCoreEvents(); uint64 overflows = 0; const auto corruptedCountersMask = m->checkCustomCoreProgramming(msr); // reading core PMU counters #ifdef PCM_USE_PERF if(m->canUsePerf) { std::vector perfData(PERF_MAX_COUNTERS, 0ULL); m->readPerfData(msr->getCoreId(), perfData); cInstRetiredAny = perfData[PCM::PERF_INST_RETIRED_POS]; cCpuClkUnhaltedThread = perfData[PCM::PERF_CPU_CLK_UNHALTED_THREAD_POS]; cCpuClkUnhaltedRef = perfData[PCM::PERF_CPU_CLK_UNHALTED_REF_POS]; for (int i = 0; i < core_gen_counter_num_max; ++i) { cCustomEvents[i] = perfData[PCM::PERF_GEN_EVENT_0_POS + i]; } if (m->isHWTMAL1Supported() && m->perfSupportsTopDown()) { cFrontendBoundSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_FRONTEND_POS]]; cBadSpeculationSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_BADSPEC_POS]]; cBackendBoundSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_BACKEND_POS]]; cRetiringSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_RETIRING_POS]]; cAllSlotsRaw = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_SLOTS_POS]]; if (core_id == 0) { DBG(3, "All: ", cAllSlotsRaw , " FE: " , cFrontendBoundSlots , " BAD-SP: " , cBadSpeculationSlots , " BE: " , cBackendBoundSlots , " RET: " , cRetiringSlots); } if (m->isHWTMAL2Supported()) { cMemBoundSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_MEM_BOUND_POS]]; cFetchLatSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_FETCH_LAT_POS]]; cBrMispredSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_BR_MISPRED_POS]];; cHeavyOpsSlots = perfData[m->perfTopDownPos[PCM::PERF_TOPDOWN_HEAVY_OPS_POS]]; } } } else #endif { { msr->read(IA32_PERF_GLOBAL_STATUS, &overflows); // read overflows DBG(3, "Debug " , core_id , " IA32_PERF_GLOBAL_STATUS: " , overflows); msr->read(INST_RETIRED_ADDR, &cInstRetiredAny); msr->read(CPU_CLK_UNHALTED_THREAD_ADDR, &cCpuClkUnhaltedThread); msr->read(CPU_CLK_UNHALTED_REF_ADDR, &cCpuClkUnhaltedRef); for (int i = 0; i < core_gen_counter_num_max; ++i) { msr->read(IA32_PMC0 + i, &cCustomEvents[i]); } } msr->write(IA32_PERF_GLOBAL_OVF_CTRL, overflows); // clear overflows if (m->isHWTMAL1Supported()) { uint64 perfMetrics = 0, slots = 0; msr->lock(); msr->read(PERF_METRICS_ADDR, &perfMetrics); msr->read(TOPDOWN_SLOTS_ADDR, &slots); msr->write(PERF_METRICS_ADDR, 0); msr->write(TOPDOWN_SLOTS_ADDR, 0); cFrontendBoundSlots = extract_bits(perfMetrics, 16, 23); cBadSpeculationSlots = extract_bits(perfMetrics, 8, 15); cBackendBoundSlots = extract_bits(perfMetrics, 24, 31); cRetiringSlots = extract_bits(perfMetrics, 0, 7); if (m->isHWTMAL2Supported()) { cMemBoundSlots = extract_bits(perfMetrics, 32 + 3*8, 32 + 3*8 + 7); cFetchLatSlots = extract_bits(perfMetrics, 32 + 2*8, 32 + 2*8 + 7); cBrMispredSlots = extract_bits(perfMetrics, 32 + 1*8, 32 + 1*8 + 7); cHeavyOpsSlots = extract_bits(perfMetrics, 32 + 0*8, 32 + 0*8 + 7); } const double total = double(cFrontendBoundSlots + cBadSpeculationSlots + cBackendBoundSlots + cRetiringSlots); if (total != 0) { cFrontendBoundSlots = m->FrontendBoundSlots[core_id] += uint64((double(cFrontendBoundSlots) / total) * double(slots)); cBadSpeculationSlots = m->BadSpeculationSlots[core_id] += uint64((double(cBadSpeculationSlots) / total) * double(slots)); cBackendBoundSlots = m->BackendBoundSlots[core_id] += uint64((double(cBackendBoundSlots) / total) * double(slots)); cRetiringSlots = m->RetiringSlots[core_id] += uint64((double(cRetiringSlots) / total) * double(slots)); if (m->isHWTMAL2Supported()) { cMemBoundSlots = m->MemBoundSlots[core_id] += uint64((double(cMemBoundSlots) / total) * double(slots)); cFetchLatSlots = m->FetchLatSlots[core_id] += uint64((double(cFetchLatSlots) / total) * double(slots)); cBrMispredSlots = m->BrMispredSlots[core_id] += uint64((double(cBrMispredSlots) / total) * double(slots)); cHeavyOpsSlots = m->HeavyOpsSlots[core_id] += uint64((double(cHeavyOpsSlots) / total) * double(slots)); } } cAllSlotsRaw = m->AllSlotsRaw[core_id] += slots; DBG(3, slots , " " , cFrontendBoundSlots , " " , cBadSpeculationSlots , " " , cBackendBoundSlots , " " , cRetiringSlots); msr->unlock(); } } for (int i = 0; i < core_gen_counter_num_max; ++i) { if (corruptedCountersMask & (1<getCoreId() , " " , cInstRetiredAny); if (m->L3CacheOccupancyMetricAvailable() && m->useResctrl == false) { msr->lock(); uint64 event = 1; m->initQOSevent(event, core_id); msr->read(IA32_QM_CTR, &cL3Occupancy); DBG(3, "readAndAggregate reading IA32_QM_CTR " , std::dec , cL3Occupancy , std::dec); msr->unlock(); } m->readAndAggregateMemoryBWCounters(static_cast(core_id), *this); readAndAggregateTSC(msr); // reading core C state counters for (int i = 0; i <= (int)(PCM::MAX_C_STATE); ++i) { if (m->coreCStateMsr && m->coreCStateMsr[i]) { const auto index = m->coreCStateMsr[i]; msr->read(index, &(cCStateResidency[i])); MSRValues[index] = cCStateResidency[i]; } } // reading temperature msr->read(MSR_IA32_THERM_STATUS, &thermStatus); MSRValues[MSR_IA32_THERM_STATUS] = thermStatus; msr->read(MSR_SMI_COUNT, &cSMICount); MSRValues[MSR_SMI_COUNT] = cSMICount; InstRetiredAny += checked_uint64(m->extractCoreFixedCounterValue(cInstRetiredAny), extract_bits(overflows, 32, 32)); CpuClkUnhaltedThread += checked_uint64(m->extractCoreFixedCounterValue(cCpuClkUnhaltedThread), extract_bits(overflows, 33, 33)); CpuClkUnhaltedRef += checked_uint64(m->extractCoreFixedCounterValue(cCpuClkUnhaltedRef), extract_bits(overflows, 34, 34)); for (int i = 0; i < core_gen_counter_num_max; ++i) { Event[i] += checked_uint64(m->extractCoreGenCounterValue(cCustomEvents[i]), extract_bits(overflows, i, i)); } #ifdef __linux__ if (m->useResctrl) { L3Occupancy = m->resctrl.getL3OCC(core_id) / 1024; } else #endif { DBG(3, "Scaling Factor ", m->L3ScalingFactor); cL3Occupancy = m->extractQOSMonitoring(cL3Occupancy); L3Occupancy = (cL3Occupancy==PCM_INVALID_QOS_MONITORING_DATA)? PCM_INVALID_QOS_MONITORING_DATA : (uint64)((double)(cL3Occupancy * m->L3ScalingFactor) / 1024.0); } for(int i=0; i <= int(PCM::MAX_C_STATE);++i) CStateResidency[i] += cCStateResidency[i]; ThermalHeadroom = extractThermalHeadroom(thermStatus); SMICount += cSMICount; FrontendBoundSlots += cFrontendBoundSlots; BadSpeculationSlots += cBadSpeculationSlots; BackendBoundSlots += cBackendBoundSlots; RetiringSlots += cRetiringSlots; AllSlotsRaw += cAllSlotsRaw; MemBoundSlots += cMemBoundSlots; FetchLatSlots += cFetchLatSlots; BrMispredSlots += cBrMispredSlots; HeavyOpsSlots += cHeavyOpsSlots; if (freezeUnfreeze) { msr->write(IA32_CR_PERF_GLOBAL_CTRL, core_global_ctrl_value); // unfreeze } } PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) { uint32 DDRConfig[4] = {0,0,0,0}; if (enable_pmm == false) { //DDR is false if (ICX == cpu_family_model || SPR == cpu_family_model || EMR == cpu_family_model) { DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ occupancy DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ Insert DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert } else { DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM RPQ occupancy DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM RPQ Insert DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert } } else { DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0xe0) + MC_CH_PCI_PMON_CTL_UMASK(1); // PMM RDQ occupancy DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0xe3) + MC_CH_PCI_PMON_CTL_UMASK(0); // PMM RDQ Insert DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0xe4) + MC_CH_PCI_PMON_CTL_UMASK(1); // PMM WPQ Occupancy DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0xe7) + MC_CH_PCI_PMON_CTL_UMASK(0); // PMM WPQ Insert } if (DDRLatencyMetricsAvailable()) { for (size_t i = 0; i < (size_t)serverUncorePMUs.size(); ++i) { serverUncorePMUs[i]->programIMC(DDRConfig); } } return PCM::Success; } PCM::ErrorCode PCM::programServerUncoreMemoryMetrics(const ServerUncoreMemoryMetrics & metrics, int rankA, int rankB) { if (MSR.empty() || serverUncorePMUs.empty()) return PCM::MSRAccessDenied; for (int i = 0; (i < (int)serverUncorePMUs.size()) && MSR.size(); ++i) { serverUncorePMUs[i]->programServerUncoreMemoryMetrics(metrics, rankA, rankB); } programCXLCM(); programCXLDP(); return PCM::Success; } PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_profile, int * freq_bands) { if(MSR.empty() || serverUncorePMUs.empty()) return PCM::MSRAccessDenied; uint32 PCUCntConf[4] = {0,0,0,0}; auto printError = [this](const char * eventCategory) { assert(eventCategory); std::cerr << "ERROR: no " << eventCategory << " events defined for CPU family " << cpu_family << " model " << cpu_model_private << "\n"; }; switch (cpu_family_model) { case SPR: case EMR: case SRF: case GNR: case GNR_D: PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(1); // clock ticks break; default: PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0); // clock ticks } switch(pcu_profile) { case 0: PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0xB); // FREQ_BAND0_CYCLES PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0xC); // FREQ_BAND1_CYCLES PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0xD); // FREQ_BAND2_CYCLES break; case 1: switch (cpu_family_model) { case SPR: case EMR: case SRF: case GNR: case GNR_D: PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x35); // POWER_STATE_OCCUPANCY.C0 PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x36); // POWER_STATE_OCCUPANCY.C3 PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x37); // POWER_STATE_OCCUPANCY.C6 break; default: PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x80) + PCU_MSR_PMON_CTL_OCC_SEL(1); // POWER_STATE_OCCUPANCY.C0 using CLOCKTICKS + 8th-bit PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x80) + PCU_MSR_PMON_CTL_OCC_SEL(2); // POWER_STATE_OCCUPANCY.C3 using CLOCKTICKS + 8th-bit PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x80) + PCU_MSR_PMON_CTL_OCC_SEL(3); // POWER_STATE_OCCUPANCY.C6 using CLOCKTICKS + 8th-bit } break; case 2: PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x09); // PROCHOT_INTERNAL_CYCLES PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x0A); // PROCHOT_EXTERNAL_CYCLES PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x04); // Thermal frequency limit cycles: FREQ_MAX_LIMIT_THERMAL_CYCLES break; case 3: PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x04); // Thermal frequency limit cycles: FREQ_MAX_LIMIT_THERMAL_CYCLES PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x05); // Power frequency limit cycles: FREQ_MAX_POWER_CYCLES PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x07); // Clipped frequency limit cycles: FREQ_MAX_CURRENT_CYCLES (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) break; case 4: // not supported on SKX, ICX, SNOWRIDGE, SPR, EMR PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x06); // OS frequency limit cycles: FREQ_MAX_OS_CYCLES PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x05); // Power frequency limit cycles: FREQ_MAX_POWER_CYCLES PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x07); // Clipped frequency limit cycles: FREQ_MAX_CURRENT_CYCLES (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) break; case 5: if (JAKETOWN == cpu_family_model) { PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0) + PCU_MSR_PMON_CTL_EXTRA_SEL + PCU_MSR_PMON_CTL_EDGE_DET ; // number of frequency transitions PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0) + PCU_MSR_PMON_CTL_EXTRA_SEL ; // cycles spent changing frequency } else if (IVYTOWN == cpu_family_model) { PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x60) + PCU_MSR_PMON_CTL_EDGE_DET ; // number of frequency transitions PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x60) ; // cycles spent changing frequency: FREQ_TRANS_CYCLES } else if ( HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model || SKX == cpu_family_model || ICX == cpu_family_model || SNOWRIDGE == cpu_family_model || SPR == cpu_family_model || EMR == cpu_family_model || SRF == cpu_family_model || GNR == cpu_family_model || GNR_D == cpu_family_model ) { PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x74) + PCU_MSR_PMON_CTL_EDGE_DET ; // number of frequency transitions PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x74) ; // cycles spent changing frequency: FREQ_TRANS_CYCLES if(HASWELLX == cpu_family_model) { PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x79) + PCU_MSR_PMON_CTL_EDGE_DET ; // number of UFS transitions PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0x79) ; // UFS transition cycles } } else { printError("frequency transition"); } break; case 6: if (IVYTOWN == cpu_family_model) { PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x2B) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC2 transitions PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x2D) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC6 transitions } else if ( HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model || SKX == cpu_family_model || ICX == cpu_family_model || SNOWRIDGE == cpu_family_model || SPR == cpu_family_model || EMR == cpu_family_model || SRF == cpu_family_model || GNR == cpu_family_model || GNR_D == cpu_family_model ) { PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0x4E) ; // PC1e residenicies (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x4E) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC1 transitions (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x2B) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC2e transitions PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x2D) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC6 transitions } else { printError("package C-state transition"); } break; case 7: if (HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model) { PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0x7E) ; // UFS_TRANSITIONS_PERF_P_LIMIT PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x7D) ; // UFS_TRANSITIONS_IO_P_LIMIT PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x7A) ; // UFS_TRANSITIONS_UP_RING_TRAFFIC PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x7B) ; // UFS_TRANSITIONS_UP_STALL_CYCLES } else { printError("UFS transition"); } break; case 8: if (HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model) { PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0x7C) ; // UFS_TRANSITIONS_DOWN } else { printError("UFS transition"); } break; default: std::cerr << "ERROR: unsupported PCU profile " << pcu_profile << "\n"; } for (auto& u : serverUncorePMUs) { u->program_power_metrics(mc_profile); } uint64 filter = 0; if (freq_bands == NULL) { filter = PCU_MSR_PMON_BOX_FILTER_BAND_0(10) + // 1000 MHz PCU_MSR_PMON_BOX_FILTER_BAND_1(20) + // 2000 MHz PCU_MSR_PMON_BOX_FILTER_BAND_2(30); // 3000 MHz } else { filter = PCU_MSR_PMON_BOX_FILTER_BAND_0(freq_bands[0]) + PCU_MSR_PMON_BOX_FILTER_BAND_1(freq_bands[1]) + PCU_MSR_PMON_BOX_FILTER_BAND_2(freq_bands[2]); } programPCU(PCUCntConf, filter); return PCM::Success; } void PCM::programPCU(uint32* PCUCntConf, const uint64 filter) { programUncorePMUs(PCU_PMU_ID, [&PCUCntConf, &filter](UncorePMU& pmu) { pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); if (pmu.filter[0].get()) { *pmu.filter[0] = filter; } program(pmu, &PCUCntConf[0], &PCUCntConf[4], UNC_PMON_UNIT_CTL_FRZ_EN); }); } PCM::ErrorCode PCM::program(const RawPMUConfigs& curPMUConfigs_, const bool silent, const int pid) { if (MSR.empty()) return PCM::MSRAccessDenied; threadMSRConfig = RawPMUConfig{}; packageMSRConfig = RawPMUConfig{}; pcicfgConfig = RawPMUConfig{}; mmioConfig = RawPMUConfig{}; pmtConfig = RawPMUConfig{}; RawPMUConfigs curPMUConfigs = curPMUConfigs_; constexpr auto globalRegPos = 0ULL; PCM::ExtendedCustomCoreEventDescription conf; auto updateRegs = [this, &conf](const RawPMUConfig& corePMUConfig, EventSelectRegister* regs) -> bool { if (corePMUConfig.programmable.size() > (size_t)getMaxCustomCoreEvents()) { std::cerr << "ERROR: trying to program " << corePMUConfig.programmable.size() << " core PMU counters, which exceeds the max num possible (" << getMaxCustomCoreEvents() << ").\n"; for (const auto& e : corePMUConfig.programmable) { std::cerr << " Event: " << e.second << "\n"; } return false; } size_t c = 0; for (; c < corePMUConfig.programmable.size() && c < (size_t)getMaxCustomCoreEvents() && c < PERF_MAX_CUSTOM_COUNTERS; ++c) { regs[c].value = corePMUConfig.programmable[c].first[0]; } conf.nGPCounters = (std::max)((uint32)c, conf.nGPCounters); return true; }; FixedEventControlRegister fixedReg; auto setOtherConf = [&conf, &fixedReg #ifdef _MSC_VER , &globalRegPos #endif ](const RawPMUConfig& corePMUConfig) { if ((size_t)globalRegPos < corePMUConfig.programmable.size()) { conf.OffcoreResponseMsrValue[0] = corePMUConfig.programmable[globalRegPos].first[OCR0Pos]; conf.OffcoreResponseMsrValue[1] = corePMUConfig.programmable[globalRegPos].first[OCR1Pos]; conf.LoadLatencyMsrValue = corePMUConfig.programmable[globalRegPos].first[LoadLatencyPos]; conf.FrontendMsrValue = corePMUConfig.programmable[globalRegPos].first[FrontendPos]; } if (corePMUConfig.fixed.empty()) { conf.fixedCfg = NULL; // default } else { fixedReg.value = 0; for (const auto& cfg : corePMUConfig.fixed) { fixedReg.value |= uint64(cfg.first[0]); } conf.fixedCfg = &fixedReg; } }; EventSelectRegister regs[PERF_MAX_CUSTOM_COUNTERS]; EventSelectRegister atomRegs[PERF_MAX_CUSTOM_COUNTERS]; conf.OffcoreResponseMsrValue[0] = 0; conf.OffcoreResponseMsrValue[1] = 0; if (curPMUConfigs.count("core") > 0) { // need to program core PMU first const auto & corePMUConfig = curPMUConfigs["core"]; if (updateRegs(corePMUConfig, regs) == false) { return PCM::UnknownError; } conf.gpCounterCfg = regs; setOtherConf(corePMUConfig); conf.defaultUncoreProgramming = false; curPMUConfigs.erase("core"); if (curPMUConfigs.count("atom")) { const auto & atomPMUConfig = curPMUConfigs["atom"]; if (updateRegs(atomPMUConfig, atomRegs) == false) { return PCM::UnknownError; } conf.gpCounterHybridAtomCfg = atomRegs; curPMUConfigs.erase("atom"); } const auto status = program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf, silent, pid); if (status != PCM::Success) { return status; } } else if (curPMUConfigs.count("atom") > 0) // no core, only atom { const auto& atomPMUConfig = curPMUConfigs["atom"]; if (updateRegs(atomPMUConfig, atomRegs) == false) { return PCM::UnknownError; } conf.gpCounterHybridAtomCfg = atomRegs; setOtherConf(atomPMUConfig); conf.defaultUncoreProgramming = false; curPMUConfigs.erase("atom"); const auto status = program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf, silent, pid); if (status != PCM::Success) { return status; } } for (auto& pmuConfig : curPMUConfigs) { const auto & type = pmuConfig.first; const auto & events = pmuConfig.second; if (events.programmable.empty() && events.fixed.empty()) { continue; } if (events.programmable.size() > ServerUncoreCounterState::maxCounters && isRegisterEvent(type) == false) { std::cerr << "ERROR: trying to program " << events.programmable.size() << " uncore PMU counters, which exceeds the max num possible (" << ServerUncoreCounterState::maxCounters << ")."; return PCM::UnknownError; } uint32 events32[ServerUncoreCounterState::maxCounters] = { 0,0,0,0,0,0,0,0 }; uint64 events64[ServerUncoreCounterState::maxCounters] = { 0,0,0,0,0,0,0,0 }; for (size_t c = 0; c < events.programmable.size() && c < ServerUncoreCounterState::maxCounters; ++c) { events32[c] = (uint32)events.programmable[c].first[0]; events64[c] = events.programmable[c].first[0]; } if (type == "m3upi") { for (auto& uncore : serverUncorePMUs) { uncore->programM3UPI(events32); } } else if (type == "xpi" || type == "upi" || type == "qpi") { for (auto& uncore : serverUncorePMUs) { uncore->programXPI(events32); } } else if (type == "imc") { for (auto& uncore : serverUncorePMUs) { uncore->programIMC(events32); } } else if (type == "ha") { for (auto& uncore : serverUncorePMUs) { uncore->programHA(events32); } } else if (type == "m2m") { for (auto& uncore : serverUncorePMUs) { uncore->programM2M(events64); } } else if (type == "pcu") { uint64 filter = 0; if (globalRegPos < events.programmable.size()) { filter = events.programmable[globalRegPos].first[1]; } programPCU(events32, filter); } else if (type == "ubox") { programUBOX(events64); } else if (type == "cbo" || type == "cha") { uint64 filter0 = 0, filter1 = 0; if (globalRegPos < events.programmable.size()) { filter0 = events.programmable[globalRegPos].first[1]; filter1 = events.programmable[globalRegPos].first[2]; } programCboRaw(events64, filter0, filter1); } else if (type == "mdf") { programMDF(events64); } else if (type == "irp") { programIRPCounters(events64); } else if (type == "iio") { programIIOCounters(events64); } else if (type == "package_msr") { packageMSRConfig = pmuConfig.second; } else if (type == "thread_msr") { threadMSRConfig = pmuConfig.second; } else if (type == "pcicfg") { pcicfgConfig = pmuConfig.second; auto addLocations = [this](const std::vector& configs) { for (const auto& c : configs) { if (PCICFGRegisterLocations.find(c.first) == PCICFGRegisterLocations.end()) { // add locations std::vector locations; const auto deviceID = c.first[PCICFGEventPosition::deviceID]; forAllIntelDevices([&locations, &deviceID, &c](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 device_id) { if (deviceID == device_id && PciHandleType::exists(group, bus, device, function)) { // PciHandleType shared ptr, offset locations.push_back(PCICFGRegisterEncoding{ std::make_shared(group, bus, device, function), (uint32)c.first[PCICFGEventPosition::offset] }); } }); PCICFGRegisterLocations[c.first] = locations; } } }; addLocations(pcicfgConfig.programmable); addLocations(pcicfgConfig.fixed); } else if (type == "tpmi") { tpmiConfig = pmuConfig.second; auto addLocations = [this](const std::vector& configs) { for (const auto& c : configs) { if (TPMIRegisterLocations.find(c.first) == TPMIRegisterLocations.end()) { // add locations std::vector locations; const auto tpmiID = c.first[TPMIEventPosition::ID]; const uint32 offset = (uint32)c.first[TPMIEventPosition::offset]; const auto numInstances = TPMIHandle::getNumInstances(); for (auto instance = 0ULL; instance < numInstances; ++instance) { std::shared_ptr tpmiHandle = std::make_shared(instance, tpmiID, offset); locations.push_back(TPMIRegisterEncoding{ tpmiHandle }); } TPMIRegisterLocations[c.first] = locations; } } }; addLocations(tpmiConfig.programmable); addLocations(tpmiConfig.fixed); } else if (type == "mmio") { mmioConfig = pmuConfig.second; auto addLocations = [this](const std::vector& configs) { for (const auto& c : configs) { if (MMIORegisterLocations.find(c.first) == MMIORegisterLocations.end()) { // add locations std::vector locations; const auto deviceID = c.first[MMIOEventPosition::deviceID]; forAllIntelDevices([&locations, &deviceID, &c](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 device_id) { if (deviceID == device_id && PciHandleType::exists(group, bus, device, function)) { PciHandleType pciHandle(group, bus, device, function); auto computeBarOffset = [&pciHandle](uint64 membarBits) -> size_t { if (membarBits) { const auto destPos = extract_bits(membarBits, 32, 39); const auto numBits = extract_bits(membarBits, 24, 31); const auto srcPos = extract_bits(membarBits, 16, 23); const auto pcicfgOffset = extract_bits(membarBits, 0, 15); uint32 memBarOffset = 0; pciHandle.read32(pcicfgOffset, &memBarOffset); return size_t(extract_bits_32(memBarOffset, srcPos, srcPos + numBits - 1)) << destPos; } return 0; }; size_t memBar = computeBarOffset(c.first[MMIOEventPosition::membar_bits1]) | computeBarOffset(c.first[MMIOEventPosition::membar_bits2]); assert(memBar); const size_t addr = memBar + c.first[MMIOEventPosition::offset]; // MMIORange shared ptr (handle), offset locations.push_back(MMIORegisterEncoding{ std::make_shared(addr & ~4095ULL, 4096), (uint32) (addr & 4095ULL) }); } }); MMIORegisterLocations[c.first] = locations; } } }; addLocations(mmioConfig.programmable); addLocations(mmioConfig.fixed); } else if (type == "pmt") { pmtConfig = pmuConfig.second; auto addLocations = [this](const std::vector& configs) { for (const auto& c : configs) { if (PMTRegisterLocations.find(c.first) == PMTRegisterLocations.end()) { // add locations std::vector locations; const auto UID = c.first[PMTEventPosition::UID]; for (size_t inst = 0; inst < TelemetryArray::numInstances(UID); ++inst) { locations.push_back(std::make_shared(UID, inst)); DBG(3, "PMTRegisterLocations: UID: 0x" , std::hex , UID , " inst: " , std::dec , inst); } PMTRegisterLocations[c.first] = locations; } } }; addLocations(pmtConfig.programmable); addLocations(pmtConfig.fixed); } else if (type == "cxlcm") { programCXLCM(events64); } else if (type == "cxldp") { programCXLDP(events64); } else if (strToUncorePMUID(type) != INVALID_PMU_ID) { const auto pmu_id = strToUncorePMUID(type); programUncorePMUs(pmu_id, [&events64, &events, &pmu_id](UncorePMU& pmu) { uint64 * eventsIter = (uint64 *)events64; if (pmu_id != PCIE_GEN5x16_PMU_ID && pmu_id != PCIE_GEN5x8_PMU_ID) { pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); } PCM::program(pmu, eventsIter, eventsIter + (std::min)(events.programmable.size(), (size_t)ServerUncoreCounterState::maxCounters), UNC_PMON_UNIT_CTL_FRZ_EN); }); } else { std::cerr << "ERROR: unrecognized PMU type \"" << type << "\" when trying to program PMUs.\n"; return PCM::UnknownError; } } return PCM::Success; } void PCM::freezeServerUncoreCounters() { for (int i = 0; (i < (int)serverUncorePMUs.size()) && MSR.size(); ++i) { serverUncorePMUs[i]->freezeCounters(); const auto refCore = socketRefCore[i]; TemporalThreadAffinity tempThreadAffinity(refCore); // speedup trick for Linux forAllUncorePMUs(i, PCU_PMU_ID, [](UncorePMU& pmu) { pmu.freeze(UNC_PMON_UNIT_CTL_FRZ_EN); }); if (IIOEventsAvailable()) { for (auto & pmu : iioPMUs[i]) { pmu.second.freeze(UNC_PMON_UNIT_CTL_RSV); } } if (size_t(i) < irpPMUs.size()) { for (auto& pmu : irpPMUs[i]) { pmu.second.freeze(UNC_PMON_UNIT_CTL_RSV); } } forAllUncorePMUs(i, CBO_PMU_ID, [](UncorePMU& pmu) { pmu.freeze(UNC_PMON_UNIT_CTL_FRZ_EN); }); forAllUncorePMUs(i, MDF_PMU_ID, [](UncorePMU& pmu) { pmu.freeze(UNC_PMON_UNIT_CTL_FRZ_EN); }); } for (auto& sPMUs : cxlPMUs) { for (auto& pmus : sPMUs) { pmus.first.freeze(UNC_PMON_UNIT_CTL_FRZ_EN); pmus.second.freeze(UNC_PMON_UNIT_CTL_FRZ_EN); } } } void PCM::unfreezeServerUncoreCounters() { for (int i = 0; (i < (int)serverUncorePMUs.size()) && MSR.size(); ++i) { serverUncorePMUs[i]->unfreezeCounters(); const auto refCore = socketRefCore[i]; TemporalThreadAffinity tempThreadAffinity(refCore); // speedup trick for Linux forAllUncorePMUs(i, PCU_PMU_ID, [](UncorePMU& pmu) { pmu.unfreeze(UNC_PMON_UNIT_CTL_FRZ_EN); }); if (IIOEventsAvailable()) { for (auto & pmu : iioPMUs[i]) { pmu.second.unfreeze(UNC_PMON_UNIT_CTL_RSV); } } if (size_t(i) < irpPMUs.size()) { for (auto& pmu : irpPMUs[i]) { pmu.second.unfreeze(UNC_PMON_UNIT_CTL_RSV); } } forAllUncorePMUs(i, CBO_PMU_ID, [](UncorePMU& pmu) { pmu.unfreeze(UNC_PMON_UNIT_CTL_FRZ_EN); }); forAllUncorePMUs(i, MDF_PMU_ID, [](UncorePMU& pmu) { pmu.unfreeze(UNC_PMON_UNIT_CTL_FRZ_EN); }); } for (auto& sPMUs : cxlPMUs) { for (auto& pmus : sPMUs) { pmus.first.unfreeze(UNC_PMON_UNIT_CTL_FRZ_EN); pmus.second.unfreeze(UNC_PMON_UNIT_CTL_FRZ_EN); } } } void UncoreCounterState::readAndAggregate(std::shared_ptr msr) { const auto coreID = msr->getCoreId(); TemporalThreadAffinity tempThreadAffinity(coreID); // speedup trick for Linux auto pcm = PCM::getInstance(); pcm->readAndAggregatePackageCStateResidencies(msr, *this); } SystemCounterState PCM::getSystemCounterState() { SystemCounterState result; if (MSR.size()) { // read core and uncore counter state for (int32 core = 0; core < num_cores; ++core) if ( isCoreOnline( core ) ) result.readAndAggregate(MSR[core]); for (uint32 s = 0; s < (uint32)num_sockets; s++) { if ( isSocketOnline( s ) ) { readAndAggregateUncoreMCCounters(s, result); readAndAggregateEnergyCounters(s, result); } } readAndAggregateCXLCMCounters(result); readQPICounters(result); readSystemEnergyStatus(result); result.ThermalHeadroom = static_cast(PCM_INVALID_THERMAL_HEADROOM); // not available for system } return result; } template void PCM::readAndAggregateMemoryBWCounters(const uint32 core, CounterStateType & result) { #ifdef __linux__ if (useResctrl) { if (CoreLocalMemoryBWMetricAvailable()) { result.MemoryBWLocal += resctrl.getMBL(core) / (1024*1024); } if (CoreRemoteMemoryBWMetricAvailable()) { result.MemoryBWTotal += resctrl.getMBT(core) / (1024*1024); } return; } #endif uint64 cMemoryBWLocal = 0; uint64 cMemoryBWTotal = 0; if(core < memory_bw_local.size()) { cMemoryBWLocal = memory_bw_local[core]->read(); cMemoryBWLocal = extractQOSMonitoring(cMemoryBWLocal); DBG(3,"Read MemoryBWLocal ", cMemoryBWLocal ); if(cMemoryBWLocal==PCM_INVALID_QOS_MONITORING_DATA) result.MemoryBWLocal = PCM_INVALID_QOS_MONITORING_DATA; // do not accumulate invalid reading else result.MemoryBWLocal += (uint64)((double)(cMemoryBWLocal * L3ScalingFactor) / (1024.0 * 1024.0)); } if(core < memory_bw_total.size()) { cMemoryBWTotal = memory_bw_total[core]->read(); cMemoryBWTotal = extractQOSMonitoring(cMemoryBWTotal); DBG(3, "Read MemoryBWTotal " , cMemoryBWTotal); if(cMemoryBWTotal==PCM_INVALID_QOS_MONITORING_DATA) result.MemoryBWTotal = PCM_INVALID_QOS_MONITORING_DATA; // do not accumulate invalid reading else result.MemoryBWTotal += (uint64)((double)(cMemoryBWTotal * L3ScalingFactor) / (1024.0 * 1024.0)); } } template void PCM::readAndAggregateCXLCMCounters( CounterStateType & result) { for (size_t socket = 0; socket < getNumSockets(); ++socket) { uint64 CXLWriteMem = 0; uint64 CXLWriteCache = 0; for (size_t p = 0; p < getNumCXLPorts(socket); ++p) { CXLWriteMem += *cxlPMUs[socket][p].first.counterValue[0]; CXLWriteCache += *cxlPMUs[socket][p].first.counterValue[1]; } result.CXLWriteMem[socket] = CXLWriteMem; result.CXLWriteCache[socket] = CXLWriteCache; } } template void PCM::readAndAggregateUncoreMCCounters(const uint32 socket, CounterStateType & result) { if (LLCReadMissLatencyMetricsAvailable()) { result.TOROccupancyIAMiss += getUncoreCounterState(CBO_PMU_ID, socket, EventPosition::TOR_OCCUPANCY); result.TORInsertsIAMiss += getUncoreCounterState(CBO_PMU_ID, socket, EventPosition::TOR_INSERTS); } if (LLCReadMissLatencyMetricsAvailable() || uncoreFrequencyMetricAvailable()) { result.UncClocks += getUncoreClocks(socket); } if (socket < UFSStatus.size()) { result.UFSStatus.clear(); for (size_t die = 0; die < UFSStatus[socket].size(); ++die) { auto & handle = UFSStatus[socket][die]; if (handle.get() && die < handle->getNumEntries()) { const auto value = handle->read64(die); DBG(3, std::hex , value , std::dec); result.UFSStatus.push_back(value); } } } const bool ReadMCStatsFromServerBW = (socket < serverBW.size()); if (ReadMCStatsFromServerBW) { result.UncMCNormalReads += serverBW[socket]->getImcReads(); result.UncMCFullWrites += serverBW[socket]->getImcWrites(); if (PMMTrafficMetricsAvailable()) { result.UncPMMReads += serverBW[socket]->getPMMReads(); result.UncPMMWrites += serverBW[socket]->getPMMWrites(); } } if (hasPCICFGUncore()) { if (serverUncorePMUs.size() && serverUncorePMUs[socket].get()) { serverUncorePMUs[socket]->freezeCounters(); if (ReadMCStatsFromServerBW == false) { result.UncMCNormalReads += serverUncorePMUs[socket]->getImcReads(); result.UncMCFullWrites += serverUncorePMUs[socket]->getImcWrites(); if(nearMemoryMetricsAvailable()){ result.UncNMHit += serverUncorePMUs[socket]->getNMHits(); result.UncNMMiss += serverUncorePMUs[socket]->getNMMisses(); } } if (localMemoryRequestRatioMetricAvailable()) { if (hasCHA()) { result.UncHARequests += getUncoreCounterState(CBO_PMU_ID, socket, EventPosition::REQUESTS_ALL); result.UncHALocalRequests += getUncoreCounterState(CBO_PMU_ID, socket, EventPosition::REQUESTS_LOCAL); } else { result.UncHARequests += serverUncorePMUs[socket]->getHARequests(); result.UncHALocalRequests += serverUncorePMUs[socket]->getHALocalRequests(); } } if (PMMTrafficMetricsAvailable() && (ReadMCStatsFromServerBW == false)) { result.UncPMMReads += serverUncorePMUs[socket]->getPMMReads(); result.UncPMMWrites += serverUncorePMUs[socket]->getPMMWrites(); } if (HBMmemoryTrafficMetricsAvailable()) { result.UncEDCNormalReads += serverUncorePMUs[socket]->getEdcReads(); result.UncEDCFullWrites += serverUncorePMUs[socket]->getEdcWrites(); } serverUncorePMUs[socket]->unfreezeCounters(); } } else if(clientBW.get() && socket == 0) { result.UncMCNormalReads += clientImcReads->read(); result.UncMCFullWrites += clientImcWrites->read(); result.UncMCGTRequests += clientGtRequests->read(); result.UncMCIARequests += clientIaRequests->read(); result.UncMCIORequests += clientIoRequests->read(); } else { std::shared_ptr msr = MSR[socketRefCore[socket]]; TemporalThreadAffinity tempThreadAffinity(socketRefCore[socket]); // speedup trick for Linux switch (cpu_family_model) { case PCM::WESTMERE_EP: case PCM::NEHALEM_EP: { uint64 cUncMCFullWrites = 0; uint64 cUncMCNormalReads = 0; msr->read(MSR_UNCORE_PMC0, &cUncMCFullWrites); msr->read(MSR_UNCORE_PMC1, &cUncMCNormalReads); result.UncMCFullWrites += extractUncoreGenCounterValue(cUncMCFullWrites); result.UncMCNormalReads += extractUncoreGenCounterValue(cUncMCNormalReads); } break; case PCM::NEHALEM_EX: case PCM::WESTMERE_EX: { uint64 cUncMCNormalReads = 0; msr->read(MB0_MSR_PMU_CNT_0, &cUncMCNormalReads); result.UncMCNormalReads += extractUncoreGenCounterValue(cUncMCNormalReads); msr->read(MB1_MSR_PMU_CNT_0, &cUncMCNormalReads); result.UncMCNormalReads += extractUncoreGenCounterValue(cUncMCNormalReads); uint64 cUncMCFullWrites = 0; // really good approximation of msr->read(BB0_MSR_PERF_CNT_1, &cUncMCFullWrites); result.UncMCFullWrites += extractUncoreGenCounterValue(cUncMCFullWrites); msr->read(BB1_MSR_PERF_CNT_1, &cUncMCFullWrites); result.UncMCFullWrites += extractUncoreGenCounterValue(cUncMCFullWrites); } break; default:; } } } template void PCM::readAndAggregateEnergyCounters(const uint32 socket, CounterStateType & result) { if(socket < (uint32)energy_status.size()) result.PackageEnergyStatus += energy_status[socket]->read(); if (socket < (uint32)dram_energy_status.size()) result.DRAMEnergyStatus += dram_energy_status[socket]->read(); if (socket == 0) { for (size_t pp = 0; pp < pp_energy_status.size(); ++pp) { result.PPEnergyStatus[pp] += pp_energy_status[pp]->read(); } } } template void PCM::readMSRs(std::shared_ptr msr, const PCM::RawPMUConfig& msrConfig, CounterStateType& result) { auto read = [&msr, &result](const RawEventConfig & cfg) { const auto index = cfg.first[MSREventPosition::index]; if (result.MSRValues.find(index) == result.MSRValues.end()) { uint64 val{ 0 }; msr->read(index, &val); result.MSRValues[index] = val; } }; for (const auto& cfg : msrConfig.programmable) { read(cfg); } for (const auto& cfg : msrConfig.fixed) { read(cfg); } } template void PCM::readAndAggregatePackageCStateResidencies(std::shared_ptr msr, CounterStateType & result) { // reading package C state counters uint64 cCStateResidency[PCM::MAX_C_STATE + 1]; std::fill(cCStateResidency, cCStateResidency + PCM::MAX_C_STATE + 1, 0); for(int i=0; i <= int(PCM::MAX_C_STATE) ;++i) if(pkgCStateMsr && pkgCStateMsr[i]) msr->read(pkgCStateMsr[i], &(cCStateResidency[i])); for (int i = 0; i <= int(PCM::MAX_C_STATE); ++i) { if (cCStateResidency[i]) { atomic_fetch_add((std::atomic *)(result.CStateResidency + i), cCStateResidency[i]); } } } void PCM::readPCICFGRegisters(SystemCounterState& systemState) { auto read = [this, &systemState](const RawEventConfig& cfg) { const RawEventEncoding& reEnc = cfg.first; systemState.PCICFGValues[reEnc].clear(); for (auto& reg : PCICFGRegisterLocations[reEnc]) { const auto width = reEnc[PCICFGEventPosition::width]; auto& h = reg.first; const auto& offset = reg.second; if (h.get()) { uint64 value = ~0ULL; uint32 value32 = 0; switch (width) { case 16: h->read32(offset, &value32); value = (uint64)extract_bits_32(value32, 0, 15); break; case 32: h->read32(offset, &value32); value = (uint64)value32; break; case 64: h->read64(offset, &value); break; default: std::cerr << "ERROR: Unsupported width " << width << " for pcicfg register " << cfg.second << "\n"; } systemState.PCICFGValues[reEnc].push_back(value); } } }; for (const auto& cfg : pcicfgConfig.programmable) { read(cfg); } for (const auto& cfg : pcicfgConfig.fixed) { read(cfg); } } void PCM::readTPMIRegisters(SystemCounterState& systemState) { auto read = [this, &systemState](const RawEventConfig& cfg) { const RawEventEncoding& reEnc = cfg.first; systemState.TPMIValues[reEnc].clear(); for (auto& h : TPMIRegisterLocations[reEnc]) { if (h.get()) { for (auto e = 0ULL; e < h->getNumEntries(); ++e) { systemState.TPMIValues[reEnc].push_back(h->read64(e)); } } } }; for (const auto& cfg : tpmiConfig.programmable) { read(cfg); } for (const auto& cfg : tpmiConfig.fixed) { read(cfg); } } void PCM::readMMIORegisters(SystemCounterState& systemState) { auto read = [this, &systemState](const RawEventConfig& cfg) { const RawEventEncoding& reEnc = cfg.first; systemState.MMIOValues[reEnc].clear(); for (auto& reg : MMIORegisterLocations[reEnc]) { const auto width = reEnc[MMIOEventPosition::width]; auto& h = reg.first; const auto& offset = reg.second; if (h.get()) { uint64 value = ~0ULL; uint32 value32 = 0; switch (width) { case 16: value32 = h->read32(offset); value = (uint64)extract_bits_32(value32, 0, 15); break; case 32: value32 = h->read32(offset); value = (uint64)value32; break; case 64: value = h->read64(offset); break; default: std::cerr << "ERROR: Unsupported width " << width << " for mmio register " << cfg.second << "\n"; } systemState.MMIOValues[reEnc].push_back(value); } } }; for (const auto& cfg : mmioConfig.programmable) { read(cfg); } for (const auto& cfg : mmioConfig.fixed) { read(cfg); } } void PCM::readPMTRegisters(SystemCounterState& systemState) { for (auto & p: PMTRegisterLocations) { for (auto & t: p.second) { if (t.get()) { t->load(); } } } auto read = [this, &systemState](const RawEventConfig& cfg) { const RawEventEncoding& reEnc = cfg.first; systemState.PMTValues[reEnc].clear(); const auto lsb = reEnc[PMTEventPosition::lsb]; const auto msb = reEnc[PMTEventPosition::msb]; const auto offset = reEnc[PMTEventPosition::offset]; DBG(3, "PMTValues: " , std::hex , reEnc[PMTEventPosition::UID] , std::dec); for (auto& reg : PMTRegisterLocations[reEnc]) { if (reg.get()) { systemState.PMTValues[reEnc].push_back(reg->get(offset, lsb, msb)); DBG(3, "PMTValues: " , std::hex , reEnc[PMTEventPosition::UID] , " " , std::dec , reg->get(offset, lsb, msb)); } } }; for (const auto& cfg : pmtConfig.programmable) { read(cfg); } for (const auto& cfg : pmtConfig.fixed) { read(cfg); } } void PCM::readQPICounters(SystemCounterState & result) { // read QPI counters std::vector SocketProcessed(num_sockets, false); if (cpu_family_model == PCM::NEHALEM_EX || cpu_family_model == PCM::WESTMERE_EX) { for (int32 core = 0; core < num_cores; ++core) { if(isCoreOnline(core) == false) continue; if(core == socketRefCore[0]) MSR[core]->read(W_MSR_PMON_FIXED_CTR, &(result.uncoreTSC)); uint32 s = topology[core].socket_id; if (!SocketProcessed[s]) { TemporalThreadAffinity tempThreadAffinity(core); // speedup trick for Linux // incoming data responses from QPI link 0 MSR[core]->read(R_MSR_PMON_CTR1, &(result.incomingQPIPackets[s][0])); // incoming data responses from QPI link 1 (yes, from CTR0) MSR[core]->read(R_MSR_PMON_CTR0, &(result.incomingQPIPackets[s][1])); // incoming data responses from QPI link 2 MSR[core]->read(R_MSR_PMON_CTR8, &(result.incomingQPIPackets[s][2])); // incoming data responses from QPI link 3 MSR[core]->read(R_MSR_PMON_CTR9, &(result.incomingQPIPackets[s][3])); // outgoing idle flits from QPI link 0 MSR[core]->read(R_MSR_PMON_CTR3, &(result.outgoingQPIFlits[s][0])); // outgoing idle flits from QPI link 1 (yes, from CTR0) MSR[core]->read(R_MSR_PMON_CTR2, &(result.outgoingQPIFlits[s][1])); // outgoing idle flits from QPI link 2 MSR[core]->read(R_MSR_PMON_CTR10, &(result.outgoingQPIFlits[s][2])); // outgoing idle flits from QPI link 3 MSR[core]->read(R_MSR_PMON_CTR11, &(result.outgoingQPIFlits[s][3])); SocketProcessed[s] = true; } } } else if ((cpu_family_model == PCM::NEHALEM_EP || cpu_family_model == PCM::WESTMERE_EP)) { if (num_sockets == 2) { uint32 SCore[2] = { (uint32)socketRefCore[0], (uint32)socketRefCore[1] }; uint64 Total_Reads[2] = { 0, 0 }; uint64 Total_Writes[2] = { 0, 0 }; uint64 IOH_Reads[2] = { 0, 0 }; uint64 IOH_Writes[2] = { 0, 0 }; uint64 Remote_Reads[2] = { 0, 0 }; uint64 Remote_Writes[2] = { 0, 0 }; uint64 Local_Reads[2] = { 0, 0 }; uint64 Local_Writes[2] = { 0, 0 }; for (int s = 0; s < 2; ++s) { TemporalThreadAffinity tempThreadAffinity(SCore[s]); // speedup trick for Linux MSR[SCore[s]]->read(MSR_UNCORE_PMC0, &Total_Writes[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC1, &Total_Reads[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC2, &IOH_Reads[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC3, &IOH_Writes[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC4, &Remote_Reads[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC5, &Remote_Writes[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC6, &Local_Reads[s]); MSR[SCore[s]]->read(MSR_UNCORE_PMC7, &Local_Writes[s]); } #if 1 // compute Remote_Reads differently for (int s = 0; s < 2; ++s) { uint64 total = Total_Writes[s] + Total_Reads[s]; uint64 rem = IOH_Reads[s] + IOH_Writes[s] + Local_Reads[s] + Local_Writes[s] + Remote_Writes[s]; Remote_Reads[s] = (total > rem) ? (total - rem) : 0; } #endif // only an estimation (lower bound) - does not count NT stores correctly result.incomingQPIPackets[0][0] = Remote_Reads[1] + Remote_Writes[0]; result.incomingQPIPackets[0][1] = IOH_Reads[0]; result.incomingQPIPackets[1][0] = Remote_Reads[0] + Remote_Writes[1]; result.incomingQPIPackets[1][1] = IOH_Reads[1]; } else { // for a single socket systems no information is available result.incomingQPIPackets[0][0] = 0; } } else if (hasPCICFGUncore()) { for (int32 s = 0; (s < (int32)serverUncorePMUs.size()); ++s) { serverUncorePMUs[s]->freezeCounters(); for (uint32 port = 0; port < (uint32)getQPILinksPerSocket(); ++port) { result.incomingQPIPackets[s][port] = uint64(double(serverUncorePMUs[s]->getIncomingDataFlits(port)) / (64./getDataBytesPerFlit())); result.outgoingQPIFlits[s][port] = serverUncorePMUs[s]->getOutgoingFlits(port); result.TxL0Cycles[s][port] = serverUncorePMUs[s]->getUPIL0TxCycles(port); } serverUncorePMUs[s]->unfreezeCounters(); } } // end of reading QPI counters } template void PCM::readPackageThermalHeadroom(const uint32 socket, CounterStateType & result) { if(packageThermalMetricsAvailable()) { uint64 val = 0; MSR[socketRefCore[socket]]->read(MSR_PACKAGE_THERM_STATUS,&val); result.MSRValues[MSR_PACKAGE_THERM_STATUS] = val; result.ThermalHeadroom = extractThermalHeadroom(val); } else result.ThermalHeadroom = PCM_INVALID_THERMAL_HEADROOM; // not available } // Explicit instantiation needed in topology.cpp template void PCM::readAndAggregatePackageCStateResidencies(std::shared_ptr, UncoreCounterState &); template void PCM::readAndAggregateUncoreMCCounters(const uint32, UncoreCounterState&); template void PCM::readAndAggregateEnergyCounters(const uint32, UncoreCounterState&); template void PCM::readPackageThermalHeadroom(const uint32, SocketCounterState &); template void PCM::readAndAggregateCXLCMCounters(SystemCounterState &); SocketCounterState PCM::getSocketCounterState(uint32 socket) { SocketCounterState result; if (MSR.size()) { // reading core and uncore counter states for (int32 core = 0; core < num_cores; ++core) if (isCoreOnline(core) && (topology[core].socket_id == int32(socket))) result.readAndAggregate(MSR[core]); readAndAggregateUncoreMCCounters(socket, result); readAndAggregateEnergyCounters(socket, result); readPackageThermalHeadroom(socket, result); } return result; } void PCM::getAllCounterStates(SystemCounterState & systemState, std::vector & socketStates, std::vector & coreStates, const bool readAndAggregateSocketUncoreCounters) { // clear and zero-initialize all inputs systemState = SystemCounterState(); socketStates.clear(); socketStates.resize(num_sockets); coreStates.clear(); coreStates.resize(num_cores); std::vector > asyncCoreResults; for (int32 core = 0; core < num_cores; ++core) { // read core counters if (isCoreOnline(core)) { std::packaged_task task([this,&coreStates,&socketStates,core,readAndAggregateSocketUncoreCounters]() -> void { coreStates[core].readAndAggregate(MSR[core]); if (readAndAggregateSocketUncoreCounters) { socketStates[topology[core].socket_id].UncoreCounterState::readAndAggregate(MSR[core]); // read package C state counters } readMSRs(MSR[core], threadMSRConfig, coreStates[core]); } ); asyncCoreResults.push_back(task.get_future()); coreTaskQueues[core]->push(task); } DBG(3, core , " " , coreStates[core].InstRetiredAny.getRawData_NoOverflowProtection() ); } for (uint32 s = 0; s < (uint32)num_sockets && readAndAggregateSocketUncoreCounters; ++s) { int32 refCore = socketRefCore[s]; if (refCore<0) refCore = 0; std::packaged_task task([this, s, &socketStates, refCore]() -> void { readAndAggregateUncoreMCCounters(s, socketStates[s]); readAndAggregateEnergyCounters(s, socketStates[s]); readPackageThermalHeadroom(s, socketStates[s]); readMSRs(MSR[refCore], packageMSRConfig, socketStates[s]); } ); asyncCoreResults.push_back(task.get_future()); coreTaskQueues[refCore]->push(task); } if (readAndAggregateSocketUncoreCounters) { readQPICounters(systemState); readPCICFGRegisters(systemState); readMMIORegisters(systemState); readPMTRegisters(systemState); readTPMIRegisters(systemState); } for (auto & ar : asyncCoreResults) ar.wait(); for (int32 core = 0; core < num_cores; ++core) { // aggregate core counters into sockets if(isCoreOnline(core)) socketStates[topology[core].socket_id] += coreStates[core]; } for (int32 s = 0; s < num_sockets; ++s) { // aggregate core counters from sockets into system state and // aggregate socket uncore iMC, energy and package C state counters into system systemState += socketStates[s]; } readSystemEnergyStatus(systemState); } void PCM::readSystemEnergyStatus(SystemCounterState & systemState) { if (systemEnergyMetricAvailable() && system_energy_status.get() != nullptr) { systemState.systemEnergyStatus = system_energy_status->read(); } } void PCM::getUncoreCounterStates(SystemCounterState & systemState, std::vector & socketStates) { // clear and zero-initialize all inputs systemState = SystemCounterState(); socketStates.clear(); socketStates.resize(num_sockets); std::vector refCoreStates(num_sockets); for (uint32 s = 0; s < (uint32)num_sockets; ++s) { const int32 refCore = socketRefCore[s]; if(isCoreOnline(refCore)) { refCoreStates[s].readAndAggregateTSC(MSR[refCore]); } readAndAggregateUncoreMCCounters(s, socketStates[s]); readAndAggregateEnergyCounters(s, socketStates[s]); readPackageThermalHeadroom(s, socketStates[s]); } readQPICounters(systemState); readSystemEnergyStatus(systemState); for (int32 s = 0; s < num_sockets; ++s) { const int32 refCore = socketRefCore[s]; if(isCoreOnline(refCore)) { for(uint32 core=0; core < getNumCores(); ++core) { if(topology[core].socket_id == s && isCoreOnline(core)) socketStates[s] += refCoreStates[s]; } } // aggregate socket uncore iMC, energy counters into system systemState += socketStates[s]; } } CoreCounterState PCM::getCoreCounterState(uint32 core) { CoreCounterState result; if (MSR.size()) result.readAndAggregate(MSR[core]); return result; } uint32 PCM::getNumCores() const { return (uint32)num_cores; } uint32 PCM::getNumOnlineCores() const { return (uint32)num_online_cores; } uint32 PCM::getNumSockets() const { return (uint32)num_sockets; } uint32 PCM::getAccel() const { return accel; } void PCM::setAccel(uint32 input) { accel = input; } uint32 PCM::getNumberofAccelCounters() const { return accel_counters_num_max; } void PCM::setNumberofAccelCounters(uint32 input) { accel_counters_num_max = input; } uint32 PCM::getNumOnlineSockets() const { return (uint32)num_online_sockets; } uint32 PCM::getThreadsPerCore() const { return (uint32)threads_per_core; } bool PCM::getSMT() const { return threads_per_core > 1; } uint64 PCM::getNominalFrequency() const { return nominal_frequency; } uint32 PCM::getL3ScalingFactor() const { PCM_CPUID_INFO cpuinfo; pcm_cpuid(0xf,0x1,cpuinfo); return (uint32)cpuinfo.reg.ebx; } bool PCM::isSomeCoreOfflined() { PCM_CPUID_INFO cpuid_args; pcm_cpuid(0xB,1,cpuid_args); uint32 max_num_lcores_per_socket = cpuid_args.reg.ebx & 0xFFFF; uint32 max_num_lcores = max_num_lcores_per_socket * getNumSockets(); if(threads_per_core == 1 && (getNumOnlineCores() * 2 == max_num_lcores)) // HT is disabled in the BIOS { return false; } return !(getNumOnlineCores() == max_num_lcores); } ServerUncoreCounterState PCM::getServerUncoreCounterState(uint32 socket) { ServerUncoreCounterState result; if (socket < serverBW.size() && serverBW[socket].get()) { result.freeRunningCounter[ServerUncoreCounterState::ImcReads] = serverBW[socket]->getImcReads(); result.freeRunningCounter[ServerUncoreCounterState::ImcWrites] = serverBW[socket]->getImcWrites(); result.freeRunningCounter[ServerUncoreCounterState::PMMReads] = serverBW[socket]->getPMMReads(); result.freeRunningCounter[ServerUncoreCounterState::PMMWrites] = serverBW[socket]->getPMMWrites(); } if(serverUncorePMUs.size() && serverUncorePMUs[socket].get()) { serverUncorePMUs[socket]->freezeCounters(); for(uint32 port=0;port < (uint32)serverUncorePMUs[socket]->getNumQPIPorts();++port) { assert(port < result.xPICounter.size()); for (uint32 cnt = 0; cnt < ServerUncoreCounterState::maxCounters; ++cnt) result.xPICounter[port][cnt] = serverUncorePMUs[socket]->getQPILLCounter(port, cnt); assert(port < result.M3UPICounter.size()); for (uint32 cnt = 0; cnt < ServerUncoreCounterState::maxCounters; ++cnt) result.M3UPICounter[port][cnt] = serverUncorePMUs[socket]->getM3UPICounter(port, cnt); } for (uint32 channel = 0; channel < (uint32)serverUncorePMUs[socket]->getNumMCChannels(); ++channel) { assert(channel < result.DRAMClocks.size()); result.DRAMClocks[channel] = serverUncorePMUs[socket]->getDRAMClocks(channel); assert(channel < result.MCCounter.size()); for (uint32 cnt = 0; cnt < ServerUncoreCounterState::maxCounters; ++cnt) result.MCCounter[channel][cnt] = serverUncorePMUs[socket]->getMCCounter(channel, cnt); } for (uint32 channel = 0; channel < (uint32)serverUncorePMUs[socket]->getNumEDCChannels(); ++channel) { assert(channel < result.HBMClocks.size()); result.HBMClocks[channel] = serverUncorePMUs[socket]->getHBMClocks(channel); assert(channel < result.EDCCounter.size()); for (uint32 cnt = 0; cnt < ServerUncoreCounterState::maxCounters; ++cnt) result.EDCCounter[channel][cnt] = serverUncorePMUs[socket]->getEDCCounter(channel, cnt); } for (uint32 controller = 0; controller < (uint32)serverUncorePMUs[socket]->getNumMC(); ++controller) { assert(controller < result.M2MCounter.size()); for (uint32 cnt = 0; cnt < ServerUncoreCounterState::maxCounters; ++cnt) result.M2MCounter[controller][cnt] = serverUncorePMUs[socket]->getM2MCounter(controller, cnt); assert(controller < result.HACounter.size()); for (uint32 cnt = 0; cnt < ServerUncoreCounterState::maxCounters; ++cnt) result.HACounter[controller][cnt] = serverUncorePMUs[socket]->getHACounter(controller, cnt); } serverUncorePMUs[socket]->unfreezeCounters(); } if (MSR.size()) { uint32 refCore = socketRefCore[socket]; TemporalThreadAffinity tempThreadAffinity(refCore); readUncoreCounterValues(result, socket); for (uint32 stack = 0; socket < iioPMUs.size() && stack < iioPMUs[socket].size() && stack < ServerUncoreCounterState::maxIIOStacks; ++stack) { for (int i = 0; i < ServerUncoreCounterState::maxCounters && size_t(i) < iioPMUs[socket][stack].size(); ++i) { result.IIOCounter[stack][i] = *(iioPMUs[socket][stack].counterValue[i]); } } for (uint32 stack = 0; socket < irpPMUs.size() && stack < irpPMUs[socket].size() && stack < ServerUncoreCounterState::maxIIOStacks; ++stack) { for (int i = 0; i < ServerUncoreCounterState::maxCounters && size_t(i) < irpPMUs[socket][stack].size(); ++i) { if (irpPMUs[socket][stack].counterValue[i].get()) { result.IRPCounter[stack][i] = *(irpPMUs[socket][stack].counterValue[i]); } } } result.UncClocks = getUncoreClocks(socket); for (size_t p = 0; p < getNumCXLPorts(socket); ++p) { for (int i = 0; i < ServerUncoreCounterState::maxCounters && socket < cxlPMUs.size() && size_t(i) < cxlPMUs[socket][p].first.size(); ++i) { result.CXLCMCounter[p][i] = *cxlPMUs[socket][p].first.counterValue[i]; } for (int i = 0; i < ServerUncoreCounterState::maxCounters && socket < cxlPMUs.size() && size_t(i) < cxlPMUs[socket][p].second.size(); ++i) { result.CXLDPCounter[p][i] = *cxlPMUs[socket][p].second.counterValue[i]; } } uint64 val=0; //MSR[refCore]->read(MSR_PKG_ENERGY_STATUS,&val); //DBG(3, "Energy status: " , val ); MSR[refCore]->read(MSR_PACKAGE_THERM_STATUS,&val); result.PackageThermalHeadroom = extractThermalHeadroom(val); result.InvariantTSC = getInvariantTSC_Fast(refCore); readAndAggregatePackageCStateResidencies(MSR[refCore], result); } readAndAggregateEnergyCounters(socket, result); return result; } #ifndef _MSC_VER void print_mcfg(const char * path) { int mcfg_handle = ::open(path, O_RDONLY); if (mcfg_handle < 0) { std::cerr << "PCM Error: Cannot open " << path << "\n"; throw std::exception(); } MCFGHeader header; ssize_t read_bytes = ::read(mcfg_handle, (void *)&header, sizeof(MCFGHeader)); if(read_bytes == 0) { std::cerr << "PCM Error: Cannot read " << path << "\n"; ::close(mcfg_handle); throw std::exception(); } const unsigned segments = header.nrecords(); header.print(); std::cout << "Segments: " << segments << "\n"; for(unsigned int i=0; i > ServerUncorePMUs::socket2iMCbus{}; std::vector > ServerUncorePMUs::socket2UPIbus{}; std::vector > ServerUncorePMUs::socket2M2Mbus{}; void initSocket2Bus(std::vector > & socket2bus, uint32 device, uint32 function, const uint32 DEV_IDS[], uint32 devIdsSize) { if (device == PCM_INVALID_DEV_ADDR || function == PCM_INVALID_FUNC_ADDR) { return; } Mutex::Scope _(socket2busMutex); if(!socket2bus.empty()) return; forAllIntelDevices( [&devIdsSize,&DEV_IDS, &socket2bus](const uint32 group, const uint32 bus, const uint32 /* device */, const uint32 /* function */, const uint32 device_id) { for (uint32 i = 0; i < devIdsSize; ++i) { // match if (DEV_IDS[i] == device_id) { DBG(2, std::hex, "found group ", group, " bus ", bus, " with device ID ", device_id, std::dec); socket2bus.push_back(std::make_pair(group, bus)); break; } } }, device, function); } int getBusFromSocket(const uint32 socket) { int cur_bus = 0; uint32 cur_socket = 0; DBG(2, "socket: " , socket); while(cur_socket <= socket) { DBG(2, "reading from bus 0x", std::hex, cur_bus, std::dec); PciHandleType h(0, cur_bus, 5, 0); uint32 cpubusno = 0; h.read32(0x108, &cpubusno); // CPUBUSNO register cur_bus = (cpubusno >> 8)& 0x0ff; DBG(2, "socket: ", cur_socket, std::hex, " cpubusno: 0x", std::hex, cpubusno, " ", cur_bus, std::dec); if(socket == cur_socket) return cur_bus; ++cur_socket; ++cur_bus; if(cur_bus > 0x0ff) return -1; } return -1; } PciHandleType * ServerUncorePMUs::createIntelPerfMonDevice(uint32 groupnr_, int32 bus_, uint32 dev_, uint32 func_, bool checkVendor) { if (PciHandleType::exists(groupnr_, (uint32)bus_, dev_, func_)) { PciHandleType * handle = new PciHandleType(groupnr_, bus_, dev_, func_); if(!checkVendor) return handle; uint32 vendor_id = 0; handle->read32(PCM_PCI_VENDOR_ID_OFFSET,&vendor_id); vendor_id &= 0x0ffff; if(vendor_id == PCM_INTEL_PCI_VENDOR_ID) return handle; deleteAndNullify(handle); } return NULL; } bool PCM::isSecureBoot() const { static int flag = -1; if (MSR.size() > 0 && flag == -1) { DBG(1, "checking MSR in isSecureBoot"); uint64 val = 0; if (MSR[0]->read(IA32_PERFEVTSEL0_ADDR, &val) != sizeof(val)) { flag = 0; // some problem with MSR read, not secure boot } // read works if (MSR[0]->write(IA32_PERFEVTSEL0_ADDR, val) != sizeof(val)/* && errno == 1 */) // errno works only on windows { // write does not work -> secure boot flag = 1; } else { flag = 0; // can write MSR -> no secure boot } } return flag == 1; } bool PCM::useLinuxPerfForUncore() const { static int use = -1; if (use != -1) { return 1 == use; } use = 0; bool secureBoot = isSecureBoot(); #ifdef PCM_USE_PERF const auto imcIDs = enumeratePerfPMUs("imc", 100); std::cerr << "INFO: Linux perf interface to program uncore PMUs is " << (imcIDs.empty()?"NOT ":"") << "present\n"; if (imcIDs.empty()) { use = 0; return 1 == use; } const char * perf_env = std::getenv("PCM_USE_UNCORE_PERF"); if (perf_env != NULL && std::string(perf_env) == std::string("1")) { std::cerr << "INFO: using Linux perf interface to program uncore PMUs because env variable PCM_USE_UNCORE_PERF=1\n"; use = 1; } if (secureBoot) { std::cerr << "INFO: Secure Boot detected. Using Linux perf for uncore PMU programming.\n"; use = 1; } #else if (1) { if (secureBoot) { std::cerr << "ERROR: Secure Boot detected. Recompile PCM with -DPCM_USE_PERF or disable Secure Boot.\n"; } } #endif return 1 == use; } template void PCM::getPCICFGPMUsFromDiscovery(const unsigned int BoxType, const size_t s, F f) const { if (uncorePMUDiscovery.get()) { const auto numBoxes = uncorePMUDiscovery->getNumBoxes(BoxType, s); for (size_t pos = 0; pos < numBoxes; ++pos) { if (uncorePMUDiscovery->getBoxAccessType(BoxType, s, pos) == UncorePMUDiscovery::accessTypeEnum::PCICFG) { std::vector > CounterControlRegs, CounterValueRegs; const auto n_regs = uncorePMUDiscovery->getBoxNumRegs(BoxType, s, pos); auto makeRegister = [](const uint64 rawAddr) { #ifndef PCI_ENABLE constexpr auto PCI_ENABLE = 0x80000000ULL; #endif UncorePMUDiscovery::PCICFGAddress Addr; Addr.raw = rawAddr; assert(Addr.raw & PCI_ENABLE); try { auto handle = std::make_shared(0, (uint32)Addr.fields.bus, (uint32)Addr.fields.device, (uint32)Addr.fields.function); assert(handle.get()); DBG(3, "opened bdf ", Addr.getStr()); return std::make_shared(handle, (size_t)Addr.fields.offset); } catch (...) { DBG(3, "error opening bdf ", Addr.getStr()); } return std::shared_ptr(); }; auto boxCtlRegister = makeRegister(uncorePMUDiscovery->getBoxCtlAddr(BoxType, s, pos)); if (boxCtlRegister.get()) { for (size_t r = 0; r < n_regs; ++r) { CounterControlRegs.push_back(makeRegister(uncorePMUDiscovery->getBoxCtlAddr(BoxType, s, pos, r))); CounterValueRegs.push_back(makeRegister(uncorePMUDiscovery->getBoxCtrAddr(BoxType, s, pos, r))); } f(UncorePMU(boxCtlRegister, CounterControlRegs, CounterValueRegs)); } } } } }; ServerUncorePMUs::ServerUncorePMUs(uint32 socket_, const PCM * pcm) : iMCbus(-1) , UPIbus(-1) , M2Mbus(-1) , groupnr(0) , cpu_family_model(pcm->getCPUFamilyModel()) , qpi_speed(0) { if (pcm->useLinuxPerfForUncore()) { initPerf(socket_, pcm); } else { initRegisterLocations(pcm); initBuses(socket_, pcm); initDirect(socket_, pcm); } std::cerr << "Socket " << socket_ << ": " << getNumMC() << " memory controllers detected with total number of " << getNumMCChannels() << " channels. " << getNumQPIPorts() << " " << pcm->xPI() << " ports detected." << " " << m2mPMUs.size() << " M2M (mesh to memory)/B2CMI blocks detected." " " << hbm_m2mPMUs.size() << " HBM M2M blocks detected." " " << edcPMUs.size() << " EDC/HBM channels detected." " " << haPMUs.size() << " Home Agents detected." " " << m3upiPMUs.size() << " M3UPI/B2UPI blocks detected." "\n"; } void ServerUncorePMUs::initRegisterLocations(const PCM * pcm) { #define PCM_PCICFG_MC_INIT(controller, channel, arch) \ MCRegisterLocation.resize(controller + 1); \ MCRegisterLocation[controller].resize(channel + 1); \ MCRegisterLocation[controller][channel] = \ std::make_pair(arch##_MC##controller##_CH##channel##_REGISTER_DEV_ADDR, arch##_MC##controller##_CH##channel##_REGISTER_FUNC_ADDR); #define PCM_PCICFG_QPI_INIT(port, arch) \ XPIRegisterLocation.resize(port + 1); \ XPIRegisterLocation[port] = std::make_pair(arch##_QPI_PORT##port##_REGISTER_DEV_ADDR, arch##_QPI_PORT##port##_REGISTER_FUNC_ADDR); #define PCM_PCICFG_M3UPI_INIT(port, arch) \ M3UPIRegisterLocation.resize(port + 1); \ M3UPIRegisterLocation[port] = std::make_pair(arch##_M3UPI_PORT##port##_REGISTER_DEV_ADDR, arch##_M3UPI_PORT##port##_REGISTER_FUNC_ADDR); #define PCM_PCICFG_EDC_INIT(controller, clock, arch) \ EDCRegisterLocation.resize(controller + 1); \ EDCRegisterLocation[controller] = std::make_pair(arch##_EDC##controller##_##clock##_REGISTER_DEV_ADDR, arch##_EDC##controller##_##clock##_REGISTER_FUNC_ADDR); #define PCM_PCICFG_M2M_INIT(x, arch) \ M2MRegisterLocation.resize(x + 1); \ M2MRegisterLocation[x] = std::make_pair(arch##_M2M_##x##_REGISTER_DEV_ADDR, arch##_M2M_##x##_REGISTER_FUNC_ADDR); #define PCM_PCICFG_HBM_M2M_INIT(x, arch) \ HBM_M2MRegisterLocation.resize(x + 1); \ HBM_M2MRegisterLocation[x] = std::make_pair(arch##_HBM_M2M_##x##_REGISTER_DEV_ADDR, arch##_HBM_M2M_##x##_REGISTER_FUNC_ADDR); #define PCM_PCICFG_HA_INIT(x, arch) \ HARegisterLocation.resize(x + 1); \ HARegisterLocation[x] = std::make_pair(arch##_HA##x##_REGISTER_DEV_ADDR, arch##_HA##x##_REGISTER_FUNC_ADDR); switch (cpu_family_model) { case PCM::JAKETOWN: case PCM::IVYTOWN: { PCM_PCICFG_MC_INIT(0, 0, JKTIVT) PCM_PCICFG_MC_INIT(0, 1, JKTIVT) PCM_PCICFG_MC_INIT(0, 2, JKTIVT) PCM_PCICFG_MC_INIT(0, 3, JKTIVT) PCM_PCICFG_MC_INIT(1, 0, JKTIVT) PCM_PCICFG_MC_INIT(1, 1, JKTIVT) PCM_PCICFG_MC_INIT(1, 2, JKTIVT) PCM_PCICFG_MC_INIT(1, 3, JKTIVT) PCM_PCICFG_QPI_INIT(0, JKTIVT); PCM_PCICFG_QPI_INIT(1, JKTIVT); PCM_PCICFG_QPI_INIT(2, JKTIVT); } break; case PCM::HASWELLX: case PCM::BDX_DE: case PCM::BDX: { PCM_PCICFG_MC_INIT(0, 0, HSX) PCM_PCICFG_MC_INIT(0, 1, HSX) PCM_PCICFG_MC_INIT(0, 2, HSX) PCM_PCICFG_MC_INIT(0, 3, HSX) PCM_PCICFG_MC_INIT(1, 0, HSX) PCM_PCICFG_MC_INIT(1, 1, HSX) PCM_PCICFG_MC_INIT(1, 2, HSX) PCM_PCICFG_MC_INIT(1, 3, HSX) PCM_PCICFG_QPI_INIT(0, HSX); PCM_PCICFG_QPI_INIT(1, HSX); PCM_PCICFG_QPI_INIT(2, HSX); PCM_PCICFG_HA_INIT(0, HSX); PCM_PCICFG_HA_INIT(1, HSX); } break; case PCM::SKX: { PCM_PCICFG_MC_INIT(0, 0, SKX) PCM_PCICFG_MC_INIT(0, 1, SKX) PCM_PCICFG_MC_INIT(0, 2, SKX) PCM_PCICFG_MC_INIT(0, 3, SKX) PCM_PCICFG_MC_INIT(1, 0, SKX) PCM_PCICFG_MC_INIT(1, 1, SKX) PCM_PCICFG_MC_INIT(1, 2, SKX) PCM_PCICFG_MC_INIT(1, 3, SKX) PCM_PCICFG_QPI_INIT(0, SKX); PCM_PCICFG_QPI_INIT(1, SKX); PCM_PCICFG_QPI_INIT(2, SKX); if (pcm->isCPX()) { PCM_PCICFG_QPI_INIT(3, CPX); PCM_PCICFG_QPI_INIT(4, CPX); PCM_PCICFG_QPI_INIT(5, CPX); } PCM_PCICFG_M2M_INIT(0, SKX) PCM_PCICFG_M2M_INIT(1, SKX) // M3UPI if (pcm->isCPX()) { // CPX PCM_PCICFG_M3UPI_INIT(0, CPX); PCM_PCICFG_M3UPI_INIT(1, CPX); PCM_PCICFG_M3UPI_INIT(2, CPX); PCM_PCICFG_M3UPI_INIT(3, CPX); PCM_PCICFG_M3UPI_INIT(4, CPX); PCM_PCICFG_M3UPI_INIT(5, CPX); } else { // SKX/CLX PCM_PCICFG_M3UPI_INIT(0, SKX); PCM_PCICFG_M3UPI_INIT(1, SKX); PCM_PCICFG_M3UPI_INIT(2, SKX); } } break; case PCM::ICX: { PCM_PCICFG_QPI_INIT(0, ICX); PCM_PCICFG_QPI_INIT(1, ICX); PCM_PCICFG_QPI_INIT(2, ICX); PCM_PCICFG_M3UPI_INIT(0, ICX); PCM_PCICFG_M3UPI_INIT(1, ICX); PCM_PCICFG_M3UPI_INIT(2, ICX); PCM_PCICFG_M2M_INIT(0, SERVER) PCM_PCICFG_M2M_INIT(1, SERVER) PCM_PCICFG_M2M_INIT(2, SERVER) PCM_PCICFG_M2M_INIT(3, SERVER) } break; case PCM::SPR: case PCM::EMR: { PCM_PCICFG_QPI_INIT(0, SPR); PCM_PCICFG_QPI_INIT(1, SPR); PCM_PCICFG_QPI_INIT(2, SPR); PCM_PCICFG_QPI_INIT(3, SPR); PCM_PCICFG_M3UPI_INIT(0, SPR); PCM_PCICFG_M3UPI_INIT(1, SPR); PCM_PCICFG_M3UPI_INIT(2, SPR); PCM_PCICFG_M3UPI_INIT(3, SPR); PCM_PCICFG_M2M_INIT(0, SERVER) PCM_PCICFG_M2M_INIT(1, SERVER) PCM_PCICFG_M2M_INIT(2, SERVER) PCM_PCICFG_M2M_INIT(3, SERVER) PCM_PCICFG_HBM_M2M_INIT(0, SERVER) PCM_PCICFG_HBM_M2M_INIT(1, SERVER) PCM_PCICFG_HBM_M2M_INIT(2, SERVER) PCM_PCICFG_HBM_M2M_INIT(3, SERVER) PCM_PCICFG_HBM_M2M_INIT(4, SERVER) PCM_PCICFG_HBM_M2M_INIT(5, SERVER) PCM_PCICFG_HBM_M2M_INIT(6, SERVER) PCM_PCICFG_HBM_M2M_INIT(7, SERVER) PCM_PCICFG_HBM_M2M_INIT(8, SERVER) PCM_PCICFG_HBM_M2M_INIT(9, SERVER) PCM_PCICFG_HBM_M2M_INIT(10, SERVER) PCM_PCICFG_HBM_M2M_INIT(11, SERVER) PCM_PCICFG_HBM_M2M_INIT(12, SERVER) PCM_PCICFG_HBM_M2M_INIT(13, SERVER) PCM_PCICFG_HBM_M2M_INIT(14, SERVER) PCM_PCICFG_HBM_M2M_INIT(15, SERVER) } break; case PCM::KNL: { // 2 DDR4 Memory Controllers with 3 channels each PCM_PCICFG_MC_INIT(0, 0, KNL) PCM_PCICFG_MC_INIT(0, 1, KNL) PCM_PCICFG_MC_INIT(0, 2, KNL) PCM_PCICFG_MC_INIT(1, 0, KNL) PCM_PCICFG_MC_INIT(1, 1, KNL) PCM_PCICFG_MC_INIT(1, 2, KNL) // 8 MCDRAM (Multi-Channel [Stacked] DRAM) Memory Controllers PCM_PCICFG_EDC_INIT(0, ECLK, KNL) PCM_PCICFG_EDC_INIT(1, ECLK, KNL) PCM_PCICFG_EDC_INIT(2, ECLK, KNL) PCM_PCICFG_EDC_INIT(3, ECLK, KNL) PCM_PCICFG_EDC_INIT(4, ECLK, KNL) PCM_PCICFG_EDC_INIT(5, ECLK, KNL) PCM_PCICFG_EDC_INIT(6, ECLK, KNL) PCM_PCICFG_EDC_INIT(7, ECLK, KNL) } break; case PCM::SRF: case PCM::GNR: { PCM_PCICFG_QPI_INIT(0, BHS); PCM_PCICFG_QPI_INIT(1, BHS); PCM_PCICFG_QPI_INIT(2, BHS); PCM_PCICFG_QPI_INIT(3, BHS); PCM_PCICFG_QPI_INIT(4, BHS); PCM_PCICFG_QPI_INIT(5, BHS); // B2CMI (M2M) PCM_PCICFG_M2M_INIT(0, BHS) PCM_PCICFG_M2M_INIT(1, BHS) PCM_PCICFG_M2M_INIT(2, BHS) PCM_PCICFG_M2M_INIT(3, BHS) PCM_PCICFG_M2M_INIT(4, BHS) PCM_PCICFG_M2M_INIT(5, BHS) PCM_PCICFG_M2M_INIT(6, BHS) PCM_PCICFG_M2M_INIT(7, BHS) PCM_PCICFG_M2M_INIT(8, BHS) PCM_PCICFG_M2M_INIT(9, BHS) PCM_PCICFG_M2M_INIT(10, BHS) PCM_PCICFG_M2M_INIT(11, BHS) // B2UPI (M3UPI) PCM_PCICFG_M3UPI_INIT(0, BHS); PCM_PCICFG_M3UPI_INIT(1, BHS); PCM_PCICFG_M3UPI_INIT(2, BHS); PCM_PCICFG_M3UPI_INIT(3, BHS); PCM_PCICFG_M3UPI_INIT(4, BHS); PCM_PCICFG_M3UPI_INIT(5, BHS); } break; case PCM::GNR_D: { // B2CMI (M2M) PCM_PCICFG_M2M_INIT(0, BHS) PCM_PCICFG_M2M_INIT(1, BHS) PCM_PCICFG_M2M_INIT(2, BHS) PCM_PCICFG_M2M_INIT(3, BHS) PCM_PCICFG_M2M_INIT(4, BHS) PCM_PCICFG_M2M_INIT(5, BHS) PCM_PCICFG_M2M_INIT(6, BHS) PCM_PCICFG_M2M_INIT(7, BHS) } break; case PCM::SNOWRIDGE: { PCM_PCICFG_M2M_INIT(0, SERVER) PCM_PCICFG_M2M_INIT(1, SERVER) PCM_PCICFG_M2M_INIT(2, SERVER) PCM_PCICFG_M2M_INIT(3, SERVER) } break; case PCM::GRR: { // placeholder to init GRR PCICFG } break; default: std::cerr << "Error: Uncore PMU for processor with id 0x" << std::hex << cpu_family_model << std::dec << " is not supported.\n"; throw std::exception(); } #undef PCM_PCICFG_MC_INIT #undef PCM_PCICFG_QPI_INIT #undef PCM_PCICFG_M3UPI_INIT #undef PCM_PCICFG_EDC_INIT #undef PCM_PCICFG_M2M_INIT #undef PCM_PCICFG_HA_INIT } void ServerUncorePMUs::initBuses(uint32 socket_, const PCM * pcm) { const uint32 total_sockets_ = pcm->getNumSockets(); if (M2MRegisterLocation.size()) { initSocket2Bus(socket2M2Mbus, M2MRegisterLocation[0].first, M2MRegisterLocation[0].second, M2M_DEV_IDS, (uint32)sizeof(M2M_DEV_IDS) / sizeof(M2M_DEV_IDS[0])); if (socket_ < socket2M2Mbus.size()) { groupnr = socket2M2Mbus[socket_].first; M2Mbus = socket2M2Mbus[socket_].second; } else { std::cerr << "PCM error: socket_ " << socket_ << " >= socket2M2Mbus.size() " << socket2M2Mbus.size() << "\n"; } if (total_sockets_ != socket2M2Mbus.size()) { std::cerr << "PCM warning: total_sockets_ " << total_sockets_ << " does not match socket2M2Mbus.size() " << socket2M2Mbus.size() << "\n"; } } if (MCRegisterLocation.size() > 0 && MCRegisterLocation[0].size() > 0) { initSocket2Bus(socket2iMCbus, MCRegisterLocation[0][0].first, MCRegisterLocation[0][0].second, IMC_DEV_IDS, (uint32)sizeof(IMC_DEV_IDS) / sizeof(IMC_DEV_IDS[0])); if (total_sockets_ == socket2iMCbus.size()) { if (total_sockets_ == socket2M2Mbus.size() && socket2iMCbus[socket_].first != socket2M2Mbus[socket_].first) { std::cerr << "PCM error: mismatching PCICFG group number for M2M and IMC perfmon devices.\n"; M2Mbus = -1; } groupnr = socket2iMCbus[socket_].first; iMCbus = socket2iMCbus[socket_].second; } else if (total_sockets_ <= 4) { iMCbus = getBusFromSocket(socket_); if (iMCbus < 0) { std::cerr << "Cannot find bus for socket " << socket_ << " on system with " << total_sockets_ << " sockets.\n"; throw std::exception(); } else { std::cerr << "PCM Warning: the bus for socket " << socket_ << " on system with " << total_sockets_ << " sockets could not find via PCI bus scan. Using cpubusno register. Bus = " << iMCbus << "\n"; } } else { std::cerr << "Cannot find bus for socket " << socket_ << " on system with " << total_sockets_ << " sockets.\n"; throw std::exception(); } } #if 1 if (total_sockets_ == 1) { /* * For single socket systems, do not worry at all about QPI ports. This * eliminates QPI LL programming error messages on single socket systems * with BIOS that hides QPI performance counting PCI functions. It also * eliminates register programming that is not needed since no QPI traffic * is possible with single socket systems. */ return; } #endif #ifdef PCM_NOQPI return; #endif if (PCM::hasUPI(cpu_family_model) && XPIRegisterLocation.size() > 0) { initSocket2Bus(socket2UPIbus, XPIRegisterLocation[0].first, XPIRegisterLocation[0].second, UPI_DEV_IDS, (uint32)sizeof(UPI_DEV_IDS) / sizeof(UPI_DEV_IDS[0])); if(total_sockets_ == socket2UPIbus.size()) { UPIbus = socket2UPIbus[socket_].second; if(groupnr != socket2UPIbus[socket_].first) { UPIbus = -1; std::cerr << "PCM error: mismatching PCICFG group number for UPI and IMC perfmon devices.\n"; } } else { std::cerr << "PCM error: Did not find UPI perfmon device on every socket in a multisocket system.\n"; } } else { UPIbus = iMCbus; } DBG(1, "UPIbus: ", UPIbus); } void ServerUncorePMUs::initDirect(uint32 socket_, const PCM * pcm) { { std::vector > imcHandles; auto lastWorkingChannels = imcHandles.size(); for (auto & ctrl: MCRegisterLocation) { for (auto & channel : ctrl) { PciHandleType * handle = createIntelPerfMonDevice(groupnr, iMCbus, channel.first, channel.second, true); if (handle) imcHandles.push_back(std::shared_ptr(handle)); } if (imcHandles.size() > lastWorkingChannels) { num_imc_channels.push_back((uint32)(imcHandles.size() - lastWorkingChannels)); } lastWorkingChannels = imcHandles.size(); } for (auto & handle : imcHandles) { if (cpu_family_model == PCM::KNL) { imcPMUs.push_back( UncorePMU( std::make_shared(handle, KNX_MC_CH_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTL0_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTL1_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTL2_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTL3_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTR0_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTR1_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTR2_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_CTR3_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_FIXED_CTL_ADDR), std::make_shared(handle, KNX_MC_CH_PCI_PMON_FIXED_CTR_ADDR)) ); } else { imcPMUs.push_back( UncorePMU( std::make_shared(handle, XPF_MC_CH_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTL0_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTL1_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTL2_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTL3_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTR0_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTR1_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTR2_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_CTR3_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_FIXED_CTL_ADDR), std::make_shared(handle, XPF_MC_CH_PCI_PMON_FIXED_CTR_ADDR)) ); } } } auto populateM2MPMUs = [](uint32 groupnr, int32 M2Mbus, int32 cpu_family_model, const std::vector > & M2MRegisterLocation, UncorePMUVector & m2mPMUs) { std::vector > m2mHandles; if (M2Mbus >= 0) { for (auto & reg : M2MRegisterLocation) { PciHandleType * handle = createIntelPerfMonDevice(groupnr, M2Mbus, reg.first, reg.second, true); if (handle) m2mHandles.push_back(std::shared_ptr(handle)); } } for (auto & handle : m2mHandles) { switch (cpu_family_model) { case PCM::ICX: case PCM::SNOWRIDGE: case PCM::SPR: case PCM::EMR: case PCM::GNR: // B2CMI PMUs case PCM::GNR_D: case PCM::SRF: m2mPMUs.push_back( UncorePMU( std::make_shared(handle, SERVER_M2M_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTL0_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTL1_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTL2_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTL3_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTR0_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTR1_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTR2_ADDR), std::make_shared(handle, SERVER_M2M_PCI_PMON_CTR3_ADDR) ) ); break; default: m2mPMUs.push_back( UncorePMU( std::make_shared(handle, SKX_M2M_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTL0_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTL1_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTL2_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTL3_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTR0_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTR1_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTR2_ADDR), std::make_shared(handle, SKX_M2M_PCI_PMON_CTR3_ADDR) ) ); } } }; populateM2MPMUs(groupnr, M2Mbus, cpu_family_model, M2MRegisterLocation, m2mPMUs); populateM2MPMUs(groupnr, M2Mbus, cpu_family_model, HBM_M2MRegisterLocation, hbm_m2mPMUs); int numChannels = 0; if (safe_getenv("PCM_NO_IMC_DISCOVERY") == std::string("1")) { if (cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR) { numChannels = 3; } } if (cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::ICX) { numChannels = 2; if (PCM::getCPUFamilyModelFromCPUID() == PCM::ICX_D) { numChannels = 3; } } auto createIMCPMU = [](const size_t addr, const size_t mapSize) -> UncorePMU { const auto alignedAddr = addr & ~4095ULL; const auto alignDelta = addr & 4095ULL; auto handle = std::make_shared(alignedAddr, mapSize, false); return UncorePMU( std::make_shared(handle, SERVER_MC_CH_PMON_BOX_CTL_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTL0_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTL1_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTL2_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTL3_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTR0_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTR1_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTR2_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_CTR3_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_FIXED_CTL_OFFSET + alignDelta), std::make_shared(handle, SERVER_MC_CH_PMON_FIXED_CTR_OFFSET + alignDelta) ); }; auto initAndCheckSocket2Ubox0Bus = [&socket_]() -> bool { initSocket2Ubox0Bus(); if (socket_ >= socket2UBOX0bus.size()) { std::cerr << "ERROR: socket " << socket_ << " is not found in socket2UBOX0bus. socket2UBOX0bus.size =" << socket2UBOX0bus.size() << std::endl; return false; } return true; }; if (numChannels > 0) { if (initAndCheckSocket2Ubox0Bus()) { auto memBars = getServerMemBars((uint32)m2mPMUs.size(), socket2UBOX0bus[socket_].first, socket2UBOX0bus[socket_].second); for (auto & memBar : memBars) { for (int channel = 0; channel < numChannels; ++channel) { imcPMUs.push_back(createIMCPMU(memBar + SERVER_MC_CH_PMON_BASE_ADDR + channel * SERVER_MC_CH_PMON_STEP, SERVER_MC_CH_PMON_SIZE)); } num_imc_channels.push_back(numChannels); } } } else { switch (cpu_family_model) { case PCM::SPR: case PCM::EMR: { auto & uncorePMUDiscovery = pcm->uncorePMUDiscovery; const auto BoxType = SPR_IMC_BOX_TYPE; if (uncorePMUDiscovery.get()) { const auto numBoxes = uncorePMUDiscovery->getNumBoxes(BoxType, socket_); for (size_t pos = 0; pos < numBoxes; ++pos) { if (uncorePMUDiscovery->getBoxAccessType(BoxType, socket_, pos) == UncorePMUDiscovery::accessTypeEnum::MMIO) { std::vector > CounterControlRegs, CounterValueRegs; const auto n_regs = uncorePMUDiscovery->getBoxNumRegs(BoxType, socket_, pos); auto makeRegister = [](const uint64 rawAddr, const uint32 bits) -> std::shared_ptr { const auto mapSize = SERVER_MC_CH_PMON_SIZE; const auto alignedAddr = rawAddr & ~4095ULL; const auto alignDelta = rawAddr & 4095ULL; try { auto handle = std::make_shared(alignedAddr, mapSize, false); assert(handle.get()); switch (bits) { case 32: return std::make_shared(handle, (size_t)alignDelta); case 64: return std::make_shared(handle, (size_t)alignDelta); } } catch (...) { } return std::shared_ptr(); }; auto boxCtlRegister = makeRegister(uncorePMUDiscovery->getBoxCtlAddr(BoxType, socket_, pos), 32); if (boxCtlRegister.get()) { for (size_t r = 0; r < n_regs; ++r) { CounterControlRegs.push_back(makeRegister(uncorePMUDiscovery->getBoxCtlAddr(BoxType, socket_, pos, r), 32)); CounterValueRegs.push_back(makeRegister(uncorePMUDiscovery->getBoxCtrAddr(BoxType, socket_, pos, r), 64)); } imcPMUs.push_back(UncorePMU(boxCtlRegister, CounterControlRegs, CounterValueRegs, makeRegister(uncorePMUDiscovery->getBoxCtlAddr(BoxType, socket_, pos) + SERVER_MC_CH_PMON_FIXED_CTL_OFFSET, 32), makeRegister(uncorePMUDiscovery->getBoxCtlAddr(BoxType, socket_, pos) + SERVER_MC_CH_PMON_FIXED_CTR_OFFSET, 64))); } } } } if (imcPMUs.empty() == false) { numChannels = 2; for (size_t c = 0; c < imcPMUs.size(); c += numChannels) { num_imc_channels.push_back(numChannels); } } } break; } } auto initBHSiMCPMUsBase = [&](const size_t base, const size_t numChannelsParam) { numChannels = (std::min)(numChannelsParam, m2mPMUs.size()); if (initAndCheckSocket2Ubox0Bus()) { auto memBar = getServerSCFBar(socket2UBOX0bus[socket_].first, socket2UBOX0bus[socket_].second); for (int channel = 0; channel < numChannels; ++channel) { imcPMUs.push_back(createIMCPMU(memBar + base + channel * SERVER_MC_CH_PMON_STEP, SERVER_MC_CH_PMON_SIZE)); num_imc_channels.push_back(1); } } }; auto initBHSiMCPMUs = [&](const size_t numChannelsParam) { initBHSiMCPMUsBase(BHS_MC_CH_PMON_BASE_ADDR, numChannelsParam); }; switch (cpu_family_model) { case PCM::GRR: initBHSiMCPMUs(2); break; case PCM::GNR: case PCM::SRF: initBHSiMCPMUs(12); break; case PCM::GNR_D: initBHSiMCPMUsBase(pcm->getCPUStepping() ? GNR_D_B_MC_CH_PMON_BASE_ADDR : GNR_D_A_MC_CH_PMON_BASE_ADDR, 8); break; } if (imcPMUs.empty()) { std::cerr << "PCM error: no memory controllers found.\n"; throw std::exception(); } if (cpu_family_model == PCM::KNL) { std::vector > edcHandles; for (auto & reg : EDCRegisterLocation) { PciHandleType * handle = createIntelPerfMonDevice(groupnr, iMCbus, reg.first, reg.second, true); if (handle) edcHandles.push_back(std::shared_ptr(handle)); } for (auto & handle : edcHandles) { edcPMUs.push_back( UncorePMU( std::make_shared(handle, KNX_EDC_CH_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTL0_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTL1_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTL2_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTL3_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTR0_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTR1_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTR2_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_CTR3_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_FIXED_CTL_ADDR), std::make_shared(handle, KNX_EDC_CH_PCI_PMON_FIXED_CTR_ADDR)) ); } } if (hbm_m2mPMUs.empty() == false) { // HBM if (initAndCheckSocket2Ubox0Bus()) { const auto bar = getServerSCFBar(socket2UBOX0bus[socket_].first, socket2UBOX0bus[socket_].second); for (size_t box = 0; box < hbm_m2mPMUs.size(); ++box) { for (int channel = 0; channel < 2; ++channel) { edcPMUs.push_back(createIMCPMU(bar + SERVER_HBM_CH_PMON_BASE_ADDR + box * SERVER_HBM_BOX_PMON_STEP + channel * SERVER_HBM_CH_PMON_STEP, SERVER_HBM_CH_PMON_SIZE)); } } } } std::vector > m3upiHandles; if (UPIbus >= 0) { for (auto& reg : M3UPIRegisterLocation) { PciHandleType* handle = createIntelPerfMonDevice(groupnr, UPIbus, reg.first, reg.second, true); if (handle) m3upiHandles.push_back(std::shared_ptr(handle)); } } for (auto& handle : m3upiHandles) { switch (cpu_family_model) { case PCM::ICX: case PCM::SPR: case PCM::EMR: m3upiPMUs.push_back( UncorePMU( std::make_shared(handle, ICX_M3UPI_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTL0_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTL1_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTL2_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTL3_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTR0_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTR1_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTR2_ADDR), std::make_shared(handle, ICX_M3UPI_PCI_PMON_CTR3_ADDR) ) ); break; case PCM::GNR: case PCM::SRF: m3upiPMUs.push_back( UncorePMU( std::make_shared(handle, BHS_M3UPI_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTL0_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTL1_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTL2_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTL3_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTR0_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTR1_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTR2_ADDR), std::make_shared(handle, BHS_M3UPI_PCI_PMON_CTR3_ADDR) ) ); break; default: m3upiPMUs.push_back( UncorePMU( std::make_shared(handle, M3UPI_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, M3UPI_PCI_PMON_CTL0_ADDR), std::make_shared(handle, M3UPI_PCI_PMON_CTL1_ADDR), std::make_shared(handle, M3UPI_PCI_PMON_CTL2_ADDR), std::shared_ptr(), std::make_shared(handle, M3UPI_PCI_PMON_CTR0_ADDR), std::make_shared(handle, M3UPI_PCI_PMON_CTR1_ADDR), std::make_shared(handle, M3UPI_PCI_PMON_CTR2_ADDR), std::shared_ptr() ) ); } } { std::vector > haHandles; for (auto & reg : HARegisterLocation) { auto handle = createIntelPerfMonDevice(groupnr, iMCbus, reg.first, reg.second, true); if (handle) haHandles.push_back(std::shared_ptr(handle)); } for (auto & handle : haHandles) { haPMUs.push_back( UncorePMU( std::make_shared(handle, XPF_HA_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTL0_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTL1_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTL2_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTL3_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTR0_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTR1_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTR2_ADDR), std::make_shared(handle, XPF_HA_PCI_PMON_CTR3_ADDR) ) ); } } if (pcm->getNumSockets() == 1) { /* * For single socket systems, do not worry at all about QPI ports. This * eliminates QPI LL programming error messages on single socket systems * with BIOS that hides QPI performance counting PCI functions. It also * eliminates register programming that is not needed since no QPI traffic * is possible with single socket systems. */ xpiPMUs.clear(); return; } #ifdef PCM_NOQPI xpiPMUs.clear(); std::cerr << getNumMC() << " memory controllers detected with total number of " << imcPMUs.size() << " channels. " << m2mPMUs.size() << " M2M (mesh to memory) blocks detected. " << haPMUs.size() << " Home Agents detected. " << m3upiPMUs.size() << " M3UPI blocks detected. " "\n"; return; #endif if (pcm->getNumSockets() <= 4 && safe_getenv("PCM_NO_UPILL_DISCOVERY") != std::string("1")) { switch (cpu_family_model) { // don't use the discovery on SPR to work-around the issue // mentioned in https://lore.kernel.org/lkml/20221129191023.936738-1-kan.liang@linux.intel.com/T/ case PCM::EMR: { std::cerr << "INFO: Trying to detect UPILL PMU through uncore PMU discovery..\n"; pcm->getPCICFGPMUsFromDiscovery(SPR_UPILL_BOX_TYPE, socket_, [this](const UncorePMU & pmu) { xpiPMUs.push_back(pmu); }); } break; } } std::vector > qpiLLHandles; auto xPI = pcm->xPI(); try { if (xpiPMUs.empty()) for (size_t i = 0; i < XPIRegisterLocation.size(); ++i) { PciHandleType * handle = createIntelPerfMonDevice(groupnr, UPIbus, XPIRegisterLocation[i].first, XPIRegisterLocation[i].second, true); if (handle) qpiLLHandles.push_back(std::shared_ptr(handle)); else { if (i == 0 || i == 1) { std::cerr << "ERROR: " << xPI << " LL monitoring device (" << std::hex << groupnr << ":" << UPIbus << ":" << XPIRegisterLocation[i].first << ":" << XPIRegisterLocation[i].second << ") is missing. The " << xPI << " statistics will be incomplete or missing." << std::dec << "\n"; } else if (pcm->getCPUBrandString().find("E7") != std::string::npos) // Xeon E7 { std::cerr << "ERROR: " << xPI << " LL performance monitoring device for the third " << xPI << " link was not found on " << pcm->getCPUBrandString() << " processor in socket " << socket_ << ". Possibly BIOS hides the device. The " << xPI << " statistics will be incomplete or missing.\n"; } } } } catch (...) { std::cerr << "PCM Error: can not create " << xPI << " LL handles.\n"; throw std::exception(); } if (xpiPMUs.empty()) for (auto & handle : qpiLLHandles) { switch (cpu_family_model) { case PCM::SKX: xpiPMUs.push_back( UncorePMU( std::make_shared(handle, U_L_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTL0_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTL1_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTL2_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTL3_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTR0_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTR1_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTR2_ADDR), std::make_shared(handle, U_L_PCI_PMON_CTR3_ADDR) ) ); break; case PCM::ICX: xpiPMUs.push_back( UncorePMU( std::make_shared(handle, ICX_UPI_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTL0_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTL1_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTL2_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTL3_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTR0_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTR1_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTR2_ADDR), std::make_shared(handle, ICX_UPI_PCI_PMON_CTR3_ADDR) ) ); break; case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::SRF: xpiPMUs.push_back( UncorePMU( std::make_shared(handle, SPR_UPI_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, SPR_UPI_PCI_PMON_CTL0_ADDR + 8*0), std::make_shared(handle, SPR_UPI_PCI_PMON_CTL0_ADDR + 8*1), std::make_shared(handle, SPR_UPI_PCI_PMON_CTL0_ADDR + 8*2), std::make_shared(handle, SPR_UPI_PCI_PMON_CTL0_ADDR + 8*3), std::make_shared(handle, SPR_UPI_PCI_PMON_CTR0_ADDR + 8*0), std::make_shared(handle, SPR_UPI_PCI_PMON_CTR0_ADDR + 8*1), std::make_shared(handle, SPR_UPI_PCI_PMON_CTR0_ADDR + 8*2), std::make_shared(handle, SPR_UPI_PCI_PMON_CTR0_ADDR + 8*3) ) ); break; default: xpiPMUs.push_back( UncorePMU( std::make_shared(handle, Q_P_PCI_PMON_BOX_CTL_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTL0_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTL1_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTL2_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTL3_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTR0_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTR1_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTR2_ADDR), std::make_shared(handle, Q_P_PCI_PMON_CTR3_ADDR) ) ); } } } bool ServerUncorePMUs::HBMAvailable() const { return edcPMUs.empty() == false; } #ifdef PCM_USE_PERF class PerfVirtualFilterRegister; class PerfVirtualControlRegister : public HWRegister { friend class PerfVirtualCounterRegister; friend class PerfVirtualFilterRegister; friend class IDXPerfVirtualFilterRegister; int fd; int socket; int pmuID; perf_event_attr event; bool fixed; void close() { if (fd >= 0) { ::close(fd); fd = -1; } } PerfVirtualControlRegister(const PerfVirtualControlRegister &) = delete; PerfVirtualControlRegister & operator = (const PerfVirtualControlRegister &) = delete; public: PerfVirtualControlRegister(int socket_, int pmuID_, bool fixed_ = false) : fd(-1), socket(socket_), pmuID(pmuID_), fixed(fixed_) { event = PCM_init_perf_event_attr(false); event.type = pmuID; } void operator = (uint64 val) override { close(); event.config = fixed ? 0xff : val; const auto core = PCM::getInstance()->socketRefCore[socket]; if ((fd = syscall(SYS_perf_event_open, &event, -1, core, -1, 0)) <= 0) { std::cerr << "Linux Perf: Error on programming PMU " << pmuID << ": " << strerror(errno) << "\n"; std::cerr << "config: 0x" << std::hex << event.config << " config1: 0x" << event.config1 << " config2: 0x" << event.config2 << std::dec << "\n"; if (errno == 24) std::cerr << PCM_ULIMIT_RECOMMENDATION; return; } } operator uint64 () override { return event.config; } ~PerfVirtualControlRegister() { close(); } int getFD() const { return fd; } int getPMUID() const { return pmuID; } }; class PerfVirtualCounterRegister : public HWRegister { std::shared_ptr controlReg; public: PerfVirtualCounterRegister(const std::shared_ptr & controlReg_) : controlReg(controlReg_) { } void operator = (uint64 /* val */) override { // no-op } operator uint64 () override { uint64 result = 0; if (controlReg.get() && (controlReg->getFD() >= 0)) { int status = ::read(controlReg->getFD(), &result, sizeof(result)); if (status != sizeof(result)) { std::cerr << "PCM Error: failed to read from Linux perf handle " << controlReg->getFD() << " PMU " << controlReg->getPMUID() << "\n"; } } return result; } }; class PerfVirtualFilterRegister : public HWRegister { uint64 lastValue; std::array, 4> controlRegs; int filterNr; public: PerfVirtualFilterRegister(std::array, 4> & controlRegs_, int filterNr_) : lastValue(0), controlRegs(controlRegs_), filterNr(filterNr_) { } void operator = (uint64 val) override { lastValue = val; for (auto & ctl: controlRegs) { union { uint64 config1; uint32 config1HL[2]; } cvt; cvt.config1 = ctl->event.config1; cvt.config1HL[filterNr] = val; ctl->event.config1 = cvt.config1; } } operator uint64 () override { return lastValue; } }; class IDXPerfVirtualFilterRegister : public HWRegister { uint64 lastValue; std::shared_ptr controlReg; int filterNr; public: IDXPerfVirtualFilterRegister(std::shared_ptr controlReg_, int filterNr_) : lastValue(0), controlReg(controlReg_), filterNr(filterNr_) { } void operator = (uint64 val) override { lastValue = val; /* struct { u64 wq:32; u64 tc:8; u64 pg_sz:4; u64 xfer_sz:8; u64 eng:8; } filter_cfg; */ switch (filterNr) { case 0: //FLT_WQ controlReg->event.config1 = ((controlReg->event.config1 & 0xFFFFFFF00000000) | (val & 0xFFFFFFFF)); break; case 1: //FLT_TC controlReg->event.config1 = ((controlReg->event.config1 & 0xFFFFF00FFFFFFFF) | ((val & 0xFF) << 32)); break; case 2: //FLT_PG_SZ controlReg->event.config1 = ((controlReg->event.config1 & 0xFFFF0FFFFFFFFFF) | ((val & 0xF) << 40)); break; case 3: //FLT_XFER_SZ controlReg->event.config1 = ((controlReg->event.config1 & 0xFF00FFFFFFFFFFF) | ((val & 0xFF) << 44)); break; case 4: //FLT_ENG controlReg->event.config1 = ((controlReg->event.config1 & 0x00FFFFFFFFFFFFF) | ((val & 0xFF) << 52)); break; default: break; } } operator uint64 () override { return lastValue; } }; std::vector enumeratePerfPMUs(const std::string & type, int max_id) { auto getPerfPMUID = [](const std::string & type, int num) { int id = -1; std::ostringstream pmuIDPath(std::ostringstream::out); pmuIDPath << std::string("/sys/bus/event_source/devices/uncore_") << type; if (num != -1) { pmuIDPath << "_" << num; } pmuIDPath << "/type"; const std::string pmuIDStr = readSysFS(pmuIDPath.str().c_str(), true); if (pmuIDStr.size()) { id = std::atoi(pmuIDStr.c_str()); } return id; }; std::vector ids; for (int i = -1; i < max_id; ++i) { int pmuID = getPerfPMUID(type, i); if (pmuID > 0) { DBG(2, type , " pmu id " , pmuID , " found"); ids.push_back(pmuID); } } return ids; } void populatePerfPMUs(unsigned socket_, const std::vector & ids, std::vector & pmus, bool fixed, bool filter0, bool filter1) { for (const auto & id : ids) { std::array, 4> controlRegs = { std::make_shared(socket_, id), std::make_shared(socket_, id), std::make_shared(socket_, id), std::make_shared(socket_, id) }; std::shared_ptr counterReg0 = std::make_shared(controlRegs[0]); std::shared_ptr counterReg1 = std::make_shared(controlRegs[1]); std::shared_ptr counterReg2 = std::make_shared(controlRegs[2]); std::shared_ptr counterReg3 = std::make_shared(controlRegs[3]); std::shared_ptr fixedControlReg = std::make_shared(socket_, id, true); std::shared_ptr fixedCounterReg = std::make_shared(fixedControlReg); std::shared_ptr filterReg0 = std::make_shared(controlRegs, 0); std::shared_ptr filterReg1 = std::make_shared(controlRegs, 1); pmus.push_back( UncorePMU( std::make_shared(), controlRegs[0], controlRegs[1], controlRegs[2], controlRegs[3], counterReg0, counterReg1, counterReg2, counterReg3, fixed ? fixedControlReg : std::shared_ptr(), fixed ? fixedCounterReg : std::shared_ptr(), filter0 ? filterReg0 : std::shared_ptr(), filter1 ? filterReg1 : std::shared_ptr() ) ); } } void populatePerfPMUs(unsigned socket_, const std::vector& ids, std::vector& pmus, bool fixed, bool filter0, bool filter1) { for (const auto& id : ids) { std::array, 4> controlRegs = { std::make_shared(socket_, id), std::make_shared(socket_, id), std::make_shared(socket_, id), std::make_shared(socket_, id) }; std::shared_ptr counterReg0 = std::make_shared(controlRegs[0]); std::shared_ptr counterReg1 = std::make_shared(controlRegs[1]); std::shared_ptr counterReg2 = std::make_shared(controlRegs[2]); std::shared_ptr counterReg3 = std::make_shared(controlRegs[3]); std::shared_ptr fixedControlReg = std::make_shared(socket_, id, true); std::shared_ptr fixedCounterReg = std::make_shared(fixedControlReg); std::shared_ptr filterReg0 = std::make_shared(controlRegs, 0); std::shared_ptr filterReg1 = std::make_shared(controlRegs, 1); pmus.push_back( std::make_shared( std::make_shared(), controlRegs[0], controlRegs[1], controlRegs[2], controlRegs[3], counterReg0, counterReg1, counterReg2, counterReg3, fixed ? fixedControlReg : std::shared_ptr(), fixed ? fixedCounterReg : std::shared_ptr(), filter0 ? filterReg0 : std::shared_ptr(), filter1 ? filterReg1 : std::shared_ptr() ) ); } } std::vector > enumerateIDXPerfPMUs(const std::string & type, int max_id) { uint32 numaNode=0xff; auto getPerfPMUID = [](const std::string & type, int num) { int id = -1; std::ostringstream pmuIDPath(std::ostringstream::out); pmuIDPath << std::string("/sys/bus/event_source/devices/") << type; if (num != -1) { pmuIDPath << num; } pmuIDPath << "/type"; const std::string pmuIDStr = readSysFS(pmuIDPath.str().c_str(), true); if (pmuIDStr.size()) { id = std::atoi(pmuIDStr.c_str()); } return id; }; //Enumurate IDX devices by linux sysfs scan std::vector > ids; for (int i = -1; i < max_id; ++i) { int pmuID = getPerfPMUID(type, i); if (pmuID > 0) { numaNode = 0xff; std::ostringstream devNumaNodePath(std::ostringstream::out); devNumaNodePath << std::string("/sys/bus/dsa/devices/") << type << i << "/numa_node"; const std::string devNumaNodeStr = readSysFS(devNumaNodePath.str().c_str(), true); if (devNumaNodeStr.size()) { numaNode = std::atoi(devNumaNodeStr.c_str()); if (numaNode == (std::numeric_limits::max)()) { numaNode = 0xff; //translate to special value for numa disable case. } } DBG(2, "IDX DEBUG: " , type , " pmu id " , pmuID , " found"); DBG(2, "IDX DEBUG: numa node file path=" , devNumaNodePath.str().c_str() , ", value=" , numaNode); ids.push_back(std::make_pair(pmuID, numaNode)); } } return ids; } void populateIDXPerfPMUs(unsigned socket_, const std::vector > & ids, std::vector & pmus) { for (const auto & id : ids) { uint32 n_regs = SPR_IDX_ACCEL_COUNTER_MAX_NUM; std::vector > CounterControlRegs; std::vector > CounterValueRegs; std::vector > CounterFilterWQRegs, CounterFilterENGRegs, CounterFilterTCRegs, CounterFilterPGSZRegs, CounterFilterXFERSZRegs; for (size_t r = 0; r < n_regs; ++r) { auto CounterControlReg = std::make_shared(socket_, id.first); CounterControlRegs.push_back(CounterControlReg); CounterValueRegs.push_back(std::make_shared(CounterControlReg)); CounterFilterWQRegs.push_back(std::make_shared(CounterControlReg, 0)); CounterFilterTCRegs.push_back(std::make_shared(CounterControlReg, 1)); CounterFilterPGSZRegs.push_back(std::make_shared(CounterControlReg, 2)); CounterFilterXFERSZRegs.push_back(std::make_shared(CounterControlReg, 3)); CounterFilterENGRegs.push_back(std::make_shared(CounterControlReg, 4)); } pmus.push_back( IDX_PMU( true, id.second, 0xff,//No support of socket location in perf driver mode. std::make_shared(), std::make_shared(), std::make_shared(), CounterControlRegs, CounterValueRegs, CounterFilterWQRegs, CounterFilterENGRegs, CounterFilterTCRegs, CounterFilterPGSZRegs, CounterFilterXFERSZRegs )); } } #endif void ServerUncorePMUs::initPerf(uint32 socket_, const PCM * /*pcm*/) { #ifdef PCM_USE_PERF auto imcIDs = enumeratePerfPMUs("imc", 100); auto m2mIDs = enumeratePerfPMUs("m2m", 100); auto haIDs = enumeratePerfPMUs("ha", 100); auto numMemControllers = std::max(m2mIDs.size(), haIDs.size()); for (size_t i = 0; i < numMemControllers; ++i) { const int channelsPerController = imcIDs.size() / numMemControllers; num_imc_channels.push_back(channelsPerController); } populatePerfPMUs(socket_, imcIDs, imcPMUs, true); populatePerfPMUs(socket_, m2mIDs, m2mPMUs, false); populatePerfPMUs(socket_, enumeratePerfPMUs("qpi", 100), xpiPMUs, false); populatePerfPMUs(socket_, enumeratePerfPMUs("upi", 100), xpiPMUs, false); populatePerfPMUs(socket_, enumeratePerfPMUs("m3upi", 100), m3upiPMUs, false); populatePerfPMUs(socket_, haIDs, haPMUs, false); #endif } size_t ServerUncorePMUs::getNumMCChannels(const uint32 controller) const { if (controller < num_imc_channels.size()) { return num_imc_channels[controller]; } return 0; } ServerUncorePMUs::~ServerUncorePMUs() { } void ServerUncorePMUs::programServerUncoreMemoryMetrics(const ServerUncoreMemoryMetrics & metrics, const int rankA, const int rankB) { switch (metrics) { case PartialWrites: case Pmem: case PmemMemoryMode: case PmemMixedMode: break; default: std::cerr << "PCM Error: unknown memory metrics: " << metrics << "\n"; return; } PCM * pcm = PCM::getInstance(); uint32 MCCntConfig[4] = {0,0,0,0}; uint32 EDCCntConfig[4] = {0,0,0,0}; if(rankA < 0 && rankB < 0) { auto setEvents2_3 = [&](const uint32 partial_write_event) { auto noPmem = [&pcm]() -> bool { if (pcm->PMMTrafficMetricsAvailable() == false) { std::cerr << "PCM Error: PMM/Pmem metrics are not available on your platform\n"; return true; } return false; }; switch (metrics) { case PmemMemoryMode: case PmemMixedMode: if (noPmem()) return false; MCCntConfig[EventPosition::MM_MISS_CLEAN] = MC_CH_PCI_PMON_CTL_EVENT(0xd3) + MC_CH_PCI_PMON_CTL_UMASK(2); // monitor TAGCHK.MISS_CLEAN on counter 2 MCCntConfig[EventPosition::MM_MISS_DIRTY] = MC_CH_PCI_PMON_CTL_EVENT(0xd3) + MC_CH_PCI_PMON_CTL_UMASK(4); // monitor TAGCHK.MISS_DIRTY on counter 3 break; case Pmem: if (noPmem()) return false; MCCntConfig[EventPosition::PMM_READ] = MC_CH_PCI_PMON_CTL_EVENT(0xe3); // monitor PMM_RDQ_REQUESTS on counter 2 MCCntConfig[EventPosition::PMM_WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0xe7); // monitor PMM_WPQ_REQUESTS on counter 3 break; case PartialWrites: MCCntConfig[EventPosition::PARTIAL] = partial_write_event; break; default: std::cerr << "PCM Error: unknown metrics: " << metrics << "\n"; return false; } return true; }; switch(cpu_family_model) { case PCM::KNL: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x03) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: CAS.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x03) + MC_CH_PCI_PMON_CTL_UMASK(2); // monitor reads on counter 1: CAS.WR EDCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x01) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: RPQ EDCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x02) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 1: WPQ break; case PCM::SNOWRIDGE: case PCM::ICX: if (metrics == PmemMemoryMode) { MCCntConfig[EventPosition::NM_HIT] = MC_CH_PCI_PMON_CTL_EVENT(0xd3) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: UNC_M_TAGCHK.HIT } else { MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(0x0f); // monitor reads on counter 0: CAS_COUNT.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(0x30); // monitor writes on counter 1: CAS_COUNT.WR } if (setEvents2_3(MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(0x0c)) == false) // monitor partial writes on counter 2: CAS_COUNT.RD_UNDERFILL { return; } break; case PCM::SPR: case PCM::EMR: { EDCCntConfig[EventPosition::READ] = MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 0: CAS_COUNT.RD EDCCntConfig[EventPosition::WRITE] = MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 1: CAS_COUNT.WR } if (setEvents2_3(MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xcc)) == false) // monitor partial writes on counter 2: CAS_COUNT.RD_UNDERFILL { return; } break; case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: if (metrics == PmemMemoryMode) { std::cerr << "PCM Error: PMM/Pmem metrics are not available on your platform\n"; return; } else { MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 0: CAS_COUNT_SCH0.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 1: CAS_COUNT_SCH0.WR MCCntConfig[EventPosition::READ2] = MC_CH_PCI_PMON_CTL_EVENT(0x06) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 2: CAS_COUNT_SCH1.RD MCCntConfig[EventPosition::WRITE2] = MC_CH_PCI_PMON_CTL_EVENT(0x06) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 3: CAS_COUNT_SCH1.WR } break; default: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(3); // monitor reads on counter 0: CAS_COUNT.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(12); // monitor writes on counter 1: CAS_COUNT.WR if (setEvents2_3(MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(2)) == false) // monitor partial writes on counter 2: CAS_COUNT.RD_UNDERFILL { return; } } } else { if (rankA < 0 || rankA > 7) { std::cerr << "PCM Error: invalid rankA value: " << rankA << "\n"; return; } switch(cpu_family_model) { case PCM::IVYTOWN: MCCntConfig[EventPosition::READ_RANK_A] = MC_CH_PCI_PMON_CTL_EVENT((0xb0 + rankA)) + MC_CH_PCI_PMON_CTL_UMASK(0xff); // RD_CAS_RANK(rankA) all banks MCCntConfig[EventPosition::WRITE_RANK_A] = MC_CH_PCI_PMON_CTL_EVENT((0xb8 + rankA)) + MC_CH_PCI_PMON_CTL_UMASK(0xff); // WR_CAS_RANK(rankA) all banks if (rankB >= 0 && rankB <= 7) { MCCntConfig[EventPosition::READ_RANK_B] = MC_CH_PCI_PMON_CTL_EVENT((0xb0 + rankB)) + MC_CH_PCI_PMON_CTL_UMASK(0xff); // RD_CAS_RANK(rankB) all banks MCCntConfig[EventPosition::WRITE_RANK_B] = MC_CH_PCI_PMON_CTL_EVENT((0xb8 + rankB)) + MC_CH_PCI_PMON_CTL_UMASK(0xff); // WR_CAS_RANK(rankB) all banks } break; case PCM::HASWELLX: case PCM::BDX_DE: case PCM::BDX: case PCM::SKX: MCCntConfig[EventPosition::READ_RANK_A] = MC_CH_PCI_PMON_CTL_EVENT((0xb0 + rankA)) + MC_CH_PCI_PMON_CTL_UMASK(16); // RD_CAS_RANK(rankA) all banks MCCntConfig[EventPosition::WRITE_RANK_A] = MC_CH_PCI_PMON_CTL_EVENT((0xb8 + rankA)) + MC_CH_PCI_PMON_CTL_UMASK(16); // WR_CAS_RANK(rankA) all banks if (rankB >= 0 && rankB <= 7) { MCCntConfig[EventPosition::READ_RANK_B] = MC_CH_PCI_PMON_CTL_EVENT((0xb0 + rankB)) + MC_CH_PCI_PMON_CTL_UMASK(16); // RD_CAS_RANK(rankB) all banks MCCntConfig[EventPosition::WRITE_RANK_B] = MC_CH_PCI_PMON_CTL_EVENT((0xb8 + rankB)) + MC_CH_PCI_PMON_CTL_UMASK(16); // WR_CAS_RANK(rankB) all banks } break; case PCM::KNL: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x03) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: CAS.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x03) + MC_CH_PCI_PMON_CTL_UMASK(2); // monitor reads on counter 1: CAS.WR EDCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x01) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: RPQ EDCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x02) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 1: WPQ break; default: std::cerr << "PCM Error: your processor " << pcm->getCPUBrandString() << " ID 0x" << std::hex << cpu_family_model << std::dec << " does not support the required performance events \n"; return; } } programIMC(MCCntConfig); if (pcm->HBMmemoryTrafficMetricsAvailable()) programEDC(EDCCntConfig); programM2M(); xpiPMUs.clear(); // no QPI events used return; } void ServerUncorePMUs::program() { PCM * pcm = PCM::getInstance(); uint32 MCCntConfig[4] = {0, 0, 0, 0}; uint32 EDCCntConfig[4] = {0, 0, 0, 0}; switch(cpu_family_model) { case PCM::KNL: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x03) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: CAS_COUNT.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x03) + MC_CH_PCI_PMON_CTL_UMASK(2); // monitor writes on counter 1: CAS_COUNT.WR EDCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x01) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 0: RPQ EDCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x02) + MC_CH_PCI_PMON_CTL_UMASK(1); // monitor reads on counter 1: WPQ break; case PCM::SNOWRIDGE: case PCM::ICX: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(0x0f); // monitor reads on counter 0: CAS_COUNT.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(0x30); // monitor writes on counter 1: CAS_COUNT.WR break; case PCM::SPR: case PCM::EMR: EDCCntConfig[EventPosition::READ] = MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 0: CAS_COUNT.RD EDCCntConfig[EventPosition::WRITE] = MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 1: CAS_COUNT.WR break; case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 0: CAS_COUNT_SCH0.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 1: CAS_COUNT_SCH0.WR MCCntConfig[EventPosition::READ2] = MC_CH_PCI_PMON_CTL_EVENT(0x06) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 2: CAS_COUNT_SCH1.RD MCCntConfig[EventPosition::WRITE2] = MC_CH_PCI_PMON_CTL_EVENT(0x06) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 3: CAS_COUNT_SCH1.WR break; default: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(3); // monitor reads on counter 0: CAS_COUNT.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x04) + MC_CH_PCI_PMON_CTL_UMASK(12); // monitor writes on counter 1: CAS_COUNT.WR } if (pcm->PMMTrafficMetricsAvailable()) { MCCntConfig[EventPosition::PMM_READ] = MC_CH_PCI_PMON_CTL_EVENT(0xe3); // monitor PMM_RDQ_REQUESTS on counter 2 MCCntConfig[EventPosition::PMM_WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0xe7); // monitor PMM_WPQ_REQUESTS on counter 3 } programIMC(MCCntConfig); if (pcm->HBMmemoryTrafficMetricsAvailable()) programEDC(EDCCntConfig); programM2M(); uint32 event[4]; if (PCM::hasUPI(cpu_family_model)) { // monitor TxL0_POWER_CYCLES event[0] = Q_P_PCI_PMON_CTL_EVENT(0x26); // monitor RxL_FLITS.ALL_DATA on counter 1 event[1] = Q_P_PCI_PMON_CTL_EVENT(0x03) + Q_P_PCI_PMON_CTL_UMASK(0xF); // monitor TxL_FLITS.NON_DATA+ALL_DATA on counter 2 event[2] = Q_P_PCI_PMON_CTL_EVENT(0x02) + Q_P_PCI_PMON_CTL_UMASK((0x97|0x0F)); // monitor UPI CLOCKTICKS event[ServerUncoreCounterState::EventPosition::xPI_CLOCKTICKS] = Q_P_PCI_PMON_CTL_EVENT(0x01); } else { // monitor DRS data received on counter 0: RxL_FLITS_G1.DRS_DATA event[0] = Q_P_PCI_PMON_CTL_EVENT(0x02) + Q_P_PCI_PMON_CTL_EVENT_EXT + Q_P_PCI_PMON_CTL_UMASK(8); // monitor NCB data received on counter 1: RxL_FLITS_G2.NCB_DATA event[1] = Q_P_PCI_PMON_CTL_EVENT(0x03) + Q_P_PCI_PMON_CTL_EVENT_EXT + Q_P_PCI_PMON_CTL_UMASK(4); // monitor outgoing data+nondata flits on counter 2: TxL_FLITS_G0.DATA + TxL_FLITS_G0.NON_DATA event[2] = Q_P_PCI_PMON_CTL_EVENT(0x00) + Q_P_PCI_PMON_CTL_UMASK(6); // monitor QPI clocks event[ServerUncoreCounterState::EventPosition::xPI_CLOCKTICKS] = Q_P_PCI_PMON_CTL_EVENT(0x14); // QPI clocks (CLOCKTICKS) } programXPI(event); programHA(); } void ServerUncorePMUs::programXPI(const uint32 * event) { const uint32 extra = PCM::hasUPI(cpu_family_model) ? UNC_PMON_UNIT_CTL_RSV : UNC_PMON_UNIT_CTL_FRZ_EN; for (uint32 i = 0; i < (uint32)xpiPMUs.size(); ++i) { // QPI LL PMU if (xpiPMUs[i].initFreeze(extra, " Please see BIOS options to enable the export of QPI/UPI performance monitoring devices (devices 8 and 9: function 2).\n") == false) { std::cout << "Link " << (i + 1) << " is disabled\n"; continue; } PCM::program(xpiPMUs[i], event, event + 4, extra); } cleanupQPIHandles(); } void ServerUncorePMUs::cleanupQPIHandles() { for(auto i = xpiPMUs.begin(); i != xpiPMUs.end(); ++i) { if (!i->valid()) { xpiPMUs.erase(i); cleanupQPIHandles(); return; } } } void ServerUncorePMUs::cleanupPMUs() { for (auto & pmu : xpiPMUs) { pmu.cleanup(); } for (auto & pmu : imcPMUs) { pmu.cleanup(); } for (auto & pmu : edcPMUs) { pmu.cleanup(); } for (auto & pmu : m2mPMUs) { pmu.cleanup(); } for (auto & pmu : haPMUs) { pmu.cleanup(); } } uint64 ServerUncorePMUs::getImcReads() { return getImcReadsForChannels((uint32)0, (uint32)imcPMUs.size()); } uint64 ServerUncorePMUs::getImcReadsForController(uint32 controller) { assert(controller < num_imc_channels.size()); uint32 beginChannel = 0; for (uint32 i = 0; i < controller; ++i) { beginChannel += num_imc_channels[i]; } const uint32 endChannel = beginChannel + num_imc_channels[controller]; return getImcReadsForChannels(beginChannel, endChannel); } uint64 ServerUncorePMUs::getImcReadsForChannels(uint32 beginChannel, uint32 endChannel) { uint64 result = 0; for (uint32 i = beginChannel; i < endChannel && i < imcPMUs.size(); ++i) { result += getMCCounter(i, EventPosition::READ); switch (cpu_family_model) { case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: result += getMCCounter(i, EventPosition::READ2); break; } } return result; } uint64 ServerUncorePMUs::getImcWrites() { uint64 result = 0; for (uint32 i = 0; i < (uint32)imcPMUs.size(); ++i) { result += getMCCounter(i, EventPosition::WRITE); switch (cpu_family_model) { case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: result += getMCCounter(i, EventPosition::WRITE2); break; } } return result; } uint64 ServerUncorePMUs::getNMHits() { uint64 result = 0; for (uint32 i = 0; i < (uint32)m2mPMUs.size(); ++i) { result += getM2MCounter(i, EventPosition::NM_HIT); } return result; } uint64 ServerUncorePMUs::getNMMisses() { uint64 result = 0; for (uint32 i = 0; i < (uint32)m2mPMUs.size(); ++i) { result += getM2MCounter(i, EventPosition::MM_MISS_CLEAN) + getM2MCounter(i, EventPosition::MM_MISS_DIRTY); } return result; } uint64 ServerUncorePMUs::getPMMReads() { uint64 result = 0; for (uint32 i = 0; i < (uint32)m2mPMUs.size(); ++i) { result += getM2MCounter(i, EventPosition::PMM_READ); } return result; } uint64 ServerUncorePMUs::getPMMWrites() { uint64 result = 0; for (uint32 i = 0; i < (uint32)m2mPMUs.size(); ++i) { result += getM2MCounter(i, EventPosition::PMM_WRITE); } return result; } uint64 ServerUncorePMUs::getEdcReads() { uint64 result = 0; for (auto & pmu: edcPMUs) { result += *pmu.counterValue[EventPosition::READ]; } return result; } uint64 ServerUncorePMUs::getEdcWrites() { uint64 result = 0; for (auto & pmu : edcPMUs) { result += *pmu.counterValue[EventPosition::WRITE]; } return result; } uint64 ServerUncorePMUs::getIncomingDataFlits(uint32 port) { uint64 drs = 0, ncb = 0; if (port >= (uint32)xpiPMUs.size()) return 0; if (PCM::hasUPI(cpu_family_model) == false) { drs = *xpiPMUs[port].counterValue[0]; } ncb = *xpiPMUs[port].counterValue[1]; return drs + ncb; } uint64 ServerUncorePMUs::getOutgoingFlits(uint32 port) { return getQPILLCounter(port,2); } uint64 ServerUncorePMUs::getUPIL0TxCycles(uint32 port) { if (PCM::hasUPI(cpu_family_model)) return getQPILLCounter(port,0); return 0; } void ServerUncorePMUs::program_power_metrics(int mc_profile) { uint32 xPIEvents[4] = { 0,0,0,0 }; xPIEvents[ServerUncoreCounterState::EventPosition::xPI_TxL0P_POWER_CYCLES] = (uint32)Q_P_PCI_PMON_CTL_EVENT((PCM::hasUPI(cpu_family_model) ? 0x27 : 0x0D)); // L0p Tx Cycles (TxL0P_POWER_CYCLES) xPIEvents[ServerUncoreCounterState::EventPosition::xPI_L1_POWER_CYCLES] = (uint32)Q_P_PCI_PMON_CTL_EVENT((PCM::hasUPI(cpu_family_model) ? 0x21 : 0x12)); // L1 Cycles (L1_POWER_CYCLES) xPIEvents[ServerUncoreCounterState::EventPosition::xPI_CLOCKTICKS] = (uint32)Q_P_PCI_PMON_CTL_EVENT((PCM::hasUPI(cpu_family_model) ? 0x01 : 0x14)); // QPI/UPI clocks (CLOCKTICKS) programXPI(xPIEvents); uint32 MCCntConfig[4] = {0,0,0,0}; unsigned int UNC_M_POWER_CKE_CYCLES = 0x83; switch (cpu_family_model) { case PCM::ICX: case PCM::SNOWRIDGE: case PCM::SPR: case PCM::EMR: case PCM::SRF: case PCM::GNR: case PCM::GNR_D: UNC_M_POWER_CKE_CYCLES = 0x47; break; } unsigned int UNC_M_POWER_CHANNEL_PPD_CYCLES = 0x85; switch (cpu_family_model) { case PCM::SRF: case PCM::GNR: case PCM::GNR_D: UNC_M_POWER_CHANNEL_PPD_CYCLES = 0x88; break; } unsigned int UNC_M_SELF_REFRESH_ENTER_SUCCESS_CYCLES_UMASK = 0; switch (cpu_family_model) { case PCM::SRF: case PCM::GNR: case PCM::GNR_D: UNC_M_SELF_REFRESH_ENTER_SUCCESS_CYCLES_UMASK = 0x01; break; } switch(mc_profile) { case 0: // POWER_CKE_CYCLES.RANK0 and POWER_CKE_CYCLES.RANK1 MCCntConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(1) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(1) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; MCCntConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(2) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(2) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; break; case 1: // POWER_CKE_CYCLES.RANK2 and POWER_CKE_CYCLES.RANK3 MCCntConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(4) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(4) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; MCCntConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(8) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(8) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; break; case 2: // POWER_CKE_CYCLES.RANK4 and POWER_CKE_CYCLES.RANK5 MCCntConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x10) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x10) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; MCCntConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x20) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x20) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; break; case 3: // POWER_CKE_CYCLES.RANK6 and POWER_CKE_CYCLES.RANK7 MCCntConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x40) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x40) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; MCCntConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x80) + MC_CH_PCI_PMON_CTL_INVERT + MC_CH_PCI_PMON_CTL_THRESH(1); MCCntConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CKE_CYCLES) + MC_CH_PCI_PMON_CTL_UMASK(0x80) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; break; case 4: // POWER_SELF_REFRESH MCCntConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x43) + MC_CH_PCI_PMON_CTL_UMASK(UNC_M_SELF_REFRESH_ENTER_SUCCESS_CYCLES_UMASK); MCCntConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x43) + MC_CH_PCI_PMON_CTL_UMASK(UNC_M_SELF_REFRESH_ENTER_SUCCESS_CYCLES_UMASK) + MC_CH_PCI_PMON_CTL_THRESH(1) + MC_CH_PCI_PMON_CTL_EDGE_DET; MCCntConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(UNC_M_POWER_CHANNEL_PPD_CYCLES); break; } programIMC(MCCntConfig); } void enableAndResetMCFixedCounter(UncorePMU& pmu) { // enable fixed counter (DRAM clocks) *pmu.fixedCounterControl = MC_CH_PCI_PMON_FIXED_CTL_EN; // reset it *pmu.fixedCounterControl = MC_CH_PCI_PMON_FIXED_CTL_EN + MC_CH_PCI_PMON_FIXED_CTL_RST; } void ServerUncorePMUs::programIMC(const uint32 * MCCntConfig) { const uint32 extraIMC = (cpu_family_model == PCM::SKX)?UNC_PMON_UNIT_CTL_RSV:UNC_PMON_UNIT_CTL_FRZ_EN; for (uint32 i = 0; i < (uint32)imcPMUs.size(); ++i) { // imc PMU imcPMUs[i].initFreeze(extraIMC); enableAndResetMCFixedCounter(imcPMUs[i]); PCM::program(imcPMUs[i], MCCntConfig, MCCntConfig + 4, extraIMC); } } void ServerUncorePMUs::programEDC(const uint32 * EDCCntConfig) { for (uint32 i = 0; i < (uint32)edcPMUs.size(); ++i) { edcPMUs[i].initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); // HBM clocks enabled by default if (cpu_family_model == PCM::KNL) { *edcPMUs[i].fixedCounterControl = EDC_CH_PCI_PMON_FIXED_CTL_EN; } else { enableAndResetMCFixedCounter(edcPMUs[i]); } PCM::program(edcPMUs[i], EDCCntConfig, EDCCntConfig + 4, UNC_PMON_UNIT_CTL_FRZ_EN); } } void ServerUncorePMUs::programM2M() { uint64 cfg[4] = {0, 0, 0, 0}; switch (cpu_family_model) { case PCM::SPR: case PCM::EMR: cfg[EventPosition::M2M_CLOCKTICKS] = M2M_PCI_PMON_CTL_EVENT(0x01); // CLOCKTICKS cfg[EventPosition::PMM_READ] = M2M_PCI_PMON_CTL_EVENT(0x24) + M2M_PCI_PMON_CTL_UMASK(0x20) + UNC_PMON_CTL_UMASK_EXT(0x03); // UNC_M2M_IMC_READS.TO_PMM cfg[EventPosition::PMM_WRITE] = M2M_PCI_PMON_CTL_EVENT(0x25) + M2M_PCI_PMON_CTL_UMASK(0x80) + UNC_PMON_CTL_UMASK_EXT(0x18); // UNC_M2M_IMC_WRITES.TO_PMM break; case PCM::ICX: cfg[EventPosition::NM_HIT] = M2M_PCI_PMON_CTL_EVENT(0x2c) + M2M_PCI_PMON_CTL_UMASK(3); // UNC_M2M_TAG_HIT.NM_DRD_HIT_* events (CLEAN | DIRTY) cfg[EventPosition::M2M_CLOCKTICKS] = 0; // CLOCKTICKS cfg[EventPosition::PMM_READ] = M2M_PCI_PMON_CTL_EVENT(0x37) + M2M_PCI_PMON_CTL_UMASK(0x20) + UNC_PMON_CTL_UMASK_EXT(0x07); // UNC_M2M_IMC_READS.TO_PMM cfg[EventPosition::PMM_WRITE] = M2M_PCI_PMON_CTL_EVENT(0x38) + M2M_PCI_PMON_CTL_UMASK(0x80) + UNC_PMON_CTL_UMASK_EXT(0x1C); // UNC_M2M_IMC_WRITES.TO_PMM break; case PCM::GNR: case PCM::GNR_D: case PCM::SRF: cfg[EventPosition::NM_HIT] = M2M_PCI_PMON_CTL_EVENT(0x1F) + M2M_PCI_PMON_CTL_UMASK(0x0F); // UNC_B2CMI_TAG_HIT.ALL cfg[EventPosition::M2M_CLOCKTICKS] = 0; // CLOCKTICKS cfg[EventPosition::MM_MISS_CLEAN] = M2M_PCI_PMON_CTL_EVENT(0x4B) + M2M_PCI_PMON_CTL_UMASK(0x05); // UNC_B2CMI_TAG_MISS.CLEAN cfg[EventPosition::MM_MISS_DIRTY] = M2M_PCI_PMON_CTL_EVENT(0x4B) + M2M_PCI_PMON_CTL_UMASK(0x0A); // UNC_B2CMI_TAG_MISS.DIRTY break; default: cfg[EventPosition::NM_HIT] = M2M_PCI_PMON_CTL_EVENT(0x2c) + M2M_PCI_PMON_CTL_UMASK(3); // UNC_M2M_TAG_HIT.NM_DRD_HIT_* events (CLEAN | DIRTY) cfg[EventPosition::M2M_CLOCKTICKS] = 0; // CLOCKTICKS cfg[EventPosition::PMM_READ] = M2M_PCI_PMON_CTL_EVENT(0x37) + M2M_PCI_PMON_CTL_UMASK(0x8); // UNC_M2M_IMC_READS.TO_PMM cfg[EventPosition::PMM_WRITE] = M2M_PCI_PMON_CTL_EVENT(0x38) + M2M_PCI_PMON_CTL_UMASK(0x20); // UNC_M2M_IMC_WRITES.TO_PMM } programM2M(cfg); } void ServerUncorePMUs::programM2M(const uint64* M2MCntConfig) { { int i = 0; for (auto & pmu : m2mPMUs) { DBG(3, "programming m2m pmu ", i++ ); pmu.initFreeze(UNC_PMON_UNIT_CTL_RSV); PCM::program(pmu, M2MCntConfig, M2MCntConfig + 4, UNC_PMON_UNIT_CTL_RSV); } } } void ServerUncorePMUs::programM3UPI(const uint32* M3UPICntConfig) { { for (auto& pmu : m3upiPMUs) { pmu.initFreeze(UNC_PMON_UNIT_CTL_RSV); PCM::program(pmu, M3UPICntConfig, M3UPICntConfig + 4, UNC_PMON_UNIT_CTL_RSV); } } } void ServerUncorePMUs::programHA(const uint32 * config) { for (auto & pmu : haPMUs) { pmu.initFreeze(UNC_PMON_UNIT_CTL_RSV); PCM::program(pmu, config, config + 4, UNC_PMON_UNIT_CTL_RSV); } } uint64 ServerUncorePMUs::getHARequests() { uint64 result = 0; for (auto & pmu: haPMUs) { result += *pmu.counterValue[PCM::EventPosition::REQUESTS_ALL]; } return result; } uint64 ServerUncorePMUs::getHALocalRequests() { uint64 result = 0; for (auto & pmu: haPMUs) { result += *pmu.counterValue[PCM::EventPosition::REQUESTS_LOCAL]; } return result; } void ServerUncorePMUs::programHA() { uint32 config[4]; config[0] = 0; config[1] = 0; #ifdef PCM_HA_REQUESTS_READS_ONLY // HA REQUESTS READ: LOCAL + REMOTE config[PCM::EventPosition::REQUESTS_ALL] = HA_PCI_PMON_CTL_EVENT(0x01) + HA_PCI_PMON_CTL_UMASK((1 + 2)); // HA REQUESTS READ: LOCAL ONLY config[PCM::EventPosition::REQUESTS_LOCAL] = HA_PCI_PMON_CTL_EVENT(0x01) + HA_PCI_PMON_CTL_UMASK((1)); #else // HA REQUESTS READ+WRITE+REMOTE+LOCAL config[PCM::EventPosition::REQUESTS_ALL] = HA_PCI_PMON_CTL_EVENT(0x01) + HA_PCI_PMON_CTL_UMASK((1 + 2 + 4 + 8)); // HA REQUESTS READ+WRITE (LOCAL only) config[PCM::EventPosition::REQUESTS_LOCAL] = HA_PCI_PMON_CTL_EVENT(0x01) + HA_PCI_PMON_CTL_UMASK((1 + 4)); #endif programHA(config); } void ServerUncorePMUs::freezeCounters() { for (auto& pmuVector : allPMUs) { for (auto& pmu : *pmuVector) { pmu.freeze((cpu_family_model == PCM::SKX) ? UNC_PMON_UNIT_CTL_RSV : UNC_PMON_UNIT_CTL_FRZ_EN); } } } void ServerUncorePMUs::unfreezeCounters() { for (auto& pmuVector : allPMUs) { for (auto& pmu : *pmuVector) { pmu.unfreeze((cpu_family_model == PCM::SKX) ? UNC_PMON_UNIT_CTL_RSV : UNC_PMON_UNIT_CTL_FRZ_EN); } } } uint64 ServerUncorePMUs::getQPIClocks(uint32 port) { return getQPILLCounter(port, ServerUncoreCounterState::EventPosition::xPI_CLOCKTICKS); } uint64 ServerUncorePMUs::getQPIL0pTxCycles(uint32 port) { return getQPILLCounter(port, ServerUncoreCounterState::EventPosition::xPI_TxL0P_POWER_CYCLES); } uint64 ServerUncorePMUs::getQPIL1Cycles(uint32 port) { return getQPILLCounter(port, ServerUncoreCounterState::EventPosition::xPI_L1_POWER_CYCLES); } uint64 ServerUncorePMUs::getDRAMClocks(uint32 channel) { uint64 result = 0; if (channel < (uint32)imcPMUs.size()) result = *(imcPMUs[channel].fixedCounterValue); DBG(3, "DRAMClocks on channel " , channel , " = " , result); return result; } uint64 ServerUncorePMUs::getHBMClocks(uint32 channel) { uint64 result = 0; if (channel < (uint32)edcPMUs.size()) result = *edcPMUs[channel].fixedCounterValue; DBG(3, "HBMClocks on EDC" , channel , " = " , result); return result; } uint64 ServerUncorePMUs::getPMUCounter(std::vector & pmu, const uint32 id, const uint32 counter) { uint64 result = 0; if (id < (uint32)pmu.size() && counter < 4 && pmu[id].counterValue[counter].get() != nullptr) { result = *(pmu[id].counterValue[counter]); } else { DBG(3, "Invalid ServerUncorePMUs::getPMUCounter(" , id , ", " , counter , ")"); } DBG(3, "ServerUncorePMUs::getPMUCounter(" , id , ", " , counter , ") = " , result); return result; } uint64 ServerUncorePMUs::getHACounter(uint32 id, uint32 counter) { return getPMUCounter(haPMUs, id, counter); } uint64 ServerUncorePMUs::getMCCounter(uint32 channel, uint32 counter) { return getPMUCounter(imcPMUs, channel, counter); } uint64 ServerUncorePMUs::getEDCCounter(uint32 channel, uint32 counter) { return getPMUCounter(edcPMUs, channel, counter); } uint64 ServerUncorePMUs::getM2MCounter(uint32 box, uint32 counter) { return getPMUCounter(m2mPMUs, box, counter); } uint64 ServerUncorePMUs::getQPILLCounter(uint32 port, uint32 counter) { return getPMUCounter(xpiPMUs, port, counter); } uint64 ServerUncorePMUs::getM3UPICounter(uint32 port, uint32 counter) { DBG(3, "ServerUncorePMUs::getM3UPICounter(" , port , ", " , counter , ") = " , getPMUCounter(m3upiPMUs, port, counter)); return getPMUCounter(m3upiPMUs, port, counter); } void ServerUncorePMUs::enableJKTWorkaround(bool enable) { { PciHandleType reg(groupnr,iMCbus,14,0); uint32 value = 0; reg.read32(0x84, &value); if(enable) value |= 2; else value &= (~2); reg.write32(0x84, value); } { PciHandleType reg(groupnr,iMCbus,8,0); uint32 value = 0; reg.read32(0x80, &value); if(enable) value |= 2; else value &= (~2); reg.write32(0x80, value); } { PciHandleType reg(groupnr,iMCbus,9,0); uint32 value = 0; reg.read32(0x80, &value); if(enable) value |= 2; else value &= (~2); reg.write32(0x80, value); } } #define PCM_MEM_CAPACITY (1024ULL*1024ULL*64ULL) // 64 MByte void ServerUncorePMUs::initMemTest(ServerUncorePMUs::MemTestParam & param) { auto & memBufferBlockSize = param.first; auto & memBuffers = param.second; #ifdef __linux__ size_t capacity = PCM_MEM_CAPACITY; char * buffer = (char *)mmap(NULL, capacity, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); if (buffer == MAP_FAILED) { std::cerr << "ERROR: mmap failed\n"; return; } const int64 onlineNodes = (int64)readMaxFromSysFS("/sys/devices/system/node/online"); unsigned long long maxNode = (unsigned long long)(onlineNodes + 1); if (maxNode == 0) { std::cerr << "ERROR: max node is 0 \n"; return; } if (maxNode >= 63) maxNode = 63; const unsigned long long nodeMask = (1ULL << maxNode) - 1ULL; if (0 != syscall(SYS_mbind, buffer, capacity, 3 /* MPOL_INTERLEAVE */, &nodeMask, maxNode, 0)) { std::cerr << "ERROR: mbind failed. nodeMask: " << nodeMask << " maxNode: " << maxNode << "\n"; return; } memBuffers.push_back((uint64 *)buffer); memBufferBlockSize = capacity; #elif defined(_MSC_VER) ULONG HighestNodeNumber; if (!GetNumaHighestNodeNumber(&HighestNodeNumber)) { std::cerr << "ERROR: GetNumaHighestNodeNumber call failed.\n"; return; } memBufferBlockSize = 4096; for (int i = 0; i < PCM_MEM_CAPACITY / memBufferBlockSize; ++i) { LPVOID result = VirtualAllocExNuma( GetCurrentProcess(), NULL, memBufferBlockSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE, i % (HighestNodeNumber + 1) ); if (result == NULL) { std::cerr << "ERROR: " << i << " VirtualAllocExNuma failed.\n"; for (auto& b : memBuffers) { VirtualFree(b, memBufferBlockSize, MEM_RELEASE); } memBuffers.clear(); break; } else { memBuffers.push_back((uint64 *)result); } } #else std::cerr << "ERROR: memory test is not implemented. QPI/UPI speed and utilization metrics may not be reliable.\n"; #endif for (auto& b : memBuffers) std::fill(b, b + (memBufferBlockSize / sizeof(uint64)), 0ULL); } void ServerUncorePMUs::doMemTest(const ServerUncorePMUs::MemTestParam & param) { const auto & memBufferBlockSize = param.first; const auto & memBuffers = param.second; // read and write each cache line once for (auto& b : memBuffers) for (unsigned int i = 0; i < memBufferBlockSize / sizeof(uint64); i += 64 / sizeof(uint64)) { (b[i])++; } } void ServerUncorePMUs::cleanupMemTest(const ServerUncorePMUs::MemTestParam & param) { const auto & memBufferBlockSize = param.first; const auto & memBuffers = param.second; for (auto& b : memBuffers) { #if defined(__linux__) munmap(b, memBufferBlockSize); #elif defined(_MSC_VER) VirtualFree(b, memBufferBlockSize, MEM_RELEASE); #elif defined(__FreeBSD__) || defined(__APPLE__) (void) b; // avoid the unused variable warning (void) memBufferBlockSize; // avoid the unused variable warning #else #endif } } uint64 ServerUncorePMUs::computeQPISpeed(const uint32 core_nr, const int cpufamilymodel) { if(qpi_speed.empty()) { PCM * pcm = PCM::getInstance(); TemporalThreadAffinity aff(core_nr); qpi_speed.resize(getNumQPIPorts()); auto getSpeed = [&] (size_t i) { if (PCM::hasUPI(cpufamilymodel) == false && i == 1) return 0ULL; // link 1 should have the same speed as link 0, skip it uint64 result = 0; if (PCM::hasUPI(cpufamilymodel) == false && i < XPIRegisterLocation.size()) { PciHandleType reg(groupnr,UPIbus, XPIRegisterLocation[i].first, QPI_PORT0_MISC_REGISTER_FUNC_ADDR); uint32 value = 0; reg.read32(QPI_RATE_STATUS_ADDR, &value); value &= 7; // extract lower 3 bits if(value) result = static_cast((4000000000ULL + ((uint64)value)*800000000ULL)*2ULL); } std::unordered_map UPISpeedMap{}; std::pair regBits{}; switch (cpufamilymodel) { case PCM::GNR: case PCM::SRF: UPISpeedMap = { { 0, 2500}, { 1, 12800}, { 2, 14400}, { 3, 16000}, { 8, 20000}, { 9, 24000} }; regBits = std::make_pair(5, 8); break; case PCM::SPR: UPISpeedMap = { {0, 2500}, {1, 12800}, {2, 14400}, {3, 16000}, {4, 20000} }; regBits = std::make_pair(0, 2); break; } if (UPISpeedMap.empty() == false && i < XPIRegisterLocation.size()) { const auto UPI_SPEED_REGISTER_FUNC_ADDR = 2; const auto UPI_SPEED_REGISTER_OFFSET = 0x2e0; PciHandleType reg(groupnr, UPIbus, XPIRegisterLocation[i].first, UPI_SPEED_REGISTER_FUNC_ADDR); uint32 value = 0; if (reg.read32(UPI_SPEED_REGISTER_OFFSET, &value) == sizeof(uint32)) { const size_t speedMT = UPISpeedMap[extract_bits_32(value, regBits.first, regBits.second)]; if (false) { std::cerr << "speedMT: " << speedMT << "\n"; } result = speedMT * 1000000ULL * pcm->getBytesPerLinkTransfer(); } } if(result == 0ULL) { if (PCM::hasUPI(cpufamilymodel) == false) std::cerr << "Warning: QPI_RATE_STATUS register is not available on port " << i << ". Computing QPI speed using a measurement loop.\n"; // compute qpi speed const uint64 timerGranularity = 1000000ULL; // mks MemTestParam param; initMemTest(param); uint64 startClocks = getQPIClocks((uint32)i); uint64 startTSC = pcm->getTickCount(timerGranularity, core_nr); uint64 endTSC; do { doMemTest(param); endTSC = pcm->getTickCount(timerGranularity, core_nr); } while (endTSC - startTSC < 200000ULL); // spin for 200 ms uint64 endClocks = getQPIClocks((uint32)i); cleanupMemTest(param); result = (uint64(double(endClocks - startClocks) * PCM::getBytesPerLinkCycle(cpufamilymodel) * double(timerGranularity) / double(endTSC - startTSC))); if(cpufamilymodel == PCM::HASWELLX || cpufamilymodel == PCM::BDX) /* BDX_DE does not have QPI. */{ result /=2; // HSX runs QPI clocks with doubled speed } } return result; }; std::vector > getSpeedsAsync; for (size_t i = 0; i < getNumQPIPorts(); ++i) { getSpeedsAsync.push_back(std::async(std::launch::async, getSpeed, i)); } for (size_t i = 0; i < getNumQPIPorts(); ++i) { qpi_speed[i] = (PCM::hasUPI(cpufamilymodel) == false && i==1)? qpi_speed[0] : getSpeedsAsync[i].get(); // link 1 does not have own speed register, it runs with the speed of link 0 } if (PCM::hasUPI(cpufamilymodel)) { // check the speed of link 3 if(qpi_speed.size() == 3 && qpi_speed[2] == 0) { std::cerr << "UPI link 3 is disabled\n"; qpi_speed.resize(2); xpiPMUs.resize(2); } } } if(!qpi_speed.empty()) { return *std::max_element(qpi_speed.begin(),qpi_speed.end()); } else { return 0; } } void ServerUncorePMUs::reportQPISpeed() const { PCM * m = PCM::getInstance(); std::cerr.precision(1); std::cerr << std::fixed; for (uint32 i = 0; i < (uint32)qpi_speed.size(); ++i) std::cerr << "Max " << m->xPI() << " link " << i << " speed: " << qpi_speed[i] / (1e9) << " GBytes/second (" << qpi_speed[i] / (1e9 * m->getBytesPerLinkTransfer()) << " GT/second)\n"; } uint64 PCM::CX_MSR_PMON_CTRY(uint32 Cbo, uint32 Ctr) const { switch (cpu_family_model) { case JAKETOWN: case IVYTOWN: return JKT_C0_MSR_PMON_CTR0 + (JKTIVT_CBO_MSR_STEP * Cbo) + Ctr; case HASWELLX: case BDX_DE: case BDX: case SKX: return HSX_C0_MSR_PMON_CTR0 + (HSX_CBO_MSR_STEP * Cbo) + Ctr; case ICX: case SNOWRIDGE: return CX_MSR_PMON_BOX_CTL(Cbo) + SERVER_CHA_MSR_PMON_CTR0_OFFSET + Ctr; case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: return SPR_CHA0_MSR_PMON_CTR0 + SPR_CHA_MSR_STEP * Cbo + Ctr; default: return 0; } } uint64 PCM::CX_MSR_PMON_BOX_FILTER(uint32 Cbo) const { switch (cpu_family_model) { case JAKETOWN: case IVYTOWN: return JKT_C0_MSR_PMON_BOX_FILTER + (JKTIVT_CBO_MSR_STEP * Cbo); case HASWELLX: case BDX_DE: case BDX: case SKX: return HSX_C0_MSR_PMON_BOX_FILTER + (HSX_CBO_MSR_STEP * Cbo); case KNL: return KNL_CHA0_MSR_PMON_BOX_CTL + (KNL_CHA_MSR_STEP * Cbo); case ICX: return CX_MSR_PMON_BOX_CTL(Cbo) + SERVER_CHA_MSR_PMON_BOX_FILTER_OFFSET; case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: return SPR_CHA0_MSR_PMON_BOX_FILTER + SPR_CHA_MSR_STEP * Cbo; default: return 0; } } uint64 PCM::CX_MSR_PMON_BOX_FILTER1(uint32 Cbo) const { switch (cpu_family_model) { case IVYTOWN: return IVT_C0_MSR_PMON_BOX_FILTER1 + (JKTIVT_CBO_MSR_STEP * Cbo); case HASWELLX: case BDX_DE: case BDX: case SKX: return HSX_C0_MSR_PMON_BOX_FILTER1 + (HSX_CBO_MSR_STEP * Cbo); default: return 0; } } uint64 PCM::CX_MSR_PMON_CTLY(uint32 Cbo, uint32 Ctl) const { switch (cpu_family_model) { case JAKETOWN: case IVYTOWN: return JKT_C0_MSR_PMON_CTL0 + (JKTIVT_CBO_MSR_STEP * Cbo) + Ctl; case HASWELLX: case BDX_DE: case BDX: case SKX: return HSX_C0_MSR_PMON_CTL0 + (HSX_CBO_MSR_STEP * Cbo) + Ctl; case ICX: case SNOWRIDGE: return CX_MSR_PMON_BOX_CTL(Cbo) + SERVER_CHA_MSR_PMON_CTL0_OFFSET + Ctl; case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: return SPR_CHA0_MSR_PMON_CTL0 + SPR_CHA_MSR_STEP * Cbo + Ctl; default: return 0; } } uint64 PCM::CX_MSR_PMON_BOX_CTL(uint32 Cbo) const { switch (cpu_family_model) { case JAKETOWN: case IVYTOWN: return JKT_C0_MSR_PMON_BOX_CTL + (JKTIVT_CBO_MSR_STEP * Cbo); case HASWELLX: case BDX_DE: case BDX: case SKX: return HSX_C0_MSR_PMON_BOX_CTL + (HSX_CBO_MSR_STEP * Cbo); case KNL: return KNL_CHA0_MSR_PMON_BOX_CTRL + (KNL_CHA_MSR_STEP * Cbo); case ICX: return ICX_CHA_MSR_PMON_BOX_CTL[Cbo]; case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: return SPR_CHA0_MSR_PMON_BOX_CTRL + SPR_CHA_MSR_STEP * Cbo; case SNOWRIDGE: return SNR_CHA_MSR_PMON_BOX_CTL[Cbo]; default: return 0; } } // Return the first device found with specific vendor/device IDs PciHandleType * getDeviceHandle(uint32 vendorId, uint32 deviceId) { #ifdef __linux__ const std::vector & mcfg = PciHandleMM::getMCFGRecords(); #else std::vector mcfg; getMCFGRecords(mcfg); #endif for(uint32 s = 0; s < (uint32)mcfg.size(); ++s) { for (uint32 bus = (uint32)mcfg[s].startBusNumber; bus <= (uint32)mcfg[s].endBusNumber; ++bus) { for (uint32 device = 0; device < 0x20; ++device) { for (uint32 function = 0; function < 0x8; ++function) { if (PciHandleType::exists(mcfg[s].PCISegmentGroupNumber, bus, device, function)) { PciHandleType * h = new PciHandleType(mcfg[s].PCISegmentGroupNumber, bus, device, function); uint32 value; h->read32(0, &value); const uint32 vid = value & 0xffff; const uint32 did = (value >> 16) & 0xffff; if (vid == vendorId && did == deviceId) return h; deleteAndNullify(h); } } } } } return NULL; } inline uint32 weight32(uint32 n) { uint32 count = 0; while (n) { n &= (n - 1); count++; } return count; } uint32 PCM::getMaxNumOfCBoxesInternal() const { static int num = -1; if (num >= 0) { return (uint32)num; } const auto refCore = socketRefCore[0]; uint64 val = 0; switch (cpu_family_model) { case GRR: case GNR: case GNR_D: case SRF: { const auto MSR_PMON_NUMBER_CBOS = 0x3fed; MSR[refCore]->read(MSR_PMON_NUMBER_CBOS, &val); num = (uint32)(val & 511); } break; case SPR: case EMR: try { PciHandleType * h = getDeviceHandle(PCM_INTEL_PCI_VENDOR_ID, 0x325b); if (h) { uint32 value; h->read32(0x9c, &value); num = (uint32)weight32(value); h->read32(0xa0, &value); num += (uint32)weight32(value); deleteAndNullify(h); } else { num = 0; } } catch (std::exception& e) { std::cerr << "Warning: reading the number of CHA from PCICFG register has failed: " << e.what() << "\n"; } break; case KNL: case SKX: case ICX: { /* * on KNL two physical cores share CHA. * The number of CHAs in the processor is stored in bits 5:0 * of NCUPMONConfig [0x702] MSR. */ const auto NCUPMONConfig = 0x702; MSR[refCore]->read(NCUPMONConfig, &val); } num = (uint32)(val & 63); break; case SNOWRIDGE: num = (uint32)num_phys_cores_per_socket / 4; break; default: /* * on other supported CPUs there is one CBox per physical core. This calculation will get us * the number of physical cores per socket which is the expected * value to be returned. */ num = (uint32)num_phys_cores_per_socket; } #ifdef PCM_USE_PERF if (num <= 0) { num = (uint32)enumeratePerfPMUs("cbox", 100).size(); } if (num <= 0) { num = (uint32)enumeratePerfPMUs("cha", 100).size(); } #endif assert(num >= 0); return (uint32)num; } uint32 PCM::getMaxNumOfIIOStacks() const { if (iioPMUs.size() > 0) { assert(irpPMUs.size()); assert(iioPMUs[0].size() == irpPMUs[0].size()); return (uint32)iioPMUs[0].size(); } return 0; } void PCM::programCboOpcodeFilter(const uint32 opc0, UncorePMU & pmu, const uint32 nc_, const uint32 opc1, const uint32 loc, const uint32 rem) { if (JAKETOWN == cpu_family_model) { *pmu.filter[0] = JKT_CBO_MSR_PMON_BOX_FILTER_OPC(opc0); } else if (IVYTOWN == cpu_family_model || HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model) { *pmu.filter[1] = IVTHSX_CBO_MSR_PMON_BOX_FILTER1_OPC(opc0); } else if (SKX == cpu_family_model) { *pmu.filter[1] = SKX_CHA_MSR_PMON_BOX_FILTER1_OPC0(opc0) + SKX_CHA_MSR_PMON_BOX_FILTER1_OPC1(opc1) + (rem?SKX_CHA_MSR_PMON_BOX_FILTER1_REM(1):0ULL) + (loc?SKX_CHA_MSR_PMON_BOX_FILTER1_LOC(1):0ULL) + SKX_CHA_MSR_PMON_BOX_FILTER1_NM(1) + SKX_CHA_MSR_PMON_BOX_FILTER1_NOT_NM(1) + (nc_?SKX_CHA_MSR_PMON_BOX_FILTER1_NC(1):0ULL); } else { std::cerr << "ERROR: programCboOpcodeFilter function is not implemented for cpu family " << cpu_family << " model " << cpu_model_private << std::endl; throw std::exception(); } } void PCM::programIIOCounters(uint64 rawEvents[4], int IIOStack) { std::vector IIO_units; if (IIOStack == -1) { int stacks_count; switch (getCPUFamilyModel()) { case PCM::GRR: stacks_count = GRR_M2IOSF_NUM; break; case PCM::GNR: case PCM::GNR_D: case PCM::SRF: stacks_count = BHS_M2IOSF_NUM; break; case PCM::SPR: case PCM::EMR: stacks_count = SPR_M2IOSF_NUM; break; case PCM::ICX: stacks_count = ICX_IIO_STACK_COUNT; break; case PCM::SNOWRIDGE: stacks_count = SNR_IIO_STACK_COUNT; break; case PCM::BDX: stacks_count = BDX_IIO_STACK_COUNT; break; case PCM::SKX: default: stacks_count = SKX_IIO_STACK_COUNT; break; } IIO_units.reserve(stacks_count); for (int stack = 0; stack < stacks_count; ++stack) { IIO_units.push_back(stack); } } else IIO_units.push_back(IIOStack); for (int32 i = 0; (i < num_sockets) && MSR.size() && iioPMUs.size(); ++i) { uint32 refCore = socketRefCore[i]; TemporalThreadAffinity tempThreadAffinity(refCore); // speedup trick for Linux for (const auto & unit: IIO_units) { if (iioPMUs[i].count(unit) == 0) { std::cerr << "IIO PMU unit (stack) " << unit << " is not found \n"; continue; } auto & pmu = iioPMUs[i][unit]; pmu.initFreeze(UNC_PMON_UNIT_CTL_RSV); program(pmu, &rawEvents[0], &rawEvents[4], UNC_PMON_UNIT_CTL_RSV); } } } void PCM::programIRPCounters(uint64 rawEvents[4], int IIOStack) { DBG(2, "PCM::programIRPCounters IRP PMU unit (stack) ", IIOStack, " getMaxNumOfIIOStacks(): ", getMaxNumOfIIOStacks()); std::vector IIO_units; if (IIOStack == -1) { for (uint32 stack = 0; stack < getMaxNumOfIIOStacks(); ++stack) { IIO_units.push_back(stack); } } else { IIO_units.push_back(IIOStack); } for (int32 i = 0; (i < num_sockets) && MSR.size() && irpPMUs.size(); ++i) { uint32 refCore = socketRefCore[i]; TemporalThreadAffinity tempThreadAffinity(refCore); // speedup trick for Linux for (const auto& unit : IIO_units) { if (irpPMUs[i].count(unit) == 0) { std::cerr << "IRP PMU unit (stack) " << unit << " is not found \n"; continue; } DBG(2, "Programming IRP PMU unit (stack) ", unit, " on socket ", i); auto& pmu = irpPMUs[i][unit]; pmu.initFreeze(UNC_PMON_UNIT_CTL_RSV); program(pmu, &rawEvents[0], &rawEvents[2], UNC_PMON_UNIT_CTL_RSV); } } } void PCM::programPCIeEventGroup(eventGroup_t &eventGroup) { assert(eventGroup.size() > 0); uint64 events[4] = {0}; uint64 umask[4] = {0}; switch (cpu_family_model) { case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: case PCM::SPR: case PCM::EMR: case PCM::ICX: case PCM::SNOWRIDGE: for (uint32 idx = 0; idx < eventGroup.size(); ++idx) events[idx] = eventGroup[idx]; programCbo(events); break; case PCM::SKX: //JKT through ĐĄLX generations allow programming only one required event at a time. if (eventGroup[0] & SKX_CHA_MSR_PMON_BOX_FILTER1_NC(1)) umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_IRQ(1)); else umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_PRQ(1)); if (eventGroup[0] & SKX_CHA_MSR_PMON_BOX_FILTER1_RSV(1)) umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_HIT(1)); else umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_MISS(1)); events[0] += CBO_MSR_PMON_CTL_EVENT(0x35) + CBO_MSR_PMON_CTL_UMASK(umask[0]); programCbo(events, SKX_CHA_MSR_PMON_BOX_GET_OPC0(eventGroup[0]), SKX_CHA_MSR_PMON_BOX_GET_NC(eventGroup[0])); break; case PCM::BDX_DE: case PCM::BDX: case PCM::KNL: case PCM::HASWELLX: case PCM::IVYTOWN: case PCM::JAKETOWN: events[0] = CBO_MSR_PMON_CTL_EVENT(0x35); events[0] += BDX_CBO_MSR_PMON_BOX_GET_FLT(eventGroup[0]) ? CBO_MSR_PMON_CTL_UMASK(0x3) : CBO_MSR_PMON_CTL_UMASK(1); events[0] += BDX_CBO_MSR_PMON_BOX_GET_TID(eventGroup[0]) ? CBO_MSR_PMON_CTL_TID_EN : 0ULL; programCbo(events, BDX_CBO_MSR_PMON_BOX_GET_OPC0(eventGroup[0]), 0, BDX_CBO_MSR_PMON_BOX_GET_TID(eventGroup[0]) ? 0x3e : 0ULL); break; } } void PCM::programCbo(const uint64 * events, const uint32 opCode, const uint32 nc_, const uint32 llc_lookup_tid_filter, const uint32 loc, const uint32 rem) { programUncorePMUs(CBO_PMU_ID, [&](UncorePMU & pmu) { pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); if ( ICX != cpu_family_model && SNOWRIDGE != cpu_family_model && SPR != cpu_family_model && EMR != cpu_family_model && GNR != cpu_family_model && GNR_D != cpu_family_model && SRF != cpu_family_model && GRR != cpu_family_model ) { programCboOpcodeFilter(opCode, pmu, nc_, 0, loc, rem); } if ((HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model || SKX == cpu_family_model) && llc_lookup_tid_filter != 0) *pmu.filter[0] = llc_lookup_tid_filter; PCM::program(pmu, events, events + ServerUncoreCounterState::maxCounters, UNC_PMON_UNIT_CTL_FRZ_EN); for (int c = 0; c < ServerUncoreCounterState::maxCounters && size_t(c) < pmu.size(); ++c) { *pmu.counterValue[c] = 0; } } ); } void PCM::programCboRaw(const uint64* events, const uint64 filter0, const uint64 filter1) { programUncorePMUs(CBO_PMU_ID, [&](UncorePMU& pmu) { pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); if (pmu.filter[0].get()) { *pmu.filter[0] = filter0; } if (pmu.filter[1].get()) { *pmu.filter[1] = filter1; } PCM::program(pmu, events, events + 4, UNC_PMON_UNIT_CTL_FRZ_EN); for (int c = 0; c < ServerUncoreCounterState::maxCounters && size_t(c) < pmu.size(); ++c) { *pmu.counterValue[c] = 0; } } ); } void PCM::programMDF(const uint64* events) { programUncorePMUs(MDF_PMU_ID, [&](UncorePMU& pmu) { pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); PCM::program(pmu, events, events + 4, UNC_PMON_UNIT_CTL_FRZ_EN); }); } void PCM::programUBOX(const uint64* events) { programUncorePMUs(UBOX_PMU_ID, [&events](UncorePMU& pmu) { pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); *pmu.fixedCounterControl = UCLK_FIXED_CTL_EN; if (events) { PCM::program(pmu, events, events + 2, 0); } }); } void PCM::controlQATTelemetry(uint32 dev, uint32 operation) { if (getNumOfIDXAccelDevs(IDX_QAT) == 0 || dev >= getNumOfIDXAccelDevs(IDX_QAT) || operation >= PCM::QAT_TLM_MAX) return; auto &gControl_reg = idxPMUs[IDX_QAT][dev].generalControl; switch (operation) { case PCM::QAT_TLM_START: case PCM::QAT_TLM_STOP: case PCM::QAT_TLM_REFRESH: *gControl_reg = operation; break; default: break; } } void PCM::programCXLCM(const uint64* events) { for (auto & sPMUs : cxlPMUs) { for (auto& pmus : sPMUs) { pmus.first.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); assert(pmus.first.size() == 8); PCM::program(pmus.first, events, events + 8, UNC_PMON_UNIT_CTL_FRZ_EN); } } } void PCM::programCXLDP(const uint64* events) { for (auto& sPMUs : cxlPMUs) { for (auto& pmus : sPMUs) { pmus.second.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); assert(pmus.second.size() == 4); PCM::program(pmus.second, events, events + 4, UNC_PMON_UNIT_CTL_FRZ_EN); } } } void PCM::programCXLCM() { uint64 CXLCMevents[8] = { 0,0,0,0,0,0,0,0 }; CXLCMevents[EventPosition::CXL_RxC_MEM] = UNC_PMON_CTL_EVENT(0x41) + UNC_PMON_CTL_UMASK(0x10); // CXLCM_RxC_PACK_BUF_INSERTS.MEM_DATA CXLCMevents[EventPosition::CXL_TxC_MEM] = UNC_PMON_CTL_EVENT(0x02) + UNC_PMON_CTL_UMASK(0x10); // CXLCM_TxC_PACK_BUF_INSERTS.MEM_DATA CXLCMevents[EventPosition::CXL_RxC_CACHE] = UNC_PMON_CTL_EVENT(0x41) + UNC_PMON_CTL_UMASK(0x04);// CXLCM_RxC_PACK_BUF_INSERTS.CACHE_DATA CXLCMevents[EventPosition::CXL_TxC_CACHE] = UNC_PMON_CTL_EVENT(0x02) + UNC_PMON_CTL_UMASK(0x04);// CXLCM_TxC_PACK_BUF_INSERTS.CACHE_DATA programCXLCM(CXLCMevents); } void PCM::programCXLDP() { uint64 events[4] = { 0,0,0,0 }; events[EventPosition::CXL_TxC_MEM] = UNC_PMON_CTL_EVENT(0x02) + UNC_PMON_CTL_UMASK(0x20); // UNC_CXLDP_TxC_AGF_INSERTS.M2S_DATA programCXLDP(events); } void PCM::programIDXAccelCounters(uint32 accel, std::vector &events, std::vector &filters_wq, std::vector &filters_eng, std::vector &filters_tc, std::vector &filters_pgsz, std::vector &filters_xfersz) { uint32 maxCTR = getMaxNumOfIDXAccelCtrs(accel); //limit the number of physical counter to use if (events.size() == 0 || accel >= IDX_MAX || getNumOfIDXAccelDevs(accel) == 0) return; //invalid input parameter or IDX accel dev NOT exist if (events.size() < maxCTR) maxCTR = events.size(); for (auto & pmu : idxPMUs[accel]) { pmu.initFreeze(); for (uint32 i = 0; i < maxCTR; i++) { auto &ctrl_reg = pmu.counterControl[i]; auto &filter_wq_reg = pmu.counterFilterWQ[i]; auto &filter_eng_reg = pmu.counterFilterENG[i]; auto &filter_tc_reg = pmu.counterFilterTC[i]; auto &filter_pgsz_reg = pmu.counterFilterPGSZ[i]; auto &filter_xfersz_reg = pmu.counterFilterXFERSZ[i]; if (pmu.getPERFMode() == false) { //disable the counter before raw program in PMU direct mode. *ctrl_reg = 0x0; } *filter_wq_reg = extract_bits_32(filters_wq.at(i), 0, 15); *filter_eng_reg = extract_bits_32(filters_eng.at(i), 0, 15); *filter_tc_reg = extract_bits_32(filters_tc.at(i), 0, 7); *filter_pgsz_reg = extract_bits_32(filters_pgsz.at(i), 0, 7); *filter_xfersz_reg = extract_bits_32(filters_xfersz.at(i), 0, 7); if (pmu.getPERFMode() == false) { *ctrl_reg = events.at(i); } else{ switch (accel) { case IDX_IAA: case IDX_DSA: //translate the event config from raw to perf format in Linux perf mode. //please reference the bitmap from DSA EAS spec and linux idxd driver perfmon interface. *ctrl_reg = ((extract_bits(events.at(i), 8, 11)) | ((extract_bits(events.at(i), 32, 59)) << 4)); break; case IDX_QAT://QAT NOT support perf mode break; default: break; } } } pmu.resetUnfreeze(); } } IDXCounterState PCM::getIDXAccelCounterState(uint32 accel, uint32 dev, uint32 counter_id) { IDXCounterState result; if (accel >= IDX_MAX || dev >= getNumOfIDXAccelDevs(accel) || counter_id >= getMaxNumOfIDXAccelCtrs(accel)) return result; result.data = *idxPMUs[accel][dev].counterValue[counter_id]; return result; } uint32 PCM::getNumOfIDXAccelDevs(int accel) const { if (accel >= IDX_MAX) return 0; return idxPMUs[accel].size(); } uint32 PCM::getMaxNumOfIDXAccelCtrs(int accel) const { uint32 retval = 0; if (supportIDXAccelDev() == true) { if (accel == IDX_IAA || accel == IDX_DSA) { retval = SPR_IDX_ACCEL_COUNTER_MAX_NUM; } else if(accel == IDX_QAT) { retval = SPR_QAT_ACCEL_COUNTER_MAX_NUM; } } return retval; } uint32 PCM::getNumaNodeOfIDXAccelDev(uint32 accel, uint32 dev) const { uint32 numa_node = 0xff; if (accel >= IDX_MAX || dev >= getNumOfIDXAccelDevs(accel)) return numa_node; numa_node = idxPMUs[accel][dev].getNumaNode(); return numa_node; } uint32 PCM::getCPUSocketIdOfIDXAccelDev(uint32 accel, uint32 dev) const { uint32 socketid = 0xff; if (accel >= IDX_MAX || dev >= getNumOfIDXAccelDevs(accel)) return socketid; socketid = idxPMUs[accel][dev].getSocketId(); return socketid; } bool PCM::supportIDXAccelDev() const { bool retval = false; switch (this->getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::SRF: case PCM::GNR_D: retval = true; break; default: retval = false; break; } return retval; } uint64 PCM::getUncoreCounterState(const int pmu_id, const size_t socket, const uint32 ctr) const { uint64 result = 0; if (socket < uncorePMUs.size() && ctr < ServerUncoreCounterState::maxCounters) { for (size_t die = 0; die < uncorePMUs[socket].size(); ++die) { TemporalThreadAffinity tempThreadAffinity(socketRefCore[socket]); // speedup trick for Linux const auto pmusIter = uncorePMUs[socket][die].find(pmu_id); if (pmusIter != uncorePMUs[socket][die].end()) { for (const auto& pmu : pmusIter->second) { if (pmu.get()) { result += *(pmu->counterValue[ctr]); } } } } } return result; } uint64 PCM::getUncoreClocks(const uint32 socket_id) { uint64 result = 0; if (socket_id < uncorePMUs.size()) { for (auto& d : uncorePMUs[socket_id]) { const auto iter = d.find(UBOX_PMU_ID); if (iter != d.end()) { for (auto& pmu : iter->second) { if (pmu.get()) { result += *pmu->fixedCounterValue; } } } } } return result; } PCIeCounterState PCM::getPCIeCounterState(const uint32 socket_, const uint32 ctr_) { PCIeCounterState result; result.data = getUncoreCounterState(CBO_PMU_ID, socket_, ctr_); return result; } uint64 PCM::getPCIeCounterData(const uint32 socket_, const uint32 ctr_) { return getUncoreCounterState(CBO_PMU_ID, socket_, ctr_); } void PCM::initLLCReadMissLatencyEvents(uint64 * events, uint32 & opCode) { if (LLCReadMissLatencyMetricsAvailable() == false) { return; } uint64 umask = 3ULL; // MISS_OPCODE switch (cpu_family_model) { case ICX: case SPR: case SNOWRIDGE: umask = 1ULL; break; case SKX: umask = (uint64)(SKX_CHA_TOR_INSERTS_UMASK_IRQ(1)) + (uint64)(SKX_CHA_TOR_INSERTS_UMASK_MISS(1)); break; } uint64 umask_ext = 0; switch (cpu_family_model) { case ICX: umask_ext = 0xC817FE; break; case SPR: umask_ext = 0x00C817FE; break; case SNOWRIDGE: umask_ext = 0xC827FE; break; } const uint64 all_umasks = CBO_MSR_PMON_CTL_UMASK(umask) + UNC_PMON_CTL_UMASK_EXT(umask_ext); events[EventPosition::TOR_OCCUPANCY] = CBO_MSR_PMON_CTL_EVENT(0x36) + all_umasks; // TOR_OCCUPANCY (must be on counter 0) events[EventPosition::TOR_INSERTS] = CBO_MSR_PMON_CTL_EVENT(0x35) + all_umasks; // TOR_INSERTS opCode = (SKX == cpu_family_model) ? 0x202 : 0x182; } void PCM::programCbo() { uint64 events[ServerUncoreCounterState::maxCounters]; std::fill(events, events + ServerUncoreCounterState::maxCounters, 0); uint32 opCode = 0; initLLCReadMissLatencyEvents(events, opCode); initCHARequestEvents(events); programCbo(events, opCode); programUBOX(nullptr); } void PCM::initCHARequestEvents(uint64 * config) { if (localMemoryRequestRatioMetricAvailable() && hasCHA()) { #ifdef PCM_HA_REQUESTS_READS_ONLY // HA REQUESTS READ: LOCAL + REMOTE config[EventPosition::REQUESTS_ALL] = CBO_MSR_PMON_CTL_EVENT(0x50) + CBO_MSR_PMON_CTL_UMASK((1 + 2)); // HA REQUESTS READ: LOCAL ONLY config[EventPosition::REQUESTS_LOCAL] = CBO_MSR_PMON_CTL_EVENT(0x50) + CBO_MSR_PMON_CTL_UMASK((1)); #else // HA REQUESTS READ+WRITE+REMOTE+LOCAL config[EventPosition::REQUESTS_ALL] = CBO_MSR_PMON_CTL_EVENT(0x50) + CBO_MSR_PMON_CTL_UMASK((1 + 2 + 4 + 8)); // HA REQUESTS READ+WRITE (LOCAL only) config[EventPosition::REQUESTS_LOCAL] = CBO_MSR_PMON_CTL_EVENT(0x50) + CBO_MSR_PMON_CTL_UMASK((1 + 4)); #endif } } CounterWidthExtender::CounterWidthExtender(AbstractRawCounter * raw_counter_, uint64 counter_width_, uint32 watchdog_delay_ms_) : raw_counter(raw_counter_), counter_width(counter_width_), watchdog_delay_ms(watchdog_delay_ms_) { last_raw_value = (*raw_counter)(); extended_value = last_raw_value; DBG(3, "Initial Value " , extended_value); UpdateThread = new std::thread( [&]() { while (1) { MySleepMs(static_cast(this->watchdog_delay_ms)); /* uint64 dummy = */ this->read(); } } ); } CounterWidthExtender::~CounterWidthExtender() { deleteAndNullify(UpdateThread); deleteAndNullify(raw_counter); } UncorePMU::UncorePMU(const HWRegisterPtr& unitControl_, const HWRegisterPtr& counterControl0, const HWRegisterPtr& counterControl1, const HWRegisterPtr& counterControl2, const HWRegisterPtr& counterControl3, const HWRegisterPtr& counterValue0, const HWRegisterPtr& counterValue1, const HWRegisterPtr& counterValue2, const HWRegisterPtr& counterValue3, const HWRegisterPtr& fixedCounterControl_, const HWRegisterPtr& fixedCounterValue_, const HWRegisterPtr& filter0, const HWRegisterPtr& filter1 ) : cpu_family_model_(0), unitControl(unitControl_), counterControl{ counterControl0, counterControl1, counterControl2, counterControl3 }, counterValue{ counterValue0, counterValue1, counterValue2, counterValue3 }, fixedCounterControl(fixedCounterControl_), fixedCounterValue(fixedCounterValue_), filter{ filter0 , filter1 } { assert(counterControl.size() == counterValue.size()); } UncorePMU::UncorePMU(const HWRegisterPtr& unitControl_, const std::vector& counterControl_, const std::vector& counterValue_, const HWRegisterPtr& fixedCounterControl_, const HWRegisterPtr& fixedCounterValue_, const HWRegisterPtr& filter0, const HWRegisterPtr& filter1 ): cpu_family_model_(0), unitControl(unitControl_), counterControl{counterControl_}, counterValue{counterValue_}, fixedCounterControl(fixedCounterControl_), fixedCounterValue(fixedCounterValue_), filter{ filter0 , filter1 } { assert(counterControl.size() == counterValue.size()); } uint32 UncorePMU::getCPUFamilyModel() { if (cpu_family_model_ == 0) { cpu_family_model_ = PCM::getInstance()->getCPUFamilyModel(); } return cpu_family_model_; } void UncorePMU::cleanup() { for (auto& cc: counterControl) { if (cc.get()) *cc = 0; } if (unitControl.get()) *unitControl = 0; if (fixedCounterControl.get()) *fixedCounterControl = 0; } void UncorePMU::freeze(const uint32 extra) { switch (getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: *unitControl = SPR_UNC_PMON_UNIT_CTL_FRZ; break; default: *unitControl = extra + UNC_PMON_UNIT_CTL_FRZ; } } void UncorePMU::unfreeze(const uint32 extra) { switch (getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: *unitControl = 0; break; default: *unitControl = extra; } } bool UncorePMU::initFreeze(const uint32 extra, const char* xPICheckMsg) { if (unitControl.get() == nullptr) { return true; // this PMU does not have unit control register => no op } switch (getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: *unitControl = SPR_UNC_PMON_UNIT_CTL_FRZ; // freeze *unitControl = SPR_UNC_PMON_UNIT_CTL_FRZ + SPR_UNC_PMON_UNIT_CTL_RST_CONTROL; // freeze and reset control registers return true; } // freeze enable *unitControl = extra; if (xPICheckMsg) { if ((extra & UNC_PMON_UNIT_CTL_VALID_BITS_MASK) != ((*unitControl) & UNC_PMON_UNIT_CTL_VALID_BITS_MASK)) { unitControl = nullptr; return false; } } // freeze *unitControl = extra + UNC_PMON_UNIT_CTL_FRZ; #ifdef PCM_UNCORE_PMON_BOX_CHECK_STATUS const uint64 val = *unitControl; if ((val & UNC_PMON_UNIT_CTL_VALID_BITS_MASK) != (extra + UNC_PMON_UNIT_CTL_FRZ)) { std::cerr << "ERROR: PMU counter programming seems not to work. PMON_BOX_CTL=0x" << std::hex << val << " needs to be =0x" << (UNC_PMON_UNIT_CTL_FRZ_EN + UNC_PMON_UNIT_CTL_FRZ) << std::dec << "\n"; if (xPICheckMsg) { std::cerr << xPICheckMsg; } } #endif return true; } void UncorePMU::resetUnfreeze(const uint32 extra) { switch (getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: *unitControl = SPR_UNC_PMON_UNIT_CTL_FRZ + SPR_UNC_PMON_UNIT_CTL_RST_COUNTERS; // freeze and reset counter registers *unitControl = 0; // unfreeze return; } // reset counter values *unitControl = extra + UNC_PMON_UNIT_CTL_FRZ + UNC_PMON_UNIT_CTL_RST_COUNTERS; // unfreeze counters *unitControl = extra; } IDX_PMU::IDX_PMU(const bool perfMode_, const uint32 numaNode_, const uint32 socketId_, const HWRegisterPtr& resetControl_, const HWRegisterPtr& freezeControl_, const HWRegisterPtr& generalControl_, const std::vector & counterControl, const std::vector & counterValue, const std::vector & counterFilterWQ, const std::vector & counterFilterENG, const std::vector & counterFilterTC, const std::vector & counterFilterPGSZ, const std::vector & counterFilterXFERSZ ) : cpu_family_model_(0), perf_mode_(perfMode_), numa_node_(numaNode_), socket_id_(socketId_), resetControl(resetControl_), freezeControl(freezeControl_), generalControl(generalControl_), counterControl{counterControl}, counterValue{counterValue}, counterFilterWQ{counterFilterWQ}, counterFilterENG{counterFilterENG}, counterFilterTC{counterFilterTC}, counterFilterPGSZ{counterFilterPGSZ}, counterFilterXFERSZ{counterFilterXFERSZ} { assert(counterControl.size() == counterValue.size()); } uint32 IDX_PMU::getCPUFamilyModel() { if (cpu_family_model_ == 0) { cpu_family_model_ = PCM::getInstance()->getCPUFamilyModel(); } return cpu_family_model_; } void IDX_PMU::cleanup() { for (auto& cc: counterControl) { if (cc.get()) { *cc = 0; } } if (resetControl.get()) { *resetControl = 0x3; } if (generalControl.get()) { *generalControl = 0x0; } DBG(3, "IDX_PMU::cleanup"); } void IDX_PMU::freeze() { *freezeControl = 0xFFFFFFFF; } void IDX_PMU::unfreeze() { *freezeControl = 0x0; } bool IDX_PMU::initFreeze() { if (resetControl.get() == nullptr || freezeControl.get() == nullptr) { return true; // does not have reset/freeze control register => no op } *resetControl = 0x2; // reset counter freeze(); // freeze counter return true; } void IDX_PMU::resetUnfreeze() { unfreeze(); // unfreeze counter } bool IDX_PMU::getPERFMode() { return perf_mode_; } uint32 IDX_PMU::getNumaNode() const { return numa_node_; } uint32 IDX_PMU::getSocketId() const { return socket_id_; } IIOCounterState PCM::getIIOCounterState(int socket, int IIOStack, int counter) { IIOCounterState result; result.data = 0; if (socket < (int)iioPMUs.size() && iioPMUs[socket].count(IIOStack) > 0) { result.data = *iioPMUs[socket][IIOStack].counterValue[counter]; } return result; } void PCM::getIIOCounterStates(int socket, int IIOStack, IIOCounterState * result) { uint32 refCore = socketRefCore[socket]; TemporalThreadAffinity tempThreadAffinity(refCore); // speedup trick for Linux for (int c = 0; c < 4; ++c) { result[c] = getIIOCounterState(socket, IIOStack, c); } } void PCM::setupCustomCoreEventsForNuma(PCM::ExtendedCustomCoreEventDescription& conf) const { switch (this->getCPUFamilyModel()) { case PCM::WESTMERE_EX: // OFFCORE_RESPONSE.ANY_REQUEST.LOCAL_DRAM: Offcore requests satisfied by the local DRAM conf.OffcoreResponseMsrValue[0] = 0x40FF; // OFFCORE_RESPONSE.ANY_REQUEST.REMOTE_DRAM: Offcore requests satisfied by a remote DRAM conf.OffcoreResponseMsrValue[1] = 0x20FF; break; case PCM::JAKETOWN: case PCM::IVYTOWN: // OFFCORE_RESPONSE.*.LOCAL_DRAM conf.OffcoreResponseMsrValue[0] = 0x780400000 | 0x08FFF; // OFFCORE_RESPONSE.*.REMOTE_DRAM conf.OffcoreResponseMsrValue[1] = 0x7ff800000 | 0x08FFF; break; case PCM::HASWELLX: // OFFCORE_RESPONSE.*.LOCAL_DRAM conf.OffcoreResponseMsrValue[0] = 0x600400000 | 0x08FFF; // OFFCORE_RESPONSE.*.REMOTE_DRAM conf.OffcoreResponseMsrValue[1] = 0x63f800000 | 0x08FFF; break; case PCM::BDX: // OFFCORE_RESPONSE.ALL_REQUESTS.L3_MISS.LOCAL_DRAM conf.OffcoreResponseMsrValue[0] = 0x0604008FFF; // OFFCORE_RESPONSE.ALL_REQUESTS.L3_MISS.REMOTE_DRAM conf.OffcoreResponseMsrValue[1] = 0x067BC08FFF; break; case PCM::SKX: // OFFCORE_RESPONSE.ALL_REQUESTS.L3_MISS_LOCAL_DRAM.ANY_SNOOP conf.OffcoreResponseMsrValue[0] = 0x3FC0008FFF | (1 << 26); // OFFCORE_RESPONSE.ALL_REQUESTS.L3_MISS_REMOTE_(HOP0,HOP1,HOP2P)_DRAM.ANY_SNOOP conf.OffcoreResponseMsrValue[1] = 0x3FC0008FFF | (1 << 27) | (1 << 28) | (1 << 29); break; case PCM::ICX: std::cerr << "INFO: Monitored accesses include demand + L2 cache prefetcher, code read and RFO.\n"; // OCR.READS_TO_CORE.LOCAL_DRAM conf.OffcoreResponseMsrValue[0] = 0x0104000477; // OCR.READS_TO_CORE.REMOTE_DRAM conf.OffcoreResponseMsrValue[1] = 0x0730000477; break; case PCM::SPR: case PCM::EMR: case PCM::GNR: std::cout << "INFO: Monitored accesses include demand + L2 cache prefetcher, code read and RFO.\n"; // OCR.READS_TO_CORE.LOCAL_DRAM conf.OffcoreResponseMsrValue[0] = 0x104004477; // OCR.READS_TO_CORE.REMOTE_DRAM and OCR.READS_TO_CORE.SNC_DRAM conf.OffcoreResponseMsrValue[1] = 0x730004477 | 0x708004477; break; default: throw UnsupportedProcessorException(); } } } // namespace pcm pcm-202502/src/cpucounters.h000066400000000000000000006172221475730356400157270ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2024, Intel Corporation // written by Roman Dementiev // Thomas Willhalm // and others #ifndef CPUCOUNTERS_HEADER #define CPUCOUNTERS_HEADER /*! \file cpucounters.h \brief Main CPU counters header Include this header file if you want to access CPU counters (core and uncore - including memory controller chips and QPI) */ #include "version.h" #ifndef PCM_API #define PCM_API #endif #undef PCM_HA_REQUESTS_READS_ONLY #undef PCM_DEBUG_TOPOLOGY // debug of topology enumeration routine #undef PCM_UNCORE_PMON_BOX_CHECK_STATUS // debug only #include "types.h" #include "topologyentry.h" #include "msr.h" #include "pci.h" #include "tpmi.h" #include "pmt.h" #include "bw.h" #include "width_extender.h" #include "exceptions/unsupported_processor_exception.hpp" #include "uncore_pmu_discovery.h" #include #include #include #include #include #include #include #include #include #ifdef PCM_USE_PERF #include #include #define PCM_PERF_COUNT_HW_REF_CPU_CYCLES (9) #endif #ifndef _MSC_VER #define NOMINMAX #include #include #include #include #include #include #endif #ifdef _MSC_VER #if _MSC_VER>= 1600 #include #endif #endif #ifdef __linux__ #include "resctrl.h" #endif namespace pcm { #ifdef _MSC_VER void PCM_API restrictDriverAccess(LPCTSTR path); #endif class SystemCounterState; class SocketCounterState; class CoreCounterState; class BasicCounterState; class ServerUncoreCounterState; class PCM; class CoreTaskQueue; class SystemRoot; /* CPU performance monitoring routines A set of performance monitoring routines for recent Intel CPUs */ class HWRegister { public: virtual void operator = (uint64 val) = 0; // write operation virtual operator uint64 () = 0; //read operation virtual ~HWRegister() {} }; class PCICFGRegister64 : public HWRegister { std::shared_ptr handle; size_t offset; public: PCICFGRegister64(const std::shared_ptr & handle_, size_t offset_) : handle(handle_), offset(offset_) { } void operator = (uint64 val) override { cvt_ds cvt; cvt.ui64 = val; handle->write32(offset, cvt.ui32.low); handle->write32(offset + sizeof(uint32), cvt.ui32.high); } operator uint64 () override { uint64 result = 0; handle->read64(offset, &result); return result; } }; class PCICFGRegister32 : public HWRegister { std::shared_ptr handle; size_t offset; public: PCICFGRegister32(const std::shared_ptr & handle_, size_t offset_) : handle(handle_), offset(offset_) { } void operator = (uint64 val) override { handle->write32(offset, (uint32)val); } operator uint64 () override { uint32 result = 0; handle->read32(offset, &result); return result; } }; class MMIORegister64 : public HWRegister { std::shared_ptr handle; size_t offset; public: MMIORegister64(const std::shared_ptr & handle_, size_t offset_) : handle(handle_), offset(offset_) { } void operator = (uint64 val) override { DBG(4, std::hex , "MMIORegister64 writing " , val , " at offset " , offset , std::dec); handle->write64(offset, val); } operator uint64 () override { const uint64 val = handle->read64(offset); DBG(4, std::hex , "MMIORegister64 read " , val , " from offset " , offset , std::dec); return val; } }; class MMIORegister32 : public HWRegister { std::shared_ptr handle; size_t offset; public: MMIORegister32(const std::shared_ptr & handle_, size_t offset_) : handle(handle_), offset(offset_) { } void operator = (uint64 val) override { DBG(4, std::hex , "MMIORegister32 writing " , val , " at offset " , offset , std::dec); handle->write32(offset, (uint32)val); } operator uint64 () override { const uint64 val = (uint64)handle->read32(offset); DBG(4, std::hex , "MMIORegister32 read " , val , " from offset " , offset , std::dec); return val; } }; class MSRRegister : public HWRegister { std::shared_ptr handle; size_t offset; public: MSRRegister(const std::shared_ptr & handle_, size_t offset_) : handle(handle_), offset(offset_) { } void operator = (uint64 val) override { handle->write(offset, val); } operator uint64 () override { uint64 value = 0; handle->read(offset, &value); DBG(4, "reading MSR " , offset , " returning " , value); return value; } }; class CounterWidthExtenderRegister : public HWRegister { std::shared_ptr handle; public: CounterWidthExtenderRegister(const std::shared_ptr & handle_) : handle(handle_) { } void operator = (uint64 val) override { if (val == 0) { handle->reset(); } else { std::cerr << "ERROR: writing non-zero values to CounterWidthExtenderRegister is not supported\n"; throw std::exception(); } } operator uint64 () override { return handle->read();; } }; class UncorePMU { typedef std::shared_ptr HWRegisterPtr; uint32 cpu_family_model_; uint32 getCPUFamilyModel(); HWRegisterPtr unitControl; public: std::vector counterControl; std::vector counterValue; HWRegisterPtr fixedCounterControl; HWRegisterPtr fixedCounterValue; HWRegisterPtr filter[2]; enum { maxCounters = 8 }; UncorePMU(const HWRegisterPtr& unitControl_, const HWRegisterPtr& counterControl0, const HWRegisterPtr& counterControl1, const HWRegisterPtr& counterControl2, const HWRegisterPtr& counterControl3, const HWRegisterPtr& counterValue0, const HWRegisterPtr& counterValue1, const HWRegisterPtr& counterValue2, const HWRegisterPtr& counterValue3, const HWRegisterPtr& fixedCounterControl_ = HWRegisterPtr(), const HWRegisterPtr& fixedCounterValue_ = HWRegisterPtr(), const HWRegisterPtr& filter0 = HWRegisterPtr(), const HWRegisterPtr& filter1 = HWRegisterPtr() ); UncorePMU(const HWRegisterPtr& unitControl_, const std::vector & counterControl_, const std::vector & counterValue_, const HWRegisterPtr& fixedCounterControl_ = HWRegisterPtr(), const HWRegisterPtr& fixedCounterValue_ = HWRegisterPtr(), const HWRegisterPtr& filter0 = HWRegisterPtr(), const HWRegisterPtr& filter1 = HWRegisterPtr() ); UncorePMU() : cpu_family_model_(0U) {} size_t size() const { return counterControl.size(); } virtual ~UncorePMU() {} bool valid() const { return unitControl.get() != nullptr; } void cleanup(); void freeze(const uint32 extra); bool initFreeze(const uint32 extra, const char* xPICheckMsg = nullptr); void unfreeze(const uint32 extra); void resetUnfreeze(const uint32 extra); }; typedef std::shared_ptr UncorePMURef; class IDX_PMU { typedef std::shared_ptr HWRegisterPtr; uint32 cpu_family_model_; uint32 getCPUFamilyModel(); bool perf_mode_; uint32 numa_node_; uint32 socket_id_; HWRegisterPtr resetControl; HWRegisterPtr freezeControl; public: HWRegisterPtr generalControl; std::vector counterControl; std::vector counterValue; std::vector counterFilterWQ; std::vector counterFilterENG; std::vector counterFilterTC; std::vector counterFilterPGSZ; std::vector counterFilterXFERSZ; IDX_PMU(const bool perfMode_, const uint32 numaNode_, const uint32 socketId_, const HWRegisterPtr& resetControl_, const HWRegisterPtr& freezeControl_, const HWRegisterPtr& generalControl_, const std::vector & counterControl, const std::vector & counterValue, const std::vector & counterFilterWQ, const std::vector & counterFilterENG, const std::vector & counterFilterTC, const std::vector & counterFilterPGSZ, const std::vector & counterFilterXFERSZ ); IDX_PMU() : cpu_family_model_(0U), perf_mode_(false), numa_node_(0), socket_id_(0) {} size_t size() const { return counterControl.size(); } virtual ~IDX_PMU() {} bool valid() const { return resetControl.get() != nullptr; } void cleanup(); void freeze(); bool initFreeze(); void unfreeze(); void resetUnfreeze(); bool getPERFMode(); uint32 getNumaNode() const; uint32 getSocketId() const; }; enum ServerUncoreMemoryMetrics { PartialWrites, Pmem, PmemMemoryMode, PmemMixedMode }; //! Object to access uncore counters in a socket/processor with microarchitecture codename SandyBridge-EP (Jaketown) or Ivytown-EP or Ivytown-EX class ServerUncorePMUs { friend class PCM; int32 iMCbus,UPIbus,M2Mbus; uint32 groupnr; int32 cpu_family_model; typedef std::vector UncorePMUVector; UncorePMUVector imcPMUs; UncorePMUVector edcPMUs; UncorePMUVector xpiPMUs; UncorePMUVector m3upiPMUs; UncorePMUVector m2mPMUs; UncorePMUVector haPMUs; UncorePMUVector hbm_m2mPMUs; std::vector allPMUs{ &imcPMUs, &edcPMUs, &xpiPMUs, &m3upiPMUs , &m2mPMUs, &haPMUs, &hbm_m2mPMUs }; std::vector qpi_speed; std::vector num_imc_channels; // number of memory channels in each memory controller std::vector > XPIRegisterLocation; // (device, function) std::vector > M3UPIRegisterLocation; // (device, function) std::vector > > MCRegisterLocation; // MCRegisterLocation[controller]: (device, function) std::vector > EDCRegisterLocation; // EDCRegisterLocation: (device, function) std::vector > M2MRegisterLocation; // M2MRegisterLocation: (device, function) std::vector > HARegisterLocation; // HARegisterLocation: (device, function) std::vector > HBM_M2MRegisterLocation; // HBM_M2MRegisterLocation: (device, function) static std::vector > socket2iMCbus; static std::vector > socket2UPIbus; static std::vector > socket2M2Mbus; ServerUncorePMUs(); // forbidden ServerUncorePMUs(ServerUncorePMUs &); // forbidden ServerUncorePMUs & operator = (const ServerUncorePMUs &); // forbidden static PciHandleType * createIntelPerfMonDevice(uint32 groupnr, int32 bus, uint32 dev, uint32 func, bool checkVendor = false); void programIMC(const uint32 * MCCntConfig); void programEDC(const uint32 * EDCCntConfig); void programM2M(const uint64 * M2MCntConfig); void programM2M(); void programHA(const uint32 * config); void programHA(); void programXPI(const uint32 * XPICntConfig); void programM3UPI(const uint32* M3UPICntConfig); typedef std::pair > MemTestParam; void initMemTest(MemTestParam & param); void doMemTest(const MemTestParam & param); void cleanupMemTest(const MemTestParam & param); void cleanupQPIHandles(); void cleanupPMUs(); void initDirect(uint32 socket_, const PCM * pcm); void initPerf(uint32 socket_, const PCM * pcm); void initBuses(uint32 socket_, const PCM * pcm); void initRegisterLocations(const PCM * pcm); uint64 getPMUCounter(std::vector & pmu, const uint32 id, const uint32 counter); bool HBMAvailable() const; public: enum EventPosition { READ=0, WRITE=1, READ2=2, WRITE2=3, READ_RANK_A=0, WRITE_RANK_A=1, READ_RANK_B=2, WRITE_RANK_B=3, PARTIAL=2, PMM_READ=2, PMM_WRITE=3, MM_MISS_CLEAN=2, MM_MISS_DIRTY=3, NM_HIT=0, // NM : Near Memory (DRAM cache) in Memory Mode M2M_CLOCKTICKS=1 }; //! \brief Initialize access data structures //! \param socket_ socket id //! \param pcm pointer to PCM instance ServerUncorePMUs(uint32 socket_, const PCM * pcm); //! \brief Program performance counters (disables programming power counters) void program(); //! \brief Get the number of integrated controller reads (in cache lines) uint64 getImcReads(); //! \brief Get the number of integrated controller reads for given controller (in cache lines) //! \param controller controller ID/number uint64 getImcReadsForController(uint32 controller); //! \brief Get the number of integrated controller reads for given channels (in cache lines) //! \param beginChannel first channel in the range //! \param endChannel last channel + 1: the range is [beginChannel, endChannel). endChannel is not included. uint64 getImcReadsForChannels(uint32 beginChannel, uint32 endChannel); //! \brief Get the number of integrated controller writes (in cache lines) uint64 getImcWrites(); //! \brief Get the number of requests to home agent (BDX/HSX only) uint64 getHALocalRequests(); //! \brief Get the number of local requests to home agent (BDX/HSX only) uint64 getHARequests(); //! \brief Get the number of Near Memory Hits uint64 getNMHits(); //! \brief Get the number of Near Memory Misses uint64 getNMMisses(); //! \brief Get the number of PMM memory reads (in cache lines) uint64 getPMMReads(); //! \brief Get the number of PMM memory writes (in cache lines) uint64 getPMMWrites(); //! \brief Get the number of cache lines read by EDC (embedded DRAM controller) uint64 getEdcReads(); //! \brief Get the number of cache lines written by EDC (embedded DRAM controller) uint64 getEdcWrites(); //! \brief Get the number of incoming data flits to the socket through a port //! \param port QPI port id uint64 getIncomingDataFlits(uint32 port); //! \brief Get the number of outgoing data and non-data or idle flits (depending on the architecture) from the socket through a port //! \param port QPI port id uint64 getOutgoingFlits(uint32 port); ~ServerUncorePMUs(); //! \brief Program power counters (disables programming performance counters) //! \param mc_profile memory controller measurement profile. See description of profiles in pcm-power.cpp void program_power_metrics(int mc_profile); //! \brief Program memory counters (disables programming performance counters) //! \param rankA count DIMM rank1 statistics (disables memory channel monitoring) //! \param rankB count DIMM rank2 statistics (disables memory channel monitoring) //! \brief metrics metric set (see the ServerUncoreMemoryMetrics enum) void programServerUncoreMemoryMetrics(const ServerUncoreMemoryMetrics & metrics, const int rankA = -1, const int rankB = -1); //! \brief Get number of QPI LL clocks on a QPI port //! \param port QPI port number uint64 getQPIClocks(uint32 port); //! \brief Get number cycles on a QPI port when the link was in a power saving half-lane mode //! \param port QPI port number uint64 getQPIL0pTxCycles(uint32 port); //! \brief Get number cycles on a UPI port when the link was in a L0 mode (fully active) //! \param port UPI port number uint64 getUPIL0TxCycles(uint32 port); //! \brief Get number cycles on a QPI port when the link was in a power saving shutdown mode //! \param port QPI port number uint64 getQPIL1Cycles(uint32 port); //! \brief Get number DRAM channel cycles //! \param channel channel number uint64 getDRAMClocks(uint32 channel); //! \brief Get number HBM channel cycles //! \param channel channel number uint64 getHBMClocks(uint32 channel); //! \brief Direct read of memory controller PMU counter (counter meaning depends on the programming: power/performance/etc) //! \param channel channel number //! \param counter counter number uint64 getMCCounter(uint32 channel, uint32 counter); //! \brief Direct read of embedded DRAM memory controller PMU counter (counter meaning depends on the programming: power/performance/etc) //! \param channel channel number //! \param counter counter number uint64 getEDCCounter(uint32 channel, uint32 counter); //! \brief Direct read of QPI LL PMU counter (counter meaning depends on the programming: power/performance/etc) //! \param port port number //! \param counter counter number uint64 getQPILLCounter(uint32 port, uint32 counter); //! \brief Direct read of M3UPI PMU counter (counter meaning depends on the programming: power/performance/etc) //! \param port port number //! \param counter counter number uint64 getM3UPICounter(uint32 port, uint32 counter); //! \brief Direct read of M2M counter //! \param box box ID/number //! \param counter counter number uint64 getM2MCounter(uint32 box, uint32 counter); //! \brief Direct read of HA counter //! \param box box ID/number //! \param counter counter number uint64 getHACounter(uint32 box, uint32 counter); //! \brief Freezes event counting void freezeCounters(); //! \brief Unfreezes event counting void unfreezeCounters(); //! \brief Measures/computes the maximum theoretical QPI link bandwidth speed in GByte/seconds uint64 computeQPISpeed(const uint32 ref_core, const int cpumodel); //! \brief Enable correct counting of various LLC events (with memory access perf penalty) void enableJKTWorkaround(bool enable); //! \brief Returns the number of detected QPI ports size_t getNumQPIPorts() const { return xpiPMUs.size(); } //! \brief Returns the speed of the QPI link uint64 getQPILinkSpeed(const uint32 linkNr) const { return qpi_speed.empty() ? 0 : qpi_speed[linkNr]; } //! \brief Print QPI Speeds void reportQPISpeed() const; //! \brief Returns the number of detected integrated memory controllers uint32 getNumMC() const { return (uint32)num_imc_channels.size(); } //! \brief Returns the total number of detected memory channels on all integrated memory controllers size_t getNumMCChannels() const { return (size_t)imcPMUs.size(); } //! \brief Returns the total number of detected memory channels on given integrated memory controller //! \param controller controller number size_t getNumMCChannels(const uint32 controller) const; //! \brief Returns the total number of detected memory channels on all embedded DRAM controllers (EDC) size_t getNumEDCChannels() const { return edcPMUs.size(); } }; class SimpleCounterState { template friend uint64 getNumberOfEvents(const T & before, const T & after); friend class PCM; uint64 data; public: SimpleCounterState() : data(0) { } uint64 getRawData() const {return data;} virtual ~SimpleCounterState() { } }; typedef SimpleCounterState PCIeCounterState; typedef SimpleCounterState IIOCounterState; typedef SimpleCounterState IDXCounterState; typedef std::vector eventGroup_t; class PerfVirtualControlRegister; /*! \brief CPU Performance Monitor This singleton object needs to be instantiated for each process before accessing counting and measuring routines */ class PCM_API PCM { friend class BasicCounterState; friend class UncoreCounterState; friend class Socket; friend class ServerUncore; friend class ClientUncore; friend class PerfVirtualControlRegister; friend class Aggregator; friend class ServerUncorePMUs; PCM(); // forbidden to call directly because it is a singleton PCM(const PCM &) = delete; PCM & operator = (const PCM &) = delete; int32 cpu_family; int32 cpu_model_private; int32 cpu_family_model; bool hybrid = false; int32 cpu_stepping; int64 cpu_microcode_level; uint32 max_cpuid; int32 threads_per_core; int32 num_cores; int32 num_sockets; int32 num_phys_cores_per_socket; int32 num_online_cores; int32 num_online_sockets; uint32 accel; uint32 accel_counters_num_max; uint32 core_gen_counter_num_max; uint32 core_gen_counter_num_used; uint32 core_gen_counter_width; uint32 core_fixed_counter_num_max; uint32 core_fixed_counter_num_used; uint32 core_fixed_counter_width; uint64 core_global_ctrl_value{0ULL}; uint32 uncore_gen_counter_num_max; uint32 uncore_gen_counter_num_used; uint32 uncore_gen_counter_width; uint32 uncore_fixed_counter_num_max; uint32 uncore_fixed_counter_num_used; uint32 uncore_fixed_counter_width; uint32 perfmon_version; int32 perfmon_config_anythread; uint64 nominal_frequency; uint64 max_qpi_speed; // in GBytes/second uint32 L3ScalingFactor; int32 pkgThermalSpecPower, pkgMinimumPower, pkgMaximumPower; enum UFS_TPMI { UFS_ID = 2, UFS_FABRIC_CLUSTER_OFFSET = 1, UFS_STATUS = 0 }; std::vector > > UFSStatus; std::vector topology; SystemRoot* systemTopology; std::string errorMessage; static PCM * instance; bool programmed_core_pmu{false}; std::vector > MSR; std::vector > serverUncorePMUs; typedef std::vector UncorePMUArrayType; public: enum UncorePMUIDs { CBO_PMU_ID, MDF_PMU_ID, PCU_PMU_ID, UBOX_PMU_ID, PCIE_GEN5x16_PMU_ID, PCIE_GEN5x8_PMU_ID, INVALID_PMU_ID }; private: std::unordered_map strToUncorePMUID_ { {"pciex8", PCIE_GEN5x8_PMU_ID}, {"pciex16", PCIE_GEN5x16_PMU_ID} }; public: UncorePMUIDs strToUncorePMUID(const std::string & type) const { const auto iter = strToUncorePMUID_.find(type); return (iter == strToUncorePMUID_.end()) ? INVALID_PMU_ID : (UncorePMUIDs)iter->second; } size_t getNumUFSDies() const { if (UFSStatus.empty()) return 0; return UFSStatus[0].size(); } private: typedef std::unordered_map UncorePMUMapType; // socket -> die -> pmu map -> pmu ref array std::vector< std::vector > uncorePMUs; template void forAllUncorePMUs(F f) { for (auto& s : uncorePMUs) { for (auto& d : s) { for (auto& p : d) { for (auto& e : p.second) { if (e.get()) { f(*e); } } } } } } template void forAllUncorePMUs(const int pmu_id, F f) { for (auto& s : uncorePMUs) { for (auto& d : s) { for (auto& e : d[pmu_id]) { if (e.get()) { f(*e); } } } } } template void forAllUncorePMUs(const size_t socket_id, const int pmu_id, F f) { if (socket_id < uncorePMUs.size()) { for (auto& d : uncorePMUs[socket_id]) { for (auto& e : d[pmu_id]) { if (e.get()) { f(*e); } } } } } template void readUncoreCounterValues(T& result, const size_t socket) const { if (socket < uncorePMUs.size()) { result.Counters.resize(uncorePMUs[socket].size()); for (size_t die = 0; die < uncorePMUs[socket].size(); ++die) { TemporalThreadAffinity tempThreadAffinity(socketRefCore[socket]); // speedup trick for Linux for (auto pmuIter = uncorePMUs[socket][die].begin(); pmuIter != uncorePMUs[socket][die].end(); ++pmuIter) { const auto & pmu_id = pmuIter->first; result.Counters[die][pmu_id].resize(pmuIter->second.size()); for (size_t unit = 0; unit < pmuIter->second.size(); ++unit) { auto& pmu = pmuIter->second[unit]; for (size_t i = 0; pmu.get() != nullptr && i < pmu->size(); ++i) { DBG(4, "s " , socket , " d " , die , " pmu " , pmu_id , " unit " , unit , " ctr " , i ); result.Counters[die][pmu_id][unit][i] = *(pmu->counterValue[i]); } } } } } } uint64 getUncoreCounterState(const int pmu_id, const size_t socket, const uint32 ctr) const; template void programUncorePMUs(const int pmu_id, F pmuFunc) { if (MSR.empty()) return; for (size_t socket = 0; socket < uncorePMUs.size(); ++socket) { for (size_t die = 0; die < uncorePMUs[socket].size(); ++die) { TemporalThreadAffinity tempThreadAffinity(socketRefCore[socket]); // speedup trick for Linux for (size_t unit = 0; unit < uncorePMUs[socket][die][pmu_id].size(); ++unit) { auto& pmu = uncorePMUs[socket][die][pmu_id][unit]; if (pmu.get()) { pmuFunc(*pmu); } } } } } // TODO: gradually move other PMUs to the uncorePMUs structure std::vector > iioPMUs; std::vector > irpPMUs; std::vector > idxPMUs; double joulesPerEnergyUnit; std::vector > energy_status; std::vector > dram_energy_status; std::vector > pp_energy_status; std::shared_ptr system_energy_status; std::vector>> cxlPMUs; // socket X CXL ports X UNIT {0,1} std::vector > memory_bw_local; std::vector > memory_bw_total; #ifdef __linux__ Resctrl resctrl; #endif bool useResctrl; std::shared_ptr clientBW; std::shared_ptr clientImcReads; std::shared_ptr clientImcWrites; std::shared_ptr clientGtRequests; std::shared_ptr clientIaRequests; std::shared_ptr clientIoRequests; std::vector > serverBW; std::shared_ptr uncorePMUDiscovery; template void getPCICFGPMUsFromDiscovery(const unsigned int BoxType, const size_t s, F f) const; bool disable_JKT_workaround; bool blocked; // track if time-driven counter update is running or not: PCM is blocked uint64 * coreCStateMsr; // MSR addresses of core C-state free-running counters uint64 * pkgCStateMsr; // MSR addresses of package C-state free-running counters std::vector > coreTaskQueues; bool L2CacheHitRatioAvailable; bool L3CacheHitRatioAvailable; bool L3CacheMissesAvailable; bool L2CacheMissesAvailable; bool L2CacheHitsAvailable; bool L3CacheHitsNoSnoopAvailable; bool L3CacheHitsSnoopAvailable; bool L3CacheHitsAvailable; bool forceRTMAbortMode; std::vector FrontendBoundSlots, BadSpeculationSlots, BackendBoundSlots, RetiringSlots, AllSlotsRaw; std::vector MemBoundSlots, FetchLatSlots, BrMispredSlots, HeavyOpsSlots; bool isFixedCounterSupported(unsigned c); bool vm = false; bool linux_arch_perfmon = false; public: size_t getMaxNumOfUncorePMUs(const int pmu_id, const size_t socket = 0) const { size_t count = 0ULL; if (socket < uncorePMUs.size()) { const auto & s = uncorePMUs[socket]; for (auto& d : s) { const auto iter = d.find(pmu_id); if (iter != d.end()) { count += iter->second.size(); } } } return count; } enum { MAX_PP = 1 }; // max power plane number on Intel architecture (client) enum { MAX_C_STATE = 10 }; // max C-state on Intel architecture //! \brief Returns true if the specified core C-state residency metric is supported bool isCoreCStateResidencySupported(int state) const { if (state == 0 || state == 1) return true; return (coreCStateMsr != NULL && state <= ((int)MAX_C_STATE) && coreCStateMsr[state] != 0); } //! \brief Returns true if the specified package C-state residency metric is supported bool isPackageCStateResidencySupported(int state) { if (state == 0) { return true; } return (pkgCStateMsr != NULL && state <= ((int)MAX_C_STATE) && pkgCStateMsr[state] != 0); } //! \brief Redirects output destination to provided file, instead of std::cout and std::cerr (optional) static void setOutput(const std::string filename, const bool cerrToo = false); //! \brief Restores output, closes output file if opened void restoreOutput(); //! \brief Set Run State. // Arguments: // -- 1 - program is running // -- 0 -pgram is sleeping void setRunState(int new_state) { run_state = new_state; } //! \brief Returns program's Run State. // Results: // -- 1 - program is running // -- 0 -pgram is sleeping int getRunState(void) { return run_state; } bool isBlocked(void) { return blocked; } void setBlocked(const bool new_blocked) { blocked = new_blocked; } //! Mode of programming (parameter in the program() method) enum ProgramMode { DEFAULT_EVENTS = 0, /*!< Default choice of events, the additional parameter is not needed and ignored */ CUSTOM_CORE_EVENTS = 1, /*!< Custom set of core events specified in the parameter to the program method. The parameter must be a pointer to array of four \c CustomCoreEventDescription values */ EXT_CUSTOM_CORE_EVENTS = 2, /*!< Custom set of core events specified in the parameter to the program method. The parameter must be a pointer to a \c ExtendedCustomCoreEventDescription data structure */ INVALID_MODE /*!< Non-programmed mode */ }; //! Return codes (e.g. for program(..) method) enum ErrorCode { Success = 0, MSRAccessDenied = 1, PMUBusy = 2, UnknownError }; enum PerfmonField { INVALID, /* Use to parse invalid field */ OPCODE, EVENT_SELECT, UMASK, RESET, EDGE_DET, IGNORED, OVERFLOW_ENABLE, ENABLE, INVERT, THRESH, CH_MASK, FC_MASK, /* Below are not part of perfmon definition */ H_EVENT_NAME, V_EVENT_NAME, MULTIPLIER, DIVIDER, COUNTER_INDEX }; enum PCIeWidthMode { X1, X4, X8, X16, XFF }; enum { // offsets/enumeration of IIO stacks IIO_CBDMA = 0, // shared with DMI IIO_PCIe0 = 1, IIO_PCIe1 = 2, IIO_PCIe2 = 3, IIO_MCP0 = 4, IIO_MCP1 = 5 }; // Offsets/enumeration of IIO stacks Skylake server. enum SkylakeIIOStacks { SKX_IIO_CBDMA_DMI = 0, SKX_IIO_PCIe0 = 1, SKX_IIO_PCIe1 = 2, SKX_IIO_PCIe2 = 3, SKX_IIO_MCP0 = 4, SKX_IIO_MCP1 = 5, SKX_IIO_STACK_COUNT = 6 }; // Offsets/enumeration of IIO stacks for IceLake server. enum IcelakeIIOStacks { ICX_IIO_PCIe0 = 0, ICX_IIO_PCIe1 = 1, ICX_IIO_MCP0 = 2, ICX_IIO_PCIe2 = 3, ICX_IIO_PCIe3 = 4, ICX_IIO_CBDMA_DMI = 5, ICX_IIO_STACK_COUNT = 6 }; // Offsets/enumeration of IIO stacks for IceLake server. enum SnowridgeIIOStacks { SNR_IIO_QAT = 0, SNR_IIO_CBDMA_DMI = 1, SNR_IIO_NIS = 2, SNR_IIO_HQM = 3, SNR_IIO_PCIe0 = 4, SNR_IIO_STACK_COUNT = 5 }; enum BDXIIOStacks { BDX_IIO_STACK_COUNT = 1 }; enum IDX_IP { IDX_IAA = 0, IDX_DSA, IDX_QAT, IDX_MAX }; enum IDX_OPERATION { QAT_TLM_STOP = 0, QAT_TLM_START, QAT_TLM_REFRESH, QAT_TLM_MAX }; enum IDX_STATE { IDX_STATE_OFF = 0, IDX_STATE_ON, }; struct SimplePCIeDevInfo { enum PCIeWidthMode width; std::string pciDevName; std::string busNumber; SimplePCIeDevInfo() : width(XFF) { } }; /*! \brief Custom Core event description See "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3B: System Programming Guide, Part 2" for the concrete values of the data structure fields, e.g. Appendix A.2 "Performance Monitoring Events for Intel(r) Core(tm) Processor Family and Xeon Processor Family" */ struct CustomCoreEventDescription { int32 event_number = 0, umask_value = 0; }; /*! \brief Extended custom core event description In contrast to CustomCoreEventDescription supports configuration of all fields. See "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3B: System Programming Guide, Part 2" for the concrete values of the data structure fields, e.g. Appendix A.2 "Performance Monitoring Events for Intel(r) Core(tm) Processor Family and Xeon Processor Family" */ struct ExtendedCustomCoreEventDescription { FixedEventControlRegister * fixedCfg; // if NULL, then default configuration performed for fixed counters uint32 nGPCounters; // number of general purpose counters EventSelectRegister * gpCounterCfg; // general purpose counters, if NULL, then default configuration performed for GP counters EventSelectRegister * gpCounterHybridAtomCfg; // general purpose counters for Atom cores in hybrid processors uint64 OffcoreResponseMsrValue[2]; uint64 LoadLatencyMsrValue, FrontendMsrValue; bool defaultUncoreProgramming{true}; static uint64 invalidMsrValue() { return ~0ULL; } ExtendedCustomCoreEventDescription() : fixedCfg(NULL), nGPCounters(0), gpCounterCfg(nullptr), gpCounterHybridAtomCfg(nullptr), LoadLatencyMsrValue(invalidMsrValue()), FrontendMsrValue(invalidMsrValue()) { OffcoreResponseMsrValue[0] = 0; OffcoreResponseMsrValue[1] = 0; } }; struct CustomIIOEventDescription { /* We program the same counters to every IIO Stacks */ std::string eventNames[4]; IIOPMUCNTCTLRegister eventOpcodes[4]; int multiplier[4]; //Some IIO event requires transformation to get meaningful output (i.e. DWord to bytes) int divider[4]; //We usually like to have some kind of divider (i.e. /10e6 ) }; struct MSREventPosition { enum constants { index = 0, type = 1 }; }; enum MSRType { Static = 0, Freerun = 1 }; private: ProgramMode mode; CustomCoreEventDescription coreEventDesc[PERF_MAX_CUSTOM_COUNTERS]; CustomCoreEventDescription hybridAtomEventDesc[PERF_MAX_CUSTOM_COUNTERS]; std::vector socketRefCore; bool canUsePerf; #ifdef PCM_USE_PERF typedef std::vector > PerfEventHandleContainer; PerfEventHandleContainer perfEventHandle; std::vector perfEventTaskHandle; void readPerfData(uint32 core, std::vector & data); void closePerfHandles(const bool silent = false); enum { PERF_INST_RETIRED_POS = 0, PERF_CPU_CLK_UNHALTED_THREAD_POS = 1, PERF_CPU_CLK_UNHALTED_REF_POS = 2, PERF_GEN_EVENT_0_POS = 3, PERF_GEN_EVENT_1_POS = 4, PERF_GEN_EVENT_2_POS = 5, PERF_GEN_EVENT_3_POS = 6, PERF_TOPDOWN_SLOTS_POS = PERF_GEN_EVENT_0_POS + PERF_MAX_CUSTOM_COUNTERS, PERF_TOPDOWN_FRONTEND_POS = PERF_TOPDOWN_SLOTS_POS + 1, PERF_TOPDOWN_BADSPEC_POS = PERF_TOPDOWN_SLOTS_POS + 2, PERF_TOPDOWN_BACKEND_POS = PERF_TOPDOWN_SLOTS_POS + 3, PERF_TOPDOWN_RETIRING_POS = PERF_TOPDOWN_SLOTS_POS + 4, PERF_TOPDOWN_MEM_BOUND_POS = PERF_TOPDOWN_SLOTS_POS + 5, PERF_TOPDOWN_FETCH_LAT_POS = PERF_TOPDOWN_SLOTS_POS + 6, PERF_TOPDOWN_BR_MISPRED_POS = PERF_TOPDOWN_SLOTS_POS + 7, PERF_TOPDOWN_HEAVY_OPS_POS = PERF_TOPDOWN_SLOTS_POS + 8 }; std::array perfTopDownPos; enum { PERF_GROUP_LEADER_COUNTER = PERF_INST_RETIRED_POS, PERF_TOPDOWN_GROUP_LEADER_COUNTER = PERF_TOPDOWN_SLOTS_POS }; #endif static std::ofstream * outfile; // output file stream static std::streambuf * backup_ofile; // backup of original output = cout static std::streambuf * backup_ofile_cerr; // backup of original output = cerr int run_state; // either running (1) or sleeping (0) bool needToRestoreNMIWatchdog; bool cleanupPEBS{false}; std::vector > lastProgrammedCustomCounters; uint32 checkCustomCoreProgramming(std::shared_ptr msr); ErrorCode programCoreCounters(int core, const PCM::ProgramMode mode, const ExtendedCustomCoreEventDescription * pExtDesc, std::vector & programmedCustomCounters, const std::vector & tids); bool PMUinUse(); void cleanupPMU(const bool silent = false); void cleanupRDT(const bool silent = false); void computeQPISpeedBeckton(int core_nr); void destroyMSR(); void computeNominalFrequency(); static bool isCPUModelSupported(const int model_); std::string getSupportedUarchCodenames() const; std::string getUnsupportedMessage() const; bool detectModel(); bool checkModel(); void initCStateSupportTables(); bool discoverSystemTopology(); void printSystemTopology() const; bool initMSR(); bool detectNominalFrequency(); void showSpecControlMSRs(); void initEnergyMonitoring(); void initUncoreObjects(); /*! * \brief initializes each core with an RMID * * \returns nothing */ void initRDT(); /*! * \brief Initializes RDT * * Initializes RDT infrastructure through resctrl Linux driver or direct MSR programming. * For the latter: initializes each core event MSR with an RMID for QOS event (L3 cache monitoring or memory bandwidth monitoring) * \returns nothing */ void initQOSevent(const uint64 event, const int32 core); void programBecktonUncore(int core); void programNehalemEPUncore(int core); void enableJKTWorkaround(bool enable); template void readAndAggregateMemoryBWCounters(const uint32 core, CounterStateType & counterState); template void readAndAggregateUncoreMCCounters(const uint32 socket, CounterStateType & counterState); template void readAndAggregateEnergyCounters(const uint32 socket, CounterStateType & counterState); template void readPackageThermalHeadroom(const uint32 socket, CounterStateType & counterState); template void readAndAggregatePackageCStateResidencies(std::shared_ptr msr, CounterStateType & result); public: struct RawPMUConfig; void programCXLCM(); void programCXLDP(); template void readAndAggregateCXLCMCounters(CounterStateType & counterState); private: template void readMSRs(std::shared_ptr msr, const RawPMUConfig & msrConfig, CounterStateType & result); void readQPICounters(SystemCounterState & counterState); void readSystemEnergyStatus(SystemCounterState & systemState); void readPCICFGRegisters(SystemCounterState& result); void readTPMIRegisters(SystemCounterState& result); void readMMIORegisters(SystemCounterState& result); void readPMTRegisters(SystemCounterState& result); void reportQPISpeed() const; void readCoreCounterConfig(const bool complainAboutMSR = false); void readCPUMicrocodeLevel(); void globalFreezeUncoreCountersInternal(const unsigned long long int freeze); uint64 CX_MSR_PMON_CTRY(uint32 Cbo, uint32 Ctr) const; uint64 CX_MSR_PMON_BOX_FILTER(uint32 Cbo) const; uint64 CX_MSR_PMON_BOX_FILTER1(uint32 Cbo) const; uint64 CX_MSR_PMON_CTLY(uint32 Cbo, uint32 Ctl) const; uint64 CX_MSR_PMON_BOX_CTL(uint32 Cbo) const; uint32 getMaxNumOfCBoxesInternal() const; void programCboOpcodeFilter(const uint32 opc0, UncorePMU & pmu, const uint32 nc_, const uint32 opc1, const uint32 loc, const uint32 rem); void initLLCReadMissLatencyEvents(uint64 * events, uint32 & opCode); void initCHARequestEvents(uint64 * events); void programCbo(); template static void program(UncorePMU& pmu, const Iterator& eventsBegin, const Iterator& eventsEnd, const uint32 extra) { if (!eventsBegin) return; Iterator curEvent = eventsBegin; const auto cpu_family_model = PCM::getInstance()->getCPUFamilyModel(); for (int c = 0; curEvent != eventsEnd && size_t(c) < pmu.size(); ++c, ++curEvent) { auto ctrl = pmu.counterControl[c]; if (ctrl.get() != nullptr) { switch (cpu_family_model) { case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: *ctrl = *curEvent; break; default: *ctrl = MC_CH_PCI_PMON_CTL_EN; *ctrl = MC_CH_PCI_PMON_CTL_EN | *curEvent; } } } if (extra) { pmu.resetUnfreeze(extra); } } void programPCU(uint32 * events, const uint64 filter); void programUBOX(const uint64* events); void programCXLDP(const uint64* events); void programCXLCM(const uint64* events); void cleanupUncorePMUs(const bool silent = false); bool isCLX() const // Cascade Lake-SP { return (PCM::SKX == cpu_family_model) && (cpu_stepping > 4 && cpu_stepping < 8); } static bool isCPX(int cpu_family_model_, int cpu_stepping_) // Cooper Lake { return (PCM::SKX == cpu_family_model_) && (cpu_stepping_ >= 10); } bool isCPX() const { return isCPX(cpu_family_model, cpu_stepping); } void initUncorePMUsDirect(); void initUncorePMUsPerf(); bool isRDTDisabled() const; #ifdef __linux__ bool perfSupportsTopDown(); #endif public: static bool isInitialized() { return instance != nullptr; } //! check if TMA level 1 metrics are supported bool isHWTMAL1Supported() const; //! check if TMA level 2 metrics are supported bool isHWTMAL2Supported() const { return isHWTMAL1Supported() && ( SPR == cpu_family_model || EMR == cpu_family_model || GNR == cpu_family_model || GNR_D == cpu_family_model ); } enum EventPosition { TOR_OCCUPANCY = 0, TOR_INSERTS = 1, REQUESTS_ALL = 2, REQUESTS_LOCAL = 3, CXL_TxC_MEM = 0, // works only on counters 0-3 CXL_TxC_CACHE = 1, // works only on counters 0-3 CXL_RxC_MEM = 4, // works only on counters 4-7 CXL_RxC_CACHE = 5 // works only on counters 4-7 }; //! check if in secure boot mode bool isSecureBoot() const; //! true if Linux perf for uncore PMU programming should AND can be used internally bool useLinuxPerfForUncore() const; //! true if the CPU is hybrid bool isHybrid() const { return hybrid; } /*! \brief The system, sockets, uncores, cores and threads are structured like a tree \returns a reference to a const System object representing the root of the tree */ SystemRoot const & getSystemTopology() const { return *systemTopology; } //! prints detailed system topology void printDetailedSystemTopology(const int detailLevel = 0); /*! \brief checks if QOS monitoring support present \returns true or false */ bool QOSMetricAvailable() const; /*! \brief checks L3 cache support for QOS present \returns true or false */ bool L3QOSMetricAvailable() const; /*! \brief checks if L3 cache monitoring present \returns true or false */ bool L3CacheOccupancyMetricAvailable() const; /*! \brief checks if local memory bandwidth monitoring present \returns true or false */ bool CoreLocalMemoryBWMetricAvailable() const; /*! \brief checks if total memory bandwidth monitoring present \returns true or false */ bool CoreRemoteMemoryBWMetricAvailable() const; /*! * \brief returns the max number of RMID supported by socket * * \returns maximum number of RMID supported by socket */ unsigned getMaxRMID() const; //! \brief Returns the number of IIO stacks per socket uint32 getMaxNumOfIIOStacks() const; /*! \brief Returns the number of IDX accel devs \param accel index of IDX accel */ uint32 getNumOfIDXAccelDevs(int accel) const; //! \brief Returns the number of IDX counters uint32 getMaxNumOfIDXAccelCtrs(int accel) const; //! \brief Returns the numa node of IDX accel dev uint32 getNumaNodeOfIDXAccelDev(uint32 accel, uint32 dev) const; //! \brief Returns the socketid of IDX accel dev uint32 getCPUSocketIdOfIDXAccelDev(uint32 accel, uint32 dev) const; //! \brief Returns the platform support IDX accel dev or NOT bool supportIDXAccelDev() const; /*! \brief Returns PCM object Returns PCM object. If the PCM has not been created before than an instance is created. PCM is a singleton. \return Pointer to PCM object */ static PCM * getInstance(); // the only way to get access /*! \brief Checks the status of PCM object Call this method to check if PCM gained access to model specific registers. The method is deprecated, see program error code instead. \return true iff access to model specific registers works without problems */ bool good(); // true if access to CPU counters works /*! \brief Returns the error message Call this when good() returns false, otherwise return an empty string */ const std::string & getErrorMessage() const { return errorMessage; } /*! \brief Programs performance counters \param mode_ mode of programming, see ProgramMode definition \param parameter_ optional parameter for some of programming modes \param silent set to true to silence diagnostic messages \param pid restrict core metrics only to specified pid (process id) Call this method before you start using the performance counting routines. \warning Using this routines with other tools that *program* Performance Monitoring Units (PMUs) on CPUs is not recommended because PMU can not be shared. Tools that are known to program PMUs: Intel(r) VTune(tm), Intel(r) Performance Tuning Utility (PTU). This code may make VTune or PTU measurements invalid. VTune or PTU measurement may make measurement with this code invalid. Please enable either usage of these routines or VTune/PTU/etc. */ ErrorCode program(const ProgramMode mode_ = DEFAULT_EVENTS, const void * parameter_ = NULL, const bool silent = false, const int pid = -1); // program counters and start counting /*! \brief checks the error without side effects. \throw std::system_error generic_category exception with PCM error code. \param code error code from the 'program' call */ void checkStatus(const ErrorCode status); /*! \brief checks the error and suggests solution and/or exits the process \param code error code from the 'program' call */ void checkError(const ErrorCode code); /*! \brief Programs uncore latency counters on microarchitectures codename SandyBridge-EP and later Xeon uarch \param enable_pmm enables DDR/PMM. See possible profile values in pcm-latency.cpp example Call this method before you start using the latency counter routines on microarchitecture codename SandyBridge-EP and later Xeon uarch \warning After this call the memory and QPI bandwidth counters on microarchitecture codename SandyBridge-EP and later Xeon uarch will not work. \warning Using this routines with other tools that *program* Performance Monitoring Units (PMUs) on CPUs is not recommended because PMU can not be shared. Tools that are known to program PMUs: Intel(r) VTune(tm), Intel(r) Performance Tuning Utility (PTU). This code may make VTune or PTU measurements invalid. VTune or PTU measurement may make measurement with this code invalid. Please enable either usage of these routines or VTune/PTU/etc. */ ErrorCode programServerUncoreLatencyMetrics(bool enable_pmm); /*! \brief Programs uncore power/energy counters on microarchitectures codename SandyBridge-EP and later Xeon uarch \param mc_profile profile for integrated memory controller PMU. See possible profile values in pcm-power.cpp example \param pcu_profile profile for power control unit PMU. See possible profile values in pcm-power.cpp example \param freq_bands array of three integer values for core frequency band monitoring. See usage in pcm-power.cpp example Call this method before you start using the power counter routines on microarchitecture codename SandyBridge-EP and later Xeon uarch \warning After this call the memory and QPI bandwidth counters on microarchitecture codename SandyBridge-EP and later Xeon uarch will not work. \warning Using this routines with other tools that *program* Performance Monitoring Units (PMUs) on CPUs is not recommended because PMU can not be shared. Tools that are known to program PMUs: Intel(r) VTune(tm), Intel(r) Performance Tuning Utility (PTU). This code may make VTune or PTU measurements invalid. VTune or PTU measurement may make measurement with this code invalid. Please enable either usage of these routines or VTune/PTU/etc. */ ErrorCode programServerUncorePowerMetrics(int mc_profile, int pcu_profile, int * freq_bands = NULL); /* \brief Program memory counters (disables programming performance counters) \param rankA count DIMM rank1 statistics (disables memory channel monitoring) \param rankB count DIMM rank2 statistics (disables memory channel monitoring) \brief metrics metric set (see the ServerUncoreMemoryMetrics enum) Call this method before you start using the memory counter routines on microarchitecture codename SandyBridge-EP and later Xeon uarch \warning Using this routines with other tools that *program* Performance Monitoring Units (PMUs) on CPUs is not recommended because PMU can not be shared. Tools that are known to program PMUs: Intel(r) VTune(tm), Intel(r) Performance Tuning Utility (PTU). This code may make VTune or PTU measurements invalid. VTune or PTU measurement may make measurement with this code invalid. Please enable either usage of these routines or VTune/PTU/etc. */ ErrorCode programServerUncoreMemoryMetrics(const ServerUncoreMemoryMetrics & metrics, int rankA = -1, int rankB = -1); // vector of IDs. E.g. for core {raw event} or {raw event, offcore response1 msr value, } or {raw event, offcore response1 msr value, offcore response2} // or for cha/cbo {raw event, filter value}, etc // + user-supplied name typedef std::array RawEventEncoding; typedef std::pair RawEventConfig; struct RawPMUConfig { std::vector programmable; std::vector fixed; }; enum { OCR0Pos = 1, OCR1Pos = 2, LoadLatencyPos = 3, FrontendPos = 4 }; typedef std::map RawPMUConfigs; ErrorCode program(const RawPMUConfigs& curPMUConfigs, const bool silent = false, const int pid = -1); struct TPMIEventPosition { enum constants { ID = 0, offset = 1, type = 2 }; }; typedef std::shared_ptr TPMIRegisterEncoding; // = TPMIHandle shared ptr struct TPMIRegisterEncodingHash { std::size_t operator()(const RawEventEncoding & e) const { std::size_t h1 = std::hash{}(e[TPMIEventPosition::ID]); std::size_t h2 = std::hash{}(e[TPMIEventPosition::offset]); return h1 ^ (h2 << 1ULL); } }; struct TPMIRegisterEncodingCmp { bool operator ()(const RawEventEncoding& a, const RawEventEncoding& b) const { return a[TPMIEventPosition::ID] == b[TPMIEventPosition::ID] && a[TPMIEventPosition::offset] == b[TPMIEventPosition::offset]; } }; struct PCICFGEventPosition { enum constants { deviceID = 0, offset = 1, type = 2, width = 5 }; }; typedef std::pair, uint32> PCICFGRegisterEncoding; // PciHandleType shared ptr, offset struct PCICFGRegisterEncodingHash { std::size_t operator()(const RawEventEncoding & e) const { std::size_t h1 = std::hash{}(e[PCICFGEventPosition::deviceID]); std::size_t h2 = std::hash{}(e[PCICFGEventPosition::offset]); std::size_t h3 = std::hash{}(e[PCICFGEventPosition::width]); return h1 ^ (h2 << 1ULL) ^ (h3 << 2ULL); } }; struct PCICFGRegisterEncodingCmp { bool operator ()(const RawEventEncoding& a, const RawEventEncoding& b) const { return a[PCICFGEventPosition::deviceID] == b[PCICFGEventPosition::deviceID] && a[PCICFGEventPosition::offset] == b[PCICFGEventPosition::offset] && a[PCICFGEventPosition::width] == b[PCICFGEventPosition::width]; } }; struct MMIOEventPosition { enum constants { deviceID = PCICFGEventPosition::deviceID, offset = PCICFGEventPosition::offset, type = PCICFGEventPosition::type, membar_bits1 = 3, membar_bits2 = 4, width = PCICFGEventPosition::width }; }; typedef std::pair, uint32> MMIORegisterEncoding; // MMIORange shared ptr, offset struct MMIORegisterEncodingHash : public PCICFGRegisterEncodingHash { std::size_t operator()(const RawEventEncoding& e) const { std::size_t h4 = std::hash{}(e[MMIOEventPosition::membar_bits1]); std::size_t h5 = std::hash{}(e[MMIOEventPosition::membar_bits2]); return PCICFGRegisterEncodingHash::operator()(e) ^ (h4 << 3ULL) ^ (h5 << 4ULL); } }; struct MMIORegisterEncodingCmp : public PCICFGRegisterEncodingCmp { bool operator ()(const RawEventEncoding& a, const RawEventEncoding& b) const { return PCICFGRegisterEncodingCmp::operator()(a,b) && a[MMIOEventPosition::membar_bits1] == b[MMIOEventPosition::membar_bits1] && a[MMIOEventPosition::membar_bits2] == b[MMIOEventPosition::membar_bits2]; } }; struct PMTEventPosition { enum constants { UID = PCICFGEventPosition::deviceID, offset = PCICFGEventPosition::offset, type = PCICFGEventPosition::type, lsb = 3, msb = 4 }; }; struct PMTRegisterEncodingHash { std::size_t operator()(const RawEventEncoding & e) const { return std::hash{}(e[PMTEventPosition::UID]); } }; struct PMTRegisterEncodingHash2 { std::size_t operator()(const RawEventEncoding & e) const { std::size_t h1 = std::hash{}(e[PMTEventPosition::UID]); std::size_t h2 = std::hash{}(e[PMTEventPosition::offset]); std::size_t h3 = std::hash{}(e[PMTEventPosition::lsb]); return h1 ^ (h2 << 1ULL) ^ (h3 << 2ULL); } }; struct PMTRegisterEncodingCmp { bool operator ()(const RawEventEncoding& a, const RawEventEncoding& b) const { return a[PMTEventPosition::UID] == b[PMTEventPosition::UID]; } }; typedef std::shared_ptr PMTRegisterEncoding; // TelemetryArray shared ptr private: std::unordered_map, TPMIRegisterEncodingHash, TPMIRegisterEncodingCmp> TPMIRegisterLocations{}; std::unordered_map, PCICFGRegisterEncodingHash, PCICFGRegisterEncodingCmp> PCICFGRegisterLocations{}; std::unordered_map, MMIORegisterEncodingHash, MMIORegisterEncodingCmp> MMIORegisterLocations{}; std::unordered_map, PMTRegisterEncodingHash, PMTRegisterEncodingCmp> PMTRegisterLocations{}; public: TopologyEntry::CoreType getCoreType(const unsigned coreID) const { assert(coreID < topology.size()); return topology[coreID].core_type; } std::pair getOCREventNr(const int event, const unsigned coreID) const { assert (coreID < topology.size()); if (hybrid) { switch (cpu_family_model) { case ADL: case RPL: case MTL: case LNL: case ARL: if (topology[coreID].core_type == TopologyEntry::Atom) { return std::make_pair(OFFCORE_RESPONSE_0_EVTNR, event + 1); } break; } } bool useGLCOCREvent = false; switch (cpu_family_model) { case SPR: case EMR: case ADL: // ADL big core (GLC) case RPL: case MTL: case LNL: case ARL: useGLCOCREvent = true; break; } switch (event) { case 0: return std::make_pair(useGLCOCREvent ? GLC_OFFCORE_RESPONSE_0_EVTNR : OFFCORE_RESPONSE_0_EVTNR, OFFCORE_RESPONSE_0_UMASK); case 1: return std::make_pair(useGLCOCREvent ? GLC_OFFCORE_RESPONSE_1_EVTNR : OFFCORE_RESPONSE_1_EVTNR, OFFCORE_RESPONSE_1_UMASK); } assert (false && "wrong event nr in getOCREventNr"); return std::make_pair(0U, 0U); } //! \brief Freezes uncore event counting using global control MSR void globalFreezeUncoreCounters(); //! \brief Unfreezes uncore event counting using global control MSR void globalUnfreezeUncoreCounters(); //! \brief Freezes uncore event counting void freezeServerUncoreCounters(); //! \brief Unfreezes uncore event counting void unfreezeServerUncoreCounters(); /*! \brief Reads the power/energy counter state of a socket (works only on microarchitecture codename SandyBridge-EP) \param socket socket id \return State of power counters in the socket */ ServerUncoreCounterState getServerUncoreCounterState(uint32 socket); /*! \brief Cleanups resources and stops performance counting One needs to call this method when your program finishes or/and you are not going to use the performance counting routines anymore. */ void cleanup(const bool silent = false); /*! \brief Forces PMU reset If there is no chance to free up PMU from other applications you might try to call this method at your own risk. */ void resetPMU(); /*! \brief Reads all counter states (including system, sockets and cores) \param systemState system counter state (return parameter) \param socketStates socket counter states (return parameter) \param coreStates core counter states (return parameter) \param readAndAggregateSocketUncoreCounters read and aggregate socket uncore counters */ void getAllCounterStates(SystemCounterState & systemState, std::vector & socketStates, std::vector & coreStates, const bool readAndAggregateSocketUncoreCounters = true); /*! \brief Reads uncore counter states (including system and sockets) but no core counters \param systemState system counter state (return parameter) \param socketStates socket counter states (return parameter) */ void getUncoreCounterStates(SystemCounterState & systemState, std::vector & socketStates); /*! \brief Return true if the core in online \param os_core_id OS core id */ bool isCoreOnline(int32 os_core_id) const; /*! \brief Return true if the socket in online \param socket_id OS socket id */ bool isSocketOnline(int32 socket_id) const; /*! \brief Reads the counter state of the system System consists of several sockets (CPUs). Socket has a CPU in it. Socket (CPU) consists of several (logical) cores. \return State of counters in the entire system */ SystemCounterState getSystemCounterState(); /*! \brief Reads the counter state of a socket \param socket socket id \return State of counters in the socket */ SocketCounterState getSocketCounterState(uint32 socket); /*! \brief Reads the counter state of a (logical) core Be aware that during the measurement other threads may be scheduled on the same core by the operating system (this is called context-switching). The performance events caused by these threads will be counted as well. \param core core id \return State of counters in the core */ CoreCounterState getCoreCounterState(uint32 core); /*! \brief Reads number of logical cores in the system \return Number of logical cores in the system */ uint32 getNumCores() const; /*! \brief Reads number of online logical cores in the system \return Number of online logical cores in the system */ uint32 getNumOnlineCores() const; /*! \brief Reads number of sockets (CPUs) in the system \return Number of sockets in the system */ uint32 getNumSockets() const; /*! \brief Reads the accel type in the system \return acceltype */ uint32 getAccel() const; /*! \brief Sets the accel type in the system \return acceltype */ void setAccel(uint32 input); /*! \brief Reads the Number of AccelCounters in the system \return None */ uint32 getNumberofAccelCounters() const; /*! \brief Sets the Number of AccelCounters in the system \return number of counters */ void setNumberofAccelCounters(uint32 input); /*! \brief Reads number of online sockets (CPUs) in the system \return Number of online sockets in the system */ uint32 getNumOnlineSockets() const; /*! \brief Reads how many hardware threads has a physical core "Hardware thread" is a logical core in a different terminology. If Intel(r) Hyperthreading(tm) is enabled then this function returns 2. \return Number of hardware threads per physical core */ uint32 getThreadsPerCore() const; /*! \brief Checks if SMT (HyperThreading) is enabled. \return true iff SMT (HyperThreading) is enabled. */ bool getSMT() const; // returns true iff SMT ("Hyperthreading") is on /*! \brief Reads the nominal core frequency \return Nominal frequency in Hz */ uint64 getNominalFrequency() const; // in Hz /*! \brief runs CPUID.0xF.0x01 to get the L3 up scaling factor to calculate L3 Occupancy * Scaling factor is returned in EBX register after running the CPU instruction * \return L3 up scaling factor */ uint32 getL3ScalingFactor() const; /*! \brief runs CPUID.0xB.0x01 to get maximum logical cores (including SMT) per socket. * max_lcores_per_socket is returned in EBX[15:0]. Compare this value with number of cores per socket * detected in the system to see if some cores are offlined * \return true iff max_lcores_per_socket == number of cores per socket detected */ bool isSomeCoreOfflined(); /*! \brief Returns the maximum number of custom (general-purpose) core events supported by CPU */ int32 getMaxCustomCoreEvents(); /*! \brief Returns cpu model id number from cpuid instruction */ /* static int getCPUModelFromCPUID(); */ /*! \brief Returns cpu family and model id number from cpuid instruction * \return cpu family and model id number (model id is in the lower 8 bits, family id is in the next 8 bits) */ static int getCPUFamilyModelFromCPUID(); #define PCM_CPU_FAMILY_MODEL(family_, model_) (((family_) << 8) + (model_)) //! \brief Identifiers of supported CPU models enum SupportedCPUModels { NEHALEM_EP = PCM_CPU_FAMILY_MODEL(6, 26), NEHALEM = PCM_CPU_FAMILY_MODEL(6, 30), ATOM = PCM_CPU_FAMILY_MODEL(6, 28), ATOM_2 = PCM_CPU_FAMILY_MODEL(6, 53), CENTERTON = PCM_CPU_FAMILY_MODEL(6, 54), BAYTRAIL = PCM_CPU_FAMILY_MODEL(6, 55), AVOTON = PCM_CPU_FAMILY_MODEL(6, 77), CHERRYTRAIL = PCM_CPU_FAMILY_MODEL(6, 76), APOLLO_LAKE = PCM_CPU_FAMILY_MODEL(6, 92), GEMINI_LAKE = PCM_CPU_FAMILY_MODEL(6, 122), DENVERTON = PCM_CPU_FAMILY_MODEL(6, 95), SNOWRIDGE = PCM_CPU_FAMILY_MODEL(6, 134), ELKHART_LAKE = PCM_CPU_FAMILY_MODEL(6, 150), JASPER_LAKE = PCM_CPU_FAMILY_MODEL(6, 156), CLARKDALE = PCM_CPU_FAMILY_MODEL(6, 37), WESTMERE_EP = PCM_CPU_FAMILY_MODEL(6, 44), NEHALEM_EX = PCM_CPU_FAMILY_MODEL(6, 46), WESTMERE_EX = PCM_CPU_FAMILY_MODEL(6, 47), SANDY_BRIDGE = PCM_CPU_FAMILY_MODEL(6, 42), JAKETOWN = PCM_CPU_FAMILY_MODEL(6, 45), IVY_BRIDGE = PCM_CPU_FAMILY_MODEL(6, 58), HASWELL = PCM_CPU_FAMILY_MODEL(6, 60), HASWELL_ULT = PCM_CPU_FAMILY_MODEL(6, 69), HASWELL_2 = PCM_CPU_FAMILY_MODEL(6, 70), IVYTOWN = PCM_CPU_FAMILY_MODEL(6, 62), HASWELLX = PCM_CPU_FAMILY_MODEL(6, 63), BROADWELL = PCM_CPU_FAMILY_MODEL(6, 61), BROADWELL_XEON_E3 = PCM_CPU_FAMILY_MODEL(6, 71), BDX_DE = PCM_CPU_FAMILY_MODEL(6, 86), SKL_UY = PCM_CPU_FAMILY_MODEL(6, 78), KBL = PCM_CPU_FAMILY_MODEL(6, 158), KBL_1 = PCM_CPU_FAMILY_MODEL(6, 142), CML = PCM_CPU_FAMILY_MODEL(6, 166), CML_1 = PCM_CPU_FAMILY_MODEL(6, 165), ICL = PCM_CPU_FAMILY_MODEL(6, 126), ICL_1 = PCM_CPU_FAMILY_MODEL(6, 125), RKL = PCM_CPU_FAMILY_MODEL(6, 167), TGL = PCM_CPU_FAMILY_MODEL(6, 140), TGL_1 = PCM_CPU_FAMILY_MODEL(6, 141), ADL = PCM_CPU_FAMILY_MODEL(6, 151), ADL_1 = PCM_CPU_FAMILY_MODEL(6, 154), RPL = PCM_CPU_FAMILY_MODEL(6, 0xb7), RPL_1 = PCM_CPU_FAMILY_MODEL(6, 0xba), RPL_2 = PCM_CPU_FAMILY_MODEL(6, 0xbf), RPL_3 = PCM_CPU_FAMILY_MODEL(6, 0xbe), MTL = PCM_CPU_FAMILY_MODEL(6, 0xAA), LNL = PCM_CPU_FAMILY_MODEL(6, 0xBD), ARL = PCM_CPU_FAMILY_MODEL(6, 197), ARL_1 = PCM_CPU_FAMILY_MODEL(6, 198), BDX = PCM_CPU_FAMILY_MODEL(6, 79), KNL = PCM_CPU_FAMILY_MODEL(6, 87), SKL = PCM_CPU_FAMILY_MODEL(6, 94), SKX = PCM_CPU_FAMILY_MODEL(6, 85), ICX_D = PCM_CPU_FAMILY_MODEL(6, 108), ICX = PCM_CPU_FAMILY_MODEL(6, 106), SPR = PCM_CPU_FAMILY_MODEL(6, 143), EMR = PCM_CPU_FAMILY_MODEL(6, 207), GNR = PCM_CPU_FAMILY_MODEL(6, 173), SRF = PCM_CPU_FAMILY_MODEL(6, 175), GNR_D = PCM_CPU_FAMILY_MODEL(6, 174), GRR = PCM_CPU_FAMILY_MODEL(6, 182), END_OF_MODEL_LIST = 0x0ffff }; #define PCM_SKL_PATH_CASES \ case PCM::SKL_UY: \ case PCM::KBL: \ case PCM::KBL_1: \ case PCM::CML: \ case PCM::ICL: \ case PCM::RKL: \ case PCM::TGL: \ case PCM::SKL: private: bool useSKLPath() const { switch (cpu_family_model) { PCM_SKL_PATH_CASES return true; } return false; } RawPMUConfig threadMSRConfig{}, packageMSRConfig{}, tpmiConfig{}, pcicfgConfig{}, mmioConfig{}, pmtConfig{}; public: //! \brief Reads CPU family //! \return CPU family uint32 getCPUFamily() const { return (uint32)cpu_family; } //! \brief Reads CPU model id (use only with the family API together, don't always assume family 6) //! \return Internal CPU model ID uint32 getInternalCPUModel() const { return (uint32)cpu_model_private; } //! \brief Reads CPU family and model id //! \return CPU family and model ID (lowest 8 bits is the model, next 8 bits is the family) uint32 getCPUFamilyModel() const { return cpu_family_model; } //! \brief Reads CPU stepping id //! \return CPU stepping ID uint32 getCPUStepping() const { return (uint32)cpu_stepping; } //! \brief Determines physical thread of given processor ID within a core //! \param os_id processor identifier //! \return physical thread identifier int32 getThreadId(uint32 os_id) const { return (int32)topology[os_id].thread_id; } //! \brief Determines physical core of given processor ID within a socket //! \param os_id processor identifier //! \return physical core identifier int32 getCoreId(uint32 os_id) const { return (int32)topology[os_id].core_id; } //! \brief Determines physical tile (cores sharing L2 cache) of given processor ID //! \param os_id processor identifier //! \return physical tile identifier int32 getTileId(uint32 os_id) const { return (int32)topology[os_id].tile_id; } //! \brief Determines socket of given core //! \param core_id core identifier //! \return socket identifier int32 getSocketId(uint32 core_id) const { return (int32)topology[core_id].socket_id; } size_t getNumCXLPorts(uint32 socket) const { if (socket < cxlPMUs.size()) { return cxlPMUs[socket].size(); } return 0; } //! \brief Returns the number of Intel(r) Quick Path Interconnect(tm) links per socket //! \return number of QPI links per socket uint64 getQPILinksPerSocket() const { switch (cpu_family_model) { case NEHALEM_EP: case WESTMERE_EP: case CLARKDALE: if (num_sockets == 2) return 2; else return 1; case NEHALEM_EX: case WESTMERE_EX: return 4; case JAKETOWN: case IVYTOWN: case HASWELLX: case BDX_DE: case BDX: case SKX: case ICX: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: return (serverUncorePMUs.size() && serverUncorePMUs[0].get()) ? (serverUncorePMUs[0]->getNumQPIPorts()) : 0; } return 0; } //! \brief Returns the number of detected integrated memory controllers per socket uint32 getMCPerSocket() const { switch (cpu_family_model) { case NEHALEM_EP: case WESTMERE_EP: case CLARKDALE: return 1; case NEHALEM_EX: case WESTMERE_EX: return 2; case JAKETOWN: case IVYTOWN: case HASWELLX: case BDX_DE: case SKX: case ICX: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: case BDX: case KNL: return (serverUncorePMUs.size() && serverUncorePMUs[0].get()) ? (serverUncorePMUs[0]->getNumMC()) : 0; } return 0; } //! \brief Returns the total number of detected memory channels on all integrated memory controllers per socket size_t getMCChannelsPerSocket() const { switch (cpu_family_model) { case NEHALEM_EP: case WESTMERE_EP: case CLARKDALE: return 3; case NEHALEM_EX: case WESTMERE_EX: return 4; case JAKETOWN: case IVYTOWN: case HASWELLX: case BDX_DE: case SKX: case ICX: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: case BDX: case KNL: case SNOWRIDGE: return (serverUncorePMUs.size() && serverUncorePMUs[0].get()) ? (serverUncorePMUs[0]->getNumMCChannels()) : 0; } return 0; } //! \brief Returns the number of detected memory channels on given integrated memory controllers //! \param socket socket //! \param controller controller size_t getMCChannels(uint32 socket, uint32 controller) const { switch (cpu_family_model) { case NEHALEM_EP: case WESTMERE_EP: case CLARKDALE: return 3; case NEHALEM_EX: case WESTMERE_EX: return 4; case JAKETOWN: case IVYTOWN: case HASWELLX: case BDX_DE: case SKX: case ICX: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: case BDX: case KNL: case SNOWRIDGE: return (socket < serverUncorePMUs.size() && serverUncorePMUs[socket].get()) ? (serverUncorePMUs[socket]->getNumMCChannels(controller)) : 0; } return 0; } //! \brief Returns the total number of detected memory channels on all integrated memory controllers per socket size_t getEDCChannelsPerSocket() const { switch (cpu_family_model) { case KNL: return (serverUncorePMUs.size() && serverUncorePMUs[0].get()) ? (serverUncorePMUs[0]->getNumEDCChannels()) : 0; } return 0; } //! \brief Returns the max number of instructions per cycle //! \return max number of instructions per cycle uint32 getMaxIPC() const { if (ICL == cpu_family_model || TGL == cpu_family_model || RKL == cpu_family_model) return 5; switch (cpu_family_model) { case ADL: case RPL: case MTL: return 6; case LNL: case ARL: return 12; case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: return 4; case DENVERTON: return 3; case NEHALEM_EP: case WESTMERE_EP: case NEHALEM_EX: case WESTMERE_EX: case CLARKDALE: case SANDY_BRIDGE: case JAKETOWN: case IVYTOWN: case IVY_BRIDGE: case HASWELL: case HASWELLX: case BROADWELL: case BDX_DE: case BDX: PCM_SKL_PATH_CASES case SKX: return 4; case KNL: return 2; case ICX: return 5; case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: return 6; } if (isAtom()) { return 2; } std::cerr << "MaxIPC is not defined for your cpu family " << cpu_family << " model " << cpu_model_private << '\n'; assert (0); return 0; } //! \brief Returns the frequency of Power Control Unit uint64 getPCUFrequency() const { switch (cpu_family_model) { case JAKETOWN: case IVYTOWN: return 800000000ULL; // 800 MHz case HASWELLX: case BDX_DE: case BDX: case KNL: return 1000000000ULL; // 1 GHz case SKX: case ICX: case SNOWRIDGE: return 1100000000ULL; // 1.1 GHz } return 0; } //! \brief Returns whether it is a server part bool isServerCPU() const { switch (cpu_family_model) { case NEHALEM_EP: case NEHALEM_EX: case WESTMERE_EP: case WESTMERE_EX: case JAKETOWN: case IVYTOWN: case HASWELLX: case BDX: case BDX_DE: case SKX: case ICX: case SNOWRIDGE: case SPR: case EMR: case GNR: case GNR_D: case GRR: case SRF: case KNL: return true; default: return false; }; } //! \brief Returns whether it is a client part bool isClientCPU() const { return !isServerCPU(); } //! \brief Return TSC timer value in time units //! \param multiplier use 1 for seconds, 1000 for ms, 1000000 for mks, etc (default is 1000: ms) //! \param core core to read on-chip TSC value (default is 0) //! \return time counter value uint64 getTickCount(uint64 multiplier = 1000 /* ms */, uint32 core = 0); uint64 getInvariantTSC_Fast(uint32 core = 0); //! \brief Returns uncore clock ticks on specified socket uint64 getUncoreClocks(const uint32 socket_); //! \brief Return QPI Link Speed in GBytes/second //! \warning Works only for Nehalem-EX (Xeon 7500) and Xeon E7 and E5 processors //! \return QPI Link Speed in GBytes/second uint64 getQPILinkSpeed(uint32 socketNr, uint32 linkNr) const { return hasPCICFGUncore() ? serverUncorePMUs[socketNr]->getQPILinkSpeed(linkNr) : max_qpi_speed; } //! \brief Returns how many joules are in an internal processor energy unit double getJoulesPerEnergyUnit() const { return joulesPerEnergyUnit; } //! \brief Returns thermal specification power of the package domain in Watt int32 getPackageThermalSpecPower() const { return pkgThermalSpecPower; } //! \brief Returns minimum power derived from electrical spec of the package domain in Watt int32 getPackageMinimumPower() const { return pkgMinimumPower; } //! \brief Returns maximum power derived from electrical spec of the package domain in Watt int32 getPackageMaximumPower() const { return pkgMaximumPower; } #ifndef NO_WINRING // In cases where loading the WinRing0 driver is not desirable as a fallback to MSR.sys, add -DNO_WINRING to compile command to remove ability to load driver //! \brief Loads and initializes Winring0 third party library for access to processor model specific and PCI configuration registers //! \return returns true in case of success static bool initWinRing0Lib(); #endif // NO_WINRING inline void disableJKTWorkaround() { disable_JKT_workaround = true; } enum PCIeEventCode { // PCIe read events (PCI devices reading from memory - application writes to disk/network/PCIe device) PCIeRdCur = 0x19E, // PCIe read current (full cache line) PCIeNSRd = 0x1E4, // PCIe non-snoop read (full cache line) // PCIe write events (PCI devices writing to memory - application reads from disk/network/PCIe device) PCIeWiLF = 0x194, // PCIe Write (non-allocating) (full cache line) PCIeItoM = 0x19C, // PCIe Write (allocating) (full cache line) PCIeNSWr = 0x1E5, // PCIe Non-snoop write (partial cache line) PCIeNSWrF = 0x1E6, // PCIe Non-snoop write (full cache line) // events shared by CPU and IO RFO = 0x180, // Demand Data RFO; share the same code for CPU, use tid to filter PCIe only traffic CRd = 0x181, // Demand Code Read DRd = 0x182, // Demand Data Read PRd = 0x187, // Partial Reads (UC) (MMIO Read) WiL = 0x18F, // Write Invalidate Line - partial (MMIO write), PL: Not documented in HSX/IVT ItoM = 0x1C8, // Request Invalidate Line; share the same code for CPU, use tid to filter PCIe only traffic SKX_RFO = 0x200, SKX_CRd = 0x201, SKX_DRd = 0x202, SKX_PRd = 0x207, SKX_WiL = 0x20F, SKX_RdCur = 0x21E, SKX_ItoM = 0x248, }; enum ChaPipelineQueue { None, IRQ, PRQ, }; enum CBoEventTid { RFOtid = 0x3E, ItoMtid = 0x3E, }; //! \brief Program uncore PCIe monitoring event(s) //! \param eventGroup - events to program for the same run void programPCIeEventGroup(eventGroup_t &eventGroup); uint64 getPCIeCounterData(const uint32 socket_, const uint32 ctr_); //! \brief Program CBO (or CHA on SKX+) counters //! \param events array with four raw event values //! \param opCode opcode match filter //! \param nc_ match non-coherent requests //! \param llc_lookup_tid_filter filter for LLC lookup event filter and TID filter (core and thread ID) //! \param loc match on local node target //! \param rem match on remote node target void programCbo(const uint64 * events, const uint32 opCode = 0, const uint32 nc_ = 0, const uint32 llc_lookup_tid_filter = 0, const uint32 loc = 1, const uint32 rem = 1); //! \brief Program CBO (or CHA on SKX+) counters //! \param events array with four raw event values //! \param filter0 raw filter value //! \param filter1 raw filter1 value void programCboRaw(const uint64* events, const uint64 filter0, const uint64 filter1); //! \brief Program MDF counters //! \param events array with four raw event values void programMDF(const uint64* events); //! \brief Get the state of PCIe counter(s) //! \param socket_ socket of the PCIe controller //! \return State of PCIe counter(s) PCIeCounterState getPCIeCounterState(const uint32 socket_, const uint32 ctr_ = 0); //! \brief Program uncore IIO events //! \param rawEvents events to program (raw format) //! \param IIOStack id of the IIO stack to program (-1 for all, if parameter omitted) void programIIOCounters(uint64 rawEvents[4], int IIOStack = -1); //! \brief Program uncore IRP events //! \param rawEvents events to program (raw format) //! \param IIOStack id of the IIO stack to program (-1 for all, if parameter omitted) void programIRPCounters(uint64 rawEvents[4], int IIOStack = -1); //! \brief Control QAT telemetry service //! \param dev device index //! \param operation control code void controlQATTelemetry(uint32 dev, uint32 operation); //! \brief Program IDX events //! \param events config of event to program //! \param filters_wq filters(work queue) of event to program //! \param filters_eng filters(engine) of event to program //! \param filters_tc filters(traffic class) of event to program //! \param filters_pgsz filters(page size) of event to program //! \param filters_xfersz filters(transfer size) of event to program void programIDXAccelCounters(uint32 accel, std::vector &events, std::vector &filters_wq, std::vector &filters_eng, std::vector &filters_tc, std::vector &filters_pgsz, std::vector &filters_xfersz); //! \brief Get the state of IIO counter //! \param socket socket of the IIO stack //! \param IIOStack id of the IIO stack //! \return State of IIO counter IIOCounterState getIIOCounterState(int socket, int IIOStack, int counter); //! \brief Get the states of the four IIO counters in bulk (faster than four single reads) //! \param socket socket of the IIO stack //! \param IIOStack id of the IIO stack //! \param result states of IIO counters (array of four IIOCounterState elements) void getIIOCounterStates(int socket, int IIOStack, IIOCounterState * result); //! \brief Get the state of IDX accel counter //! \param accel ip index //! \param dev device index //! \param counter_id perf counter index //! \return State of IDX counter IDXCounterState getIDXAccelCounterState(uint32 accel, uint32 dev, uint32 counter_id); uint64 extractCoreGenCounterValue(uint64 val); uint64 extractCoreFixedCounterValue(uint64 val); uint64 extractUncoreGenCounterValue(uint64 val); uint64 extractUncoreFixedCounterValue(uint64 val); uint64 extractQOSMonitoring(uint64 val); //! \brief Get a string describing the codename of the processor microarchitecture //! \param cpu_family_model_ cpu model (if no parameter provided the codename of the detected CPU is returned) const char * getUArchCodename(const int32 cpu_family_model_ = -1) const; //! \brief Get Brand string of processor static std::string getCPUBrandString(); std::string getCPUFamilyModelString(); static std::string getCPUFamilyModelString(const uint32 cpu_family, const uint32 cpu_model, const uint32 cpu_stepping); //! \brief Enables "force all RTM transaction abort" mode also enabling 4+ programmable counters on Skylake generation processors void enableForceRTMAbortMode(const bool silent = false); //! \brief queries status of "force all RTM transaction abort" mode bool isForceRTMAbortModeEnabled() const; //! \brief Disables "force all RTM transaction abort" mode restricting the number of programmable counters on Skylake generation processors to 3 void disableForceRTMAbortMode(const bool silent = false); //! \brief queries availability of "force all RTM transaction abort" mode static bool isForceRTMAbortModeAvailable(); //! \brief Get microcode level (returns -1 if retrieval not supported due to some restrictions) int64 getCPUMicrocodeLevel() const { return cpu_microcode_level; } //! \brief returns true if CPU model is Atom-based static bool isAtom(const int32 cpu_family_model_) { return cpu_family_model_ == ATOM || cpu_family_model_ == ATOM_2 || cpu_family_model_ == CENTERTON || cpu_family_model_ == BAYTRAIL || cpu_family_model_ == AVOTON || cpu_family_model_ == CHERRYTRAIL || cpu_family_model_ == APOLLO_LAKE || cpu_family_model_ == GEMINI_LAKE || cpu_family_model_ == DENVERTON // || cpu_family_model_ == SNOWRIDGE do not use Atom code for SNOWRIDGE ; } //! \brief returns true if CPU is Atom-based bool isAtom() const { return isAtom(cpu_family_model); } // From commit message: https://github.com/torvalds/linux/commit/e979121b1b1556e184492e6fc149bbe188fc83e6 bool memoryEventErrata() const { switch (cpu_family_model) { case SANDY_BRIDGE: case JAKETOWN: case IVYTOWN: case IVY_BRIDGE: case HASWELL: case HASWELLX: return true; } return false; } bool packageEnergyMetricsAvailable() const { return ( cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::SANDY_BRIDGE || cpu_family_model == PCM::IVY_BRIDGE || cpu_family_model == PCM::HASWELL || cpu_family_model == PCM::AVOTON || cpu_family_model == PCM::CHERRYTRAIL || cpu_family_model == PCM::BAYTRAIL || cpu_family_model == PCM::APOLLO_LAKE || cpu_family_model == PCM::GEMINI_LAKE || cpu_family_model == PCM::DENVERTON || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::ELKHART_LAKE || cpu_family_model == PCM::JASPER_LAKE || cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BROADWELL || cpu_family_model == PCM::BDX_DE || cpu_family_model == PCM::BDX || cpu_family_model == PCM::KNL || useSKLPath() || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::ADL || cpu_family_model == PCM::RPL || cpu_family_model == PCM::MTL || cpu_family_model == PCM::LNL || cpu_family_model == PCM::ARL || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GRR ); } bool dramEnergyMetricsAvailable() const { return ( cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX_DE || cpu_family_model == PCM::BDX || cpu_family_model == PCM::KNL || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GRR ); } bool systemEnergyMetricAvailable() const { return ( useSKLPath() || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::ADL || cpu_family_model == PCM::RPL || cpu_family_model == PCM::MTL || cpu_family_model == PCM::LNL || cpu_family_model == PCM::ARL || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GRR ); } bool packageThermalMetricsAvailable() const { return packageEnergyMetricsAvailable(); } bool outgoingQPITrafficMetricsAvailable() const { return getQPILinksPerSocket() > 0 && ( cpu_family_model == PCM::NEHALEM_EX || cpu_family_model == PCM::WESTMERE_EX || cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::SRF ); } bool incomingQPITrafficMetricsAvailable() const { return getQPILinksPerSocket() > 0 && ( cpu_family_model == PCM::NEHALEM_EX || cpu_family_model == PCM::WESTMERE_EX || cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::IVYTOWN || (cpu_family_model == PCM::SKX && cpu_stepping > 1) || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::SRF ); } bool localMemoryRequestRatioMetricAvailable() const { return cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GNR ; } bool qpiUtilizationMetricsAvailable() const { return outgoingQPITrafficMetricsAvailable(); } bool nearMemoryMetricsAvailable() const { return ( cpu_family_model == PCM::SRF || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D ); } bool memoryTrafficMetricsAvailable() const { return (!(isAtom() || cpu_family_model == PCM::CLARKDALE)) ; } bool HBMmemoryTrafficMetricsAvailable() const { return serverUncorePMUs.empty() == false && serverUncorePMUs[0].get() != nullptr && serverUncorePMUs[0]->HBMAvailable(); } size_t getHBMCASTransferSize() const { return (SPR == cpu_family_model) ? 32ULL : 64ULL; } bool memoryIOTrafficMetricAvailable() const { if (cpu_family_model == TGL) return false; return ( cpu_family_model == PCM::SANDY_BRIDGE || cpu_family_model == PCM::IVY_BRIDGE || cpu_family_model == PCM::HASWELL || cpu_family_model == PCM::BROADWELL || useSKLPath() ); } bool IIOEventsAvailable() const { return ( cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GRR || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D ); } bool uncoreFrequencyMetricAvailable() const { return MSR.empty() == false && getMaxNumOfUncorePMUs(UBOX_PMU_ID) > 0ULL && getNumCores() == getNumOnlineCores() && PCM::GNR != cpu_family_model && PCM::GNR_D != cpu_family_model && PCM::SRF != cpu_family_model ; } bool LatencyMetricsAvailable() const { return ( cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || useSKLPath() ); } bool DDRLatencyMetricsAvailable() const { return ( cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR ); } bool PMMTrafficMetricsAvailable() const { return ( isCLX() || isCPX() || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == SPR || cpu_family_model == EMR ); } bool PMMMemoryModeMetricsAvailable() const { return ( isCLX() || isCPX() || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SNOWRIDGE ); } bool PMMMixedModeMetricsAvailable() const { return PMMMemoryModeMetricsAvailable(); } bool LLCReadMissLatencyMetricsAvailable() const { return ( HASWELLX == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model || isCLX() || isCPX() #ifdef PCM_ENABLE_LLCRDLAT_SKX_MP || SKX == cpu_family_model #else || ((SKX == cpu_family_model) && (num_sockets == 1)) #endif || ICX == cpu_family_model || SPR == cpu_family_model || SNOWRIDGE == cpu_family_model ); } bool hasBecktonUncore() const { return ( cpu_family_model == PCM::NEHALEM_EX || cpu_family_model == PCM::WESTMERE_EX ); } bool hasPCICFGUncore() const // has PCICFG uncore PMON { return ( cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX_DE || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GRR || cpu_family_model == PCM::BDX || cpu_family_model == PCM::KNL ); } bool isSkxCompatible() const { return ( cpu_family_model == PCM::SKX ); } static bool hasUPI(const int32 cpu_family_model_) // Intel(r) Ultra Path Interconnect { return ( cpu_family_model_ == PCM::SKX || cpu_family_model_ == PCM::ICX || cpu_family_model_ == PCM::SPR || cpu_family_model_ == PCM::EMR || cpu_family_model_ == PCM::GNR || cpu_family_model_ == PCM::SRF ); } bool hasUPI() const { return hasUPI(cpu_family_model); } const char * xPI() const { if (hasUPI()) return "UPI"; return "QPI"; } bool hasCHA() const { return ( cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GRR ); } bool supportsHLE() const; bool supportsRTM() const; bool supportsRDTSCP() const; bool useSkylakeEvents() const { return useSKLPath() || PCM::SKX == cpu_family_model || PCM::ICX == cpu_family_model || PCM::SPR == cpu_family_model || PCM::EMR == cpu_family_model || PCM::GNR == cpu_family_model || PCM::GNR_D == cpu_family_model ; } bool hasClientMCCounters() const { return cpu_family_model == SANDY_BRIDGE || cpu_family_model == IVY_BRIDGE || cpu_family_model == HASWELL || cpu_family_model == BROADWELL || cpu_family_model == ADL || cpu_family_model == RPL || cpu_family_model == MTL || cpu_family_model == LNL || cpu_family_model == ARL || useSKLPath() ; } bool ppEnergyMetricsAvailable() const { return packageEnergyMetricsAvailable() && hasClientMCCounters() && num_sockets == 1; } static double getBytesPerFlit(int32 cpu_family_model_) { if (hasUPI(cpu_family_model_)) { // 172 bits per UPI flit return 172./8.; } // 8 bytes per QPI flit return 8.; } double getBytesPerFlit() const { return getBytesPerFlit(cpu_family_model); } static double getDataBytesPerFlit(const int32 cpu_family_model_) { if (hasUPI(cpu_family_model_)) { // 9 UPI flits to transfer 64 bytes return 64./9.; } // 8 bytes per QPI flit return 8.; } double getDataBytesPerFlit() const { return getDataBytesPerFlit(cpu_family_model); } static double getFlitsPerLinkCycle(const int32 cpu_family_model_) { if (hasUPI(cpu_family_model_)) { // 5 UPI flits sent every 6 link cycles return 5./6.; } return 2.; } static double getBytesPerLinkCycle(const int32 cpu_family_model_) { return getBytesPerFlit(cpu_family_model_) * getFlitsPerLinkCycle(cpu_family_model_); } double getBytesPerLinkCycle() const { return getBytesPerLinkCycle(cpu_family_model); } double getLinkTransfersPerLinkCycle() const { return 8.; } double getBytesPerLinkTransfer() const { return getBytesPerLinkCycle() / getLinkTransfersPerLinkCycle(); } //! \brief Setup ExtendedCustomCoreEventDescription object to read offcore (numa) counters for each processor type //! \param conf conf object to setup offcore MSR values void setupCustomCoreEventsForNuma(PCM::ExtendedCustomCoreEventDescription& conf) const; #define PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(m) bool is##m() const { return m; } PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L2CacheHitRatioAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L3CacheHitRatioAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L3CacheMissesAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L2CacheMissesAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L2CacheHitsAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L3CacheHitsNoSnoopAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L3CacheHitsSnoopAvailable) PCM_GENERATE_METRIC_AVAILABLE_FUNCTION(L3CacheHitsAvailable) #undef PCM_GEN_METRIC_AVAILABLE_FUNCTION bool isActiveRelativeFrequencyAvailable() const { return !isAtom(); } ~PCM(); }; //! \brief Basic core counter state //! //! Intended only for derivation, but not for the direct use class BasicCounterState { friend class PCM; friend class JSONPrinter; template friend double getExecUsage(const CounterStateType & before, const CounterStateType & after); template friend double getIPC(const CounterStateType & before, const CounterStateType & after); template friend double getAverageFrequency(const CounterStateType & before, const CounterStateType & after); template friend double getAverageFrequencyFromClocks(const int64 clocks, const CounterStateType& before, const CounterStateType& after); template friend double getActiveAverageFrequency(const CounterStateType & before, const CounterStateType & after); template friend double getRelativeFrequency(const CounterStateType & before, const CounterStateType & after); template friend double getActiveRelativeFrequency(const CounterStateType & before, const CounterStateType & after); template friend double getL2CacheHitRatio(const CounterStateType & before, const CounterStateType & after); template friend double getL3CacheHitRatio(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL3CacheMisses(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL2CacheMisses(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL2CacheHits(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL3CacheHitsNoSnoop(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL3CacheHitsSnoop(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL3CacheHits(const CounterStateType & before, const CounterStateType & after); template friend uint64 getL3CacheOccupancy(const CounterStateType & now); template friend uint64 getLocalMemoryBW(const CounterStateType & before, const CounterStateType & after); template friend uint64 getRemoteMemoryBW(const CounterStateType & before, const CounterStateType & after); template friend uint64 getCycles(const CounterStateType & before, const CounterStateType & after); template friend uint64 getInstructionsRetired(const CounterStateType & before, const CounterStateType & after); template friend uint64 getCycles(const CounterStateType & now); template friend uint64 getInstructionsRetired(const CounterStateType & now); template friend uint64 getNumberOfCustomEvents(int32 eventCounterNr, const CounterStateType & before, const CounterStateType & after); template friend uint64 getInvariantTSC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getRefCycles(const CounterStateType & before, const CounterStateType & after); template friend double getCoreCStateResidency(int state, const CounterStateType & before, const CounterStateType & after); template friend uint64 getCoreCStateResidency(int state, const CounterStateType& now); template friend uint64 getSMICount(const CounterStateType & before, const CounterStateType & after); template friend uint64 getAllSlotsRaw(const CounterStateType& before, const CounterStateType& after); template friend uint64 getAllSlots(const CounterStateType & before, const CounterStateType & after); template friend double getBackendBound(const CounterStateType & before, const CounterStateType & after); template friend double getFrontendBound(const CounterStateType & before, const CounterStateType & after); template friend double getBadSpeculation(const CounterStateType & before, const CounterStateType & after); template friend double getRetiring(const CounterStateType & before, const CounterStateType & after); template friend double getFetchLatencyBound(const CounterStateType & before, const CounterStateType & after); template friend double getFetchBandwidthBound(const CounterStateType & before, const CounterStateType & after); template friend double getBranchMispredictionBound(const CounterStateType & before, const CounterStateType & after); template friend double getMachineClearsBound(const CounterStateType & before, const CounterStateType & after); template friend double getMemoryBound(const CounterStateType & before, const CounterStateType & after); template friend double getCoreBound(const CounterStateType & before, const CounterStateType & after); template friend double getHeavyOperationsBound(const CounterStateType & before, const CounterStateType & after); template friend double getLightOperationsBound(const CounterStateType & before, const CounterStateType & after); template friend uint64 getMSREvent(const uint64 & index, const PCM::MSRType & type, const CounterStateType& before, const CounterStateType& after); protected: checked_uint64 InstRetiredAny{}; checked_uint64 CpuClkUnhaltedThread{}; checked_uint64 CpuClkUnhaltedRef{}; checked_uint64 Event[PERF_MAX_CUSTOM_COUNTERS]; enum { L3MissPos = 0, ArchLLCMissPos = 0, L3UnsharedHitPos = 1, ArchLLCRefPos = 1, SKLL3HitPos = 1, L2HitMPos = 2, SKLL2MissPos = 2, HSXL2MissPos = 2, L2HitPos = 3, HSXL2RefPos = 3 }; uint64 InvariantTSC; // invariant time stamp counter uint64 CStateResidency[PCM::MAX_C_STATE + 1]; int32 ThermalHeadroom; uint64 L3Occupancy; uint64 MemoryBWLocal; uint64 MemoryBWTotal; uint64 SMICount; uint64 FrontendBoundSlots, BadSpeculationSlots, BackendBoundSlots, RetiringSlots, AllSlotsRaw; uint64 MemBoundSlots, FetchLatSlots, BrMispredSlots, HeavyOpsSlots; std::unordered_map MSRValues; public: BasicCounterState() : InvariantTSC(0), ThermalHeadroom(PCM_INVALID_THERMAL_HEADROOM), L3Occupancy(0), MemoryBWLocal(0), MemoryBWTotal(0), SMICount(0), FrontendBoundSlots(0), BadSpeculationSlots(0), BackendBoundSlots(0), RetiringSlots(0), AllSlotsRaw(0), MemBoundSlots(0), FetchLatSlots(0), BrMispredSlots(0), HeavyOpsSlots(0) { std::fill(CStateResidency, CStateResidency + PCM::MAX_C_STATE + 1, 0); } virtual ~BasicCounterState() { } BasicCounterState( const BasicCounterState& ) = default; BasicCounterState( BasicCounterState&& ) = default; BasicCounterState & operator = ( BasicCounterState&& ) = default; BasicCounterState & operator += (const BasicCounterState & o) { InstRetiredAny += o.InstRetiredAny; CpuClkUnhaltedThread += o.CpuClkUnhaltedThread; CpuClkUnhaltedRef += o.CpuClkUnhaltedRef; for (int i = 0; i < PERF_MAX_CUSTOM_COUNTERS; ++i) { Event[i] += o.Event[i]; } InvariantTSC += o.InvariantTSC; for (int i = 0; i <= (int)PCM::MAX_C_STATE; ++i) CStateResidency[i] += o.CStateResidency[i]; // ThermalHeadroom is not accumulative L3Occupancy += o.L3Occupancy; MemoryBWLocal += o.MemoryBWLocal; MemoryBWTotal += o.MemoryBWTotal; SMICount += o.SMICount; DBG(4, "before PCM debug aggregate ", FrontendBoundSlots , " " , BadSpeculationSlots , " " , BackendBoundSlots , " " , RetiringSlots ); BasicCounterState old = *this; FrontendBoundSlots += o.FrontendBoundSlots; BadSpeculationSlots += o.BadSpeculationSlots; BackendBoundSlots += o.BackendBoundSlots; RetiringSlots += o.RetiringSlots; AllSlotsRaw += o.AllSlotsRaw; MemBoundSlots += o.MemBoundSlots; FetchLatSlots += o.FetchLatSlots; BrMispredSlots += o.BrMispredSlots; HeavyOpsSlots += o.HeavyOpsSlots; DBG(4, "after PCM debug aggregate ", FrontendBoundSlots , " " , BadSpeculationSlots , " " , BackendBoundSlots , " " ,RetiringSlots); assert(FrontendBoundSlots >= old.FrontendBoundSlots); assert(BadSpeculationSlots >= old.BadSpeculationSlots); assert(BackendBoundSlots >= old.BackendBoundSlots); assert(RetiringSlots >= old.RetiringSlots); assert(MemBoundSlots >= old.MemBoundSlots); assert(FetchLatSlots >= old.FetchLatSlots); assert(BrMispredSlots >= old.BrMispredSlots); assert(HeavyOpsSlots >= old.HeavyOpsSlots); return *this; } void readAndAggregate(std::shared_ptr); void readAndAggregateTSC(std::shared_ptr); //! Returns current thermal headroom below TjMax int32 getThermalHeadroom() const { return ThermalHeadroom; } }; inline uint64 RDTSC() { uint64 result = 0; #ifdef _MSC_VER // Windows #if _MSC_VER>= 1600 result = static_cast(__rdtsc()); #endif #else // Linux uint32 high = 0, low = 0; asm volatile("rdtsc" : "=a" (low), "=d" (high)); result = low + (uint64(high)<<32ULL); #endif return result; } inline uint64 RDTSCP() { uint64 result = 0; #ifdef _MSC_VER // Windows #if _MSC_VER>= 1600 unsigned int Aux; result = __rdtscp(&Aux); #endif #else // Linux and OS X uint32 high = 0, low = 0; asm volatile ( "rdtscp\n\t" "mov %%edx, %0\n\t" "mov %%eax, %1\n\t": "=r" (high), "=r" (low) :: "%rax", "%rcx", "%rdx"); result = low + (uint64(high)<<32ULL); #endif return result; } template int32 getThermalHeadroom(const CounterStateType & /* before */, const CounterStateType & after) { return after.getThermalHeadroom(); } /*! \brief Returns the ratio of QPI cycles in power saving half-lane mode \param port QPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return 0..1 - ratio of QPI cycles in power saving half-lane mode */ template double getNormalizedQPIL0pTxCycles(uint32 port, const CounterStateType & before, const CounterStateType & after) { return double(getQPIL0pTxCycles(port, before, after)) / double(getQPIClocks(port, before, after)); } /*! \brief Returns the ratio of QPI cycles in power saving shutdown mode \param port QPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return 0..1 - ratio of QPI cycles in power saving shutdown mode */ template double getNormalizedQPIL1Cycles(uint32 port, const CounterStateType & before, const CounterStateType & after) { return double(getQPIL1Cycles(port, before, after)) / double(getQPIClocks(port, before, after)); } /*! \brief Returns DRAM clock ticks \param channel DRAM channel number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getDRAMClocks(uint32 channel, const CounterStateType & before, const CounterStateType & after) { const auto clk = after.DRAMClocks[channel] - before.DRAMClocks[channel]; const auto cpu_family_model = PCM::getInstance()->getCPUFamilyModel(); if (cpu_family_model == PCM::ICX || cpu_family_model == PCM::SNOWRIDGE) { return 2 * clk; } return clk; } /*! \brief Returns HBM clock ticks \param channel HBM channel number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getHBMClocks(uint32 channel, const CounterStateType & before, const CounterStateType & after) { return after.HBMClocks[channel] - before.HBMClocks[channel]; } /*! \brief Direct read of memory controller PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param channel channel number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getMCCounter(uint32 channel, uint32 counter, const CounterStateType & before, const CounterStateType & after) { return after.MCCounter[channel][counter] - before.MCCounter[channel][counter]; } /*! \brief Direct read of CXLCM PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param port port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getCXLCMCounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after) { return after.CXLCMCounter[port][counter] - before.CXLCMCounter[port][counter]; } /*! \brief Direct read of CXLDP PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param port port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getCXLDPCounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after) { return after.CXLDPCounter[port][counter] - before.CXLDPCounter[port][counter]; } /*! \brief Direct read of M3UPI PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param port UPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getM3UPICounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after) { return after.M3UPICounter[port][counter] - before.M3UPICounter[port][counter]; } /*! \brief Direct read of uncore PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param pmu_id ID of PMU (unit type: CBO, etc) \param unit uncore unit ID \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getUncoreCounter(const int pmu_id, uint32 unit, uint32 counter, const CounterStateType& before, const CounterStateType& after) { for (size_t die = 0; counter < UncorePMU::maxCounters && die < after.Counters.size(); ++die) { assert(die < before.Counters.size()); const auto afterIter = after.Counters[die].find(pmu_id); const auto beforeIter = before.Counters[die].find(pmu_id); if (afterIter != after.Counters[die].end() && beforeIter != before.Counters[die].end()) { assert(afterIter->second.size() == beforeIter->second.size()); if (unit < afterIter->second.size()) { return afterIter->second[unit][counter] - beforeIter->second[unit][counter]; } unit -= afterIter->second.size(); } } return 0ULL; } /*! \brief Direct read of IIO PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param stack IIO stack number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getIIOCounter(uint32 stack, uint32 counter, const CounterStateType& before, const CounterStateType& after) { return after.IIOCounter[stack][counter] - before.IIOCounter[stack][counter]; } /*! \brief Direct read of IRP PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param stack IIO stack number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getIRPCounter(uint32 stack, uint32 counter, const CounterStateType& before, const CounterStateType& after) { return after.IRPCounter[stack][counter] - before.IRPCounter[stack][counter]; } /*! \brief Direct read of UPI or QPI PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param port UPI/QPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getXPICounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after) { return after.xPICounter[port][counter] - before.xPICounter[port][counter]; } /*! \brief Direct read of Memory2Mesh controller PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param controller controller number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getM2MCounter(uint32 controller, uint32 counter, const CounterStateType & before, const CounterStateType & after) { return after.M2MCounter[controller][counter] - before.M2MCounter[controller][counter]; } /*! \brief Direct read of HA controller PMU counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param controller controller number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getHACounter(uint32 controller, uint32 counter, const CounterStateType & before, const CounterStateType & after) { return after.HACounter[controller][counter] - before.HACounter[controller][counter]; } /*! \brief Direct read of embedded DRAM memory controller counter (counter meaning depends on the programming: power/performance/etc) \param counter counter number \param channel channel number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getEDCCounter(uint32 channel, uint32 counter, const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->HBMmemoryTrafficMetricsAvailable()) return after.EDCCounter[channel][counter] - before.EDCCounter[channel][counter]; return 0ULL; } /*! \brief Returns clock ticks of power control unit \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getPCUClocks(uint32 unit, const CounterStateType & before, const CounterStateType & after) { return getUncoreCounter(PCM::PCU_PMU_ID, unit, 0, before, after); } /*! \brief Returns energy consumed by processor, excluding DRAM (measured in internal units) \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getConsumedEnergy(const CounterStateType & before, const CounterStateType & after) { return after.PackageEnergyStatus - before.PackageEnergyStatus; } /*! \brief Returns energy consumed by processor, excluding DRAM (measured in internal units) \param powerPlane power plane ID \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getConsumedEnergy(const int powerPlane, const CounterStateType& before, const CounterStateType& after) { assert(powerPlane <= PCM::MAX_PP); return after.PPEnergyStatus[powerPlane] - before.PPEnergyStatus[powerPlane]; } /*! \brief Returns energy consumed by system \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getSystemConsumedEnergy(const CounterStateType& before, const CounterStateType& after) { return after.systemEnergyStatus - before.systemEnergyStatus; } /*! \brief Checks is systemEnergyStatusValid is valid in the state * \param s CPU counter state */ template bool systemEnergyStatusValid(const CounterStateType& s) { return s.systemEnergyStatus != 0; } /*! \brief Returns energy consumed by DRAM (measured in internal units) \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getDRAMConsumedEnergy(const CounterStateType & before, const CounterStateType & after) { return after.DRAMEnergyStatus - before.DRAMEnergyStatus; } /*! \brief Returns free running counter if it exists, -1 otherwise * \param counter name of the counter * \param before CPU counter state before the experiment * \param after CPU counter state after the experiment */ template int64 getFreeRunningCounter(const typename CounterStateType::FreeRunningCounterID & counter, const CounterStateType & before, const CounterStateType & after) { const auto beforeIt = before.freeRunningCounter.find(counter); const auto afterIt = after.freeRunningCounter.find(counter); if (beforeIt != before.freeRunningCounter.end() && afterIt != after.freeRunningCounter.end()) { return afterIt->second - beforeIt->second; } return -1; } /*! \brief Returns uncore clock ticks \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getUncoreClocks(const CounterStateType& before, const CounterStateType& after) { return after.UncClocks - before.UncClocks; } /*! \brief Returns Joules consumed by processor (excluding DRAM) \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template double getConsumedJoules(const CounterStateType & before, const CounterStateType & after) { PCM * m = PCM::getInstance(); if (!m) return -1.; return double(getConsumedEnergy(before, after)) * m->getJoulesPerEnergyUnit(); } /*! \brief Returns Joules consumed by processor (excluding DRAM) \param powePlane power plane \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template double getConsumedJoules(const int powerPlane, const CounterStateType& before, const CounterStateType& after) { PCM* m = PCM::getInstance(); if (!m) return -1.; return double(getConsumedEnergy(powerPlane, before, after)) * m->getJoulesPerEnergyUnit(); } /*! \brief Returns Joules consumed by system \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template double getSystemConsumedJoules(const CounterStateType& before, const CounterStateType& after) { PCM* m = PCM::getInstance(); if (!m) return -1.; auto unit = m->getJoulesPerEnergyUnit(); switch (m->getCPUFamilyModel()) { case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::SRF: unit = 1.0; break; } return double(getSystemConsumedEnergy(before, after)) * unit; } /*! \brief Returns Joules consumed by DRAM \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template double getDRAMConsumedJoules(const CounterStateType & before, const CounterStateType & after) { PCM * m = PCM::getInstance(); if (!m) return -1.; double dram_joules_per_energy_unit = 0.; const auto cpu_family_model = m->getCPUFamilyModel(); if (PCM::HASWELLX == cpu_family_model || PCM::BDX_DE == cpu_family_model || PCM::BDX == cpu_family_model || PCM::SKX == cpu_family_model || PCM::ICX == cpu_family_model || PCM::GNR == cpu_family_model || PCM::GNR_D == cpu_family_model || PCM::SRF == cpu_family_model || PCM::GRR == cpu_family_model || PCM::KNL == cpu_family_model ) { /* as described in sections 5.3.2 (DRAM_POWER_INFO) and 5.3.3 (DRAM_ENERGY_STATUS) of * Volume 2 (Registers) of * Intel Xeon E5-1600 v3 and Intel Xeon E5-2600 v3 (Haswell-EP) Datasheet (Ref 330784-001, Sept.2014) * ENERGY_UNIT for DRAM domain is fixed to 15.3 uJ for server HSX, BDW and KNL processors. */ dram_joules_per_energy_unit = 0.0000153; } else { /* for all other processors (including Haswell client/mobile SKUs) the ENERGY_UNIT for DRAM domain * should be read from PACKAGE_POWER_SKU register (usually value around ~61uJ) */ dram_joules_per_energy_unit = m->getJoulesPerEnergyUnit(); } return double(getDRAMConsumedEnergy(before, after)) * dram_joules_per_energy_unit; } //! \brief Basic uncore counter state //! //! Intended only for derivation, but not for the direct use class UncoreCounterState { friend class PCM; friend class JSONPrinter; template friend uint64 getBytesReadFromMC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getBytesWrittenToMC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getNMHits(const CounterStateType & before, const CounterStateType & after); template friend uint64 getNMMisses(const CounterStateType & before, const CounterStateType & after); template friend double getNMHitRate(const CounterStateType & before, const CounterStateType & after); template friend uint64 getNMMissBW(const CounterStateType & before, const CounterStateType & after); template friend uint64 getBytesReadFromPMM(const CounterStateType & before, const CounterStateType & after); template friend uint64 getBytesWrittenToPMM(const CounterStateType & before, const CounterStateType & after); template friend uint64 getBytesReadFromEDC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getBytesWrittenToEDC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getGTRequestBytesFromMC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getIARequestBytesFromMC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getIORequestBytesFromMC(const CounterStateType & before, const CounterStateType & after); template friend uint64 getConsumedEnergy(const CounterStateType & before, const CounterStateType & after); template friend uint64 getConsumedEnergy(const int pp, const CounterStateType& before, const CounterStateType& after); template friend uint64 getDRAMConsumedEnergy(const CounterStateType & before, const CounterStateType & after); template friend uint64 getUncoreClocks(const CounterStateType& before, const CounterStateType& after); template friend double getPackageCStateResidency(int state, const CounterStateType & before, const CounterStateType & after); template friend uint64 getPackageCStateResidency(int state, const CounterStateType& now); template friend double getLLCReadMissLatency(const CounterStateType & before, const CounterStateType & after); template friend double getLocalMemoryRequestRatio(const CounterStateType & before, const CounterStateType & after); template friend double getAverageUncoreFrequency(const CounterStateType& before, const CounterStateType& after); template friend std::vector getUncoreFrequency(const CounterStateType& state); template friend std::vector getUncoreDieTypes(const CounterStateType& state); template friend double getAverageFrequencyFromClocks(const int64 clocks, const CounterStateType& before, const CounterStateType& after); public: enum DieTypeBits { Compute = 1<<23, LLC = 1<<24, Memory = 1<<25, IO = 1<<26 }; static std::string getDieTypeStr(const uint64 d) { std::string type{}; if (d & UncoreCounterState::Compute) { type += "COR"; } if (d & UncoreCounterState::IO) { type += "IO"; } if (d & UncoreCounterState::LLC) { type += "LLC"; } if (d & UncoreCounterState::Memory) { type += "M"; } return type; } protected: std::vector UFSStatus; uint64 UncMCFullWrites; uint64 UncMCNormalReads; uint64 UncHARequests; uint64 UncHALocalRequests; uint64 UncNMMiss; uint64 UncNMHit; uint64 UncPMMWrites; uint64 UncPMMReads; uint64 UncEDCFullWrites; uint64 UncEDCNormalReads; uint64 UncMCGTRequests; uint64 UncMCIARequests; uint64 UncMCIORequests; uint64 PackageEnergyStatus; uint64 PPEnergyStatus[PCM::MAX_PP + 1]; uint64 DRAMEnergyStatus; uint64 TOROccupancyIAMiss; uint64 TORInsertsIAMiss; uint64 UncClocks; uint64 CStateResidency[PCM::MAX_C_STATE + 1]; void readAndAggregate(std::shared_ptr); public: UncoreCounterState() : UFSStatus{{}}, UncMCFullWrites(0), UncMCNormalReads(0), UncHARequests(0), UncHALocalRequests(0), UncNMMiss(0), UncNMHit(0), UncPMMWrites(0), UncPMMReads(0), UncEDCFullWrites(0), UncEDCNormalReads(0), UncMCGTRequests(0), UncMCIARequests(0), UncMCIORequests(0), PackageEnergyStatus(0), DRAMEnergyStatus(0), TOROccupancyIAMiss(0), TORInsertsIAMiss(0), UncClocks(0) { UFSStatus.clear(); std::fill(CStateResidency, CStateResidency + PCM::MAX_C_STATE + 1, 0); std::fill(PPEnergyStatus, PPEnergyStatus + PCM::MAX_PP + 1, 0); } virtual ~UncoreCounterState() { } UncoreCounterState( const UncoreCounterState& ) = default; UncoreCounterState( UncoreCounterState&& ) = default; UncoreCounterState & operator = ( UncoreCounterState&& ) = default; UncoreCounterState & operator += (const UncoreCounterState & o) { UncMCFullWrites += o.UncMCFullWrites; UncMCNormalReads += o.UncMCNormalReads; UncHARequests += o.UncHARequests; UncHALocalRequests += o.UncHALocalRequests; UncPMMReads += o.UncPMMReads; UncPMMWrites += o.UncPMMWrites; UncEDCFullWrites += o.UncEDCFullWrites; UncEDCNormalReads += o.UncEDCNormalReads; UncMCGTRequests += o.UncMCGTRequests; UncMCIARequests += o.UncMCIARequests; UncMCIORequests += o.UncMCIORequests; PackageEnergyStatus += o.PackageEnergyStatus; DRAMEnergyStatus += o.DRAMEnergyStatus; TOROccupancyIAMiss += o.TOROccupancyIAMiss; TORInsertsIAMiss += o.TORInsertsIAMiss; UncClocks += o.UncClocks; for (int i = 0; i <= (int)PCM::MAX_C_STATE; ++i) CStateResidency[i] += o.CStateResidency[i]; return *this; } }; //! \brief Server uncore counter state //! class ServerUncoreCounterState : public UncoreCounterState { public: enum { maxControllers = 32, maxChannels = 32, maxXPILinks = 6, maxIIOStacks = 16, maxCXLPorts = 6, maxCounters = UncorePMU::maxCounters }; enum EventPosition { xPI_TxL0P_POWER_CYCLES = 0, xPI_L1_POWER_CYCLES = 2, xPI_CLOCKTICKS = 3 }; enum FreeRunningCounterID { ImcReads, ImcWrites, PMMReads, PMMWrites }; // typedef std::array CounterArrayType; class CounterArrayType { std::array data; public: CounterArrayType() : data{{}} { std::fill(data.begin(), data.end(), 0ULL); } const uint64& operator [] (size_t i) const { return data[i]; } uint64& operator [] (size_t i) { return data[i]; } }; typedef std::vector PMUCounterArrayType; typedef std::unordered_map PMUMapCounterArrayType; // die -> pmu map -> PMUs -> counters std::vector Counters; std::array, maxXPILinks> xPICounter; std::array, maxXPILinks> M3UPICounter; std::array, maxIIOStacks> IIOCounter; std::array, maxIIOStacks> IRPCounter; std::array, maxCXLPorts> CXLCMCounter; std::array, maxCXLPorts> CXLDPCounter; std::array DRAMClocks; std::array HBMClocks; std::array, maxChannels> MCCounter; // channel X counter std::array, maxControllers> M2MCounter; // M2M/iMC boxes x counter std::array, maxControllers> HACounter; // HA boxes x counter std::array, maxChannels> EDCCounter; // EDC controller X counter std::unordered_map freeRunningCounter; int32 PackageThermalHeadroom; uint64 InvariantTSC; // invariant time stamp counter friend class PCM; template friend uint64 getDRAMClocks(uint32 channel, const CounterStateType & before, const CounterStateType & after); template friend uint64 getHBMClocks(uint32 channel, const CounterStateType & before, const CounterStateType & after); template friend uint64 getMCCounter(uint32 channel, uint32 counter, const CounterStateType & before, const CounterStateType & after); template friend uint64 getCXLCMCounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getCXLDPCounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getM3UPICounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getUncoreCounter(const int pmu_id, uint32 unit, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getIIOCounter(uint32 stack, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getIRPCounter(uint32 stack, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getXPICounter(uint32 port, uint32 counter, const CounterStateType& before, const CounterStateType& after); template friend uint64 getM2MCounter(uint32 controller, uint32 counter, const CounterStateType & before, const CounterStateType & after); template friend uint64 getHACounter(uint32 controller, uint32 counter, const CounterStateType & before, const CounterStateType & after); template friend uint64 getEDCCounter(uint32 channel, uint32 counter, const CounterStateType & before, const CounterStateType & after); template friend uint64 getConsumedEnergy(const CounterStateType & before, const CounterStateType & after); template friend uint64 getDRAMConsumedEnergy(const CounterStateType & before, const CounterStateType & after); template friend uint64 getInvariantTSC(const CounterStateType & before, const CounterStateType & after); template friend int64 getFreeRunningCounter(const typename CounterStateType::FreeRunningCounterID &, const CounterStateType & before, const CounterStateType & after); template friend double getAverageFrequencyFromClocks(const int64 clocks, const CounterStateType& before, const CounterStateType& after); public: //! Returns current thermal headroom below TjMax int32 getPackageThermalHeadroom() const { return PackageThermalHeadroom; } ServerUncoreCounterState() : xPICounter{{}}, M3UPICounter{{}}, IIOCounter{{}}, IRPCounter{{}}, CXLCMCounter{{}}, CXLDPCounter{{}}, DRAMClocks{{}}, HBMClocks{{}}, MCCounter{{}}, M2MCounter{{}}, HACounter{{}}, EDCCounter{{}}, PackageThermalHeadroom(0), InvariantTSC(0) { } }; /*! \brief Returns QPI LL clock ticks \param port QPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getQPIClocks(uint32 port, const CounterStateType& before, const CounterStateType& after) { return getXPICounter(port, ServerUncoreCounterState::EventPosition::xPI_CLOCKTICKS, before, after); } /*! \brief Returns the number of QPI cycles in power saving half-lane mode \param port QPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getQPIL0pTxCycles(uint32 port, const CounterStateType& before, const CounterStateType& after) { return getXPICounter(port, ServerUncoreCounterState::EventPosition::xPI_TxL0P_POWER_CYCLES, before, after); } /*! \brief Returns the number of QPI cycles in power saving shutdown mode \param port QPI port number \param before CPU counter state before the experiment \param after CPU counter state after the experiment */ template uint64 getQPIL1Cycles(uint32 port, const CounterStateType& before, const CounterStateType& after) { return getXPICounter(port, ServerUncoreCounterState::EventPosition::xPI_L1_POWER_CYCLES, before, after); } //! \brief (Logical) core-wide counter state class CoreCounterState : public BasicCounterState { friend class PCM; public: CoreCounterState() = default; CoreCounterState( const CoreCounterState& ) = default; CoreCounterState( CoreCounterState&& ) = default; CoreCounterState & operator= ( CoreCounterState&& ) = default; virtual ~ CoreCounterState() {} }; //! \brief Socket-wide counter state class SocketCounterState : public BasicCounterState, public UncoreCounterState { friend class PCM; protected: void readAndAggregate(std::shared_ptr handle) { BasicCounterState::readAndAggregate(handle); UncoreCounterState::readAndAggregate(handle); } public: SocketCounterState& operator += ( const BasicCounterState& ccs ) { BasicCounterState::operator += ( ccs ); return *this; } SocketCounterState& operator += ( const UncoreCounterState& ucs ) { UncoreCounterState::operator += ( ucs ); return *this; } SocketCounterState() = default; SocketCounterState( const SocketCounterState& ) = default; SocketCounterState( SocketCounterState&& ) = default; SocketCounterState & operator = ( SocketCounterState&& ) = default; SocketCounterState & operator = ( UncoreCounterState&& ucs ) { UncoreCounterState::operator = ( std::move(ucs) ); return *this; } virtual ~ SocketCounterState() {} }; //! \brief System-wide counter state class SystemCounterState : public SocketCounterState { friend class PCM; friend std::vector getTPMIEvent(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after); friend std::vector getPCICFGEvent(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after); friend std::vector getMMIOEvent(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after); friend std::vector getPMTEvent(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after); template friend bool systemEnergyStatusValid(const CounterStateType& s); template friend uint64 getSystemConsumedEnergy(const CounterStateType& before, const CounterStateType& after); std::vector > incomingQPIPackets; // each 64 byte std::vector > outgoingQPIFlits; // idle or data/non-data flits depending on the architecture std::vector > TxL0Cycles; uint64 uncoreTSC; uint64 systemEnergyStatus; std::unordered_map , PCM::TPMIRegisterEncodingHash, PCM::TPMIRegisterEncodingCmp> TPMIValues{}; std::unordered_map , PCM::PCICFGRegisterEncodingHash, PCM::PCICFGRegisterEncodingCmp> PCICFGValues{}; std::unordered_map, PCM::MMIORegisterEncodingHash, PCM::MMIORegisterEncodingCmp> MMIOValues{}; std::unordered_map, PCM::PMTRegisterEncodingHash2> PMTValues{}; protected: void readAndAggregate(std::shared_ptr handle) { BasicCounterState::readAndAggregate(handle); UncoreCounterState::readAndAggregate(handle); } public: typedef uint32_t h_id; typedef uint32_t v_id; typedef std::map,uint64_t> ctr_data; typedef std::vector dev_content; std::vector accel_counters; std::vector CXLWriteMem,CXLWriteCache; friend uint64 getIncomingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after); friend uint64 getIncomingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & now); friend double getOutgoingQPILinkUtilization(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after); friend uint64 getOutgoingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after); friend uint64 getOutgoingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & now); SystemCounterState() : uncoreTSC(0), systemEnergyStatus(0) { PCM * m = PCM::getInstance(); accel_counters.resize(m->getNumberofAccelCounters()); CXLWriteMem.resize(m->getNumSockets(),0); CXLWriteCache.resize(m->getNumSockets(),0); incomingQPIPackets.resize(m->getNumSockets(), std::vector((uint32)m->getQPILinksPerSocket(), 0)); outgoingQPIFlits.resize(m->getNumSockets(), std::vector((uint32)m->getQPILinksPerSocket(), 0)); TxL0Cycles.resize(m->getNumSockets(), std::vector((uint32)m->getQPILinksPerSocket(), 0)); } SystemCounterState( const SystemCounterState& ) = default; SystemCounterState( SystemCounterState&& ) = default; SystemCounterState & operator = ( SystemCounterState&& ) = default; SystemCounterState & operator += ( const SocketCounterState& scs ) { BasicCounterState::operator += ( scs ); UncoreCounterState::operator += ( scs ); return *this; } SystemCounterState & operator += ( const UncoreCounterState& ucs ) { UncoreCounterState::operator += ( ucs ); return *this; } virtual ~ SystemCounterState() {} }; /*! \brief Reads the counter state of the system Helper function. Uses PCM object to access counters. System consists of several sockets (CPUs). Socket has a CPU in it. Socket (CPU) consists of several (logical) cores. \return State of counters in the entire system */ PCM_API SystemCounterState getSystemCounterState(); /*! \brief Reads the counter state of a socket Helper function. Uses PCM object to access counters. \param socket socket id \return State of counters in the socket */ PCM_API SocketCounterState getSocketCounterState(uint32 socket); /*! \brief Reads the counter state of a (logical) core Helper function. Uses PCM object to access counters. \param core core id \return State of counters in the core */ PCM_API CoreCounterState getCoreCounterState(uint32 core); /*! \brief Computes average number of retired instructions per core cycle (IPC) \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return IPC */ template double getIPC(const CounterStateType & before, const CounterStateType & after) // instructions per cycle { int64 clocks = after.CpuClkUnhaltedThread - before.CpuClkUnhaltedThread; if (clocks != 0) return double(after.InstRetiredAny - before.InstRetiredAny) / double(clocks); return -1; } // \brief Returns current uncore frequency vector template std::vector getUncoreFrequency(const CounterStateType& state) { std::vector result; for (auto & e : state.UFSStatus) { result.push_back(extract_bits(e, 0, 6) * 100000000.); } return result; } // \brief Returns uncore die type vector template std::vector getUncoreDieTypes(const CounterStateType& state) { std::vector result; for (auto & e : state.UFSStatus) { result.push_back(extract_bits(e, 23, 26) << 23); } return result; } /*! \brief Computes the number of retired instructions \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return number of retired instructions */ template uint64 getInstructionsRetired(const CounterStateType & before, const CounterStateType & after) // instructions { return after.InstRetiredAny - before.InstRetiredAny; } /*! \brief Computes average number of retired instructions per time interval \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return usage */ template double getExecUsage(const CounterStateType & before, const CounterStateType & after) // usage { int64 timer_clocks = after.InvariantTSC - before.InvariantTSC; if (timer_clocks != 0) return double(after.InstRetiredAny - before.InstRetiredAny) / double(timer_clocks); return -1; } /*! \brief Computes the number of retired instructions \param now Current CPU counter state \return number of retired instructions */ template uint64 getInstructionsRetired(const CounterStateType & now) // instructions { return now.InstRetiredAny.getRawData_NoOverflowProtection(); } /*! \brief Computes the number core clock cycles when signal on a specific core is running (not halted) Returns number of used cycles (halted cyles are not counted). The counter does not advance in the following conditions: - an ACPI C-state is other than C0 for normal operation - HLT - STPCLK+ pin is asserted - being throttled by TM1 - during the frequency switching phase of a performance state transition The performance counter for this event counts across performance state transitions using different core clock frequencies \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return number core clock cycles */ template uint64 getCycles(const CounterStateType & before, const CounterStateType & after) // clocks { return after.CpuClkUnhaltedThread - before.CpuClkUnhaltedThread; } /*! \brief Computes the number of reference clock cycles while clock signal on the core is running The reference clock operates at a fixed frequency, irrespective of core frequency changes due to performance state transitions. See Intel(r) Software Developer's Manual for more details \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return number core clock cycles */ template uint64 getRefCycles(const CounterStateType & before, const CounterStateType & after) // clocks { return after.CpuClkUnhaltedRef - before.CpuClkUnhaltedRef; } /*! \brief Computes the number executed core clock cycles Returns number of used cycles (halted cyles are not counted). \param now Current CPU counter state \return number core clock cycles */ template uint64 getCycles(const CounterStateType & now) // clocks { return now.CpuClkUnhaltedThread.getRawData_NoOverflowProtection(); } /*! \brief Computes average number of retired instructions per core cycle for the entire system combining instruction counts from logical cores to corresponding physical cores Use this metric to evaluate IPC improvement between SMT(Hyperthreading) on and SMT off. \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return IPC */ template inline double getCoreIPC(const CounterStateType & before, const CounterStateType & after) // instructions per cycle { double ipc = getIPC(before, after); PCM * m = PCM::getInstance(); if (ipc >= 0. && m && (m->getNumCores() == m->getNumOnlineCores())) return ipc * double(m->getThreadsPerCore()); return -1; } /*! \brief Computes average number of retired instructions per time interval for the entire system combining instruction counts from logical cores to corresponding physical cores Use this metric to evaluate cores utilization improvement between SMT(Hyperthreading) on and SMT off. \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return usage */ template inline double getTotalExecUsage(const CounterStateType & before, const CounterStateType & after) // usage { double usage = getExecUsage(before, after); PCM * m = PCM::getInstance(); if (usage >= 0. && m && (m->getNumCores() == m->getNumOnlineCores())) return usage * double(m->getThreadsPerCore()); return -1; } template double getAverageFrequencyFromClocks(const int64 clocks, const StateType& before, const StateType& after) // in Hz { const int64 timer_clocks = after.InvariantTSC - before.InvariantTSC; PCM* m = PCM::getInstance(); if (timer_clocks != 0 && m) return double(m->getNominalFrequency()) * double(clocks) / double(timer_clocks); return -1; } /*! \brief Computes average core frequency also taking Intel Turbo Boost technology into account \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return frequency in Hz */ template double getAverageFrequency(const CounterStateType & before, const CounterStateType & after) // in Hz { return getAverageFrequencyFromClocks(after.CpuClkUnhaltedThread - before.CpuClkUnhaltedThread, before, after); } /*! \brief Computes average uncore frequency \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return frequency in Hz */ template double getAverageUncoreFrequency(const UncoreStateType& before, const UncoreStateType & after) // in Hz { auto m = PCM::getInstance(); assert(m); return double(m->getNumOnlineCores()) * getAverageFrequencyFromClocks(after.UncClocks - before.UncClocks, before, after) / double(m->getNumOnlineSockets()); } /*! \brief Computes uncore frequency for all dies \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return frequency in Hz */ template std::vector getUncoreFrequencies(const UncoreStateType& before, const UncoreStateType & after) // in Hz { auto m = PCM::getInstance(); assert(m); std::vector uncoreFrequencies{getUncoreFrequency(after)}; if (m->uncoreFrequencyMetricAvailable()) { uncoreFrequencies.push_back(getAverageUncoreFrequency(before, after)); } return uncoreFrequencies; } /*! \brief Computes average core frequency when not in powersaving C0-state (also taking Intel Turbo Boost technology into account) \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return frequency in Hz */ template double getActiveAverageFrequency(const CounterStateType & before, const CounterStateType & after) // in Hz { int64 clocks = after.CpuClkUnhaltedThread - before.CpuClkUnhaltedThread; int64 ref_clocks = after.CpuClkUnhaltedRef - before.CpuClkUnhaltedRef; PCM * m = PCM::getInstance(); if (ref_clocks != 0 && m) return double(m->getNominalFrequency()) * double(clocks) / double(ref_clocks); return -1; } /*! \brief Computes average core frequency also taking Intel Turbo Boost technology into account \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Fraction of nominal frequency */ template double getRelativeFrequency(const CounterStateType & before, const CounterStateType & after) // fraction of nominal frequency { int64 clocks = after.CpuClkUnhaltedThread - before.CpuClkUnhaltedThread; int64 timer_clocks = after.InvariantTSC - before.InvariantTSC; if (timer_clocks != 0) return double(clocks) / double(timer_clocks); return -1; } /*! \brief Computes average core frequency when not in powersaving C0-state (also taking Intel Turbo Boost technology into account) \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Fraction of nominal frequency (if >1.0 then Turbo was working during the measurement) */ template double getActiveRelativeFrequency(const CounterStateType & before, const CounterStateType & after) // fraction of nominal frequency { if (!PCM::getInstance()->isActiveRelativeFrequencyAvailable()) return -1.; int64 clocks = after.CpuClkUnhaltedThread - before.CpuClkUnhaltedThread; int64 ref_clocks = after.CpuClkUnhaltedRef - before.CpuClkUnhaltedRef; if (ref_clocks != 0) return double(clocks) / double(ref_clocks); return -1; } /*! \brief Computes L2 cache hit ratio \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return value between 0 and 1 */ template double getL2CacheHitRatio(const CounterStateType& before, const CounterStateType& after) // 0.0 - 1.0 { auto* pcm = PCM::getInstance(); if (!pcm->isL2CacheHitRatioAvailable()) return 0; const auto hits = getL2CacheHits(before, after); if (pcm->memoryEventErrata()) { const auto all = after.Event[BasicCounterState::HSXL2RefPos] - before.Event[BasicCounterState::HSXL2RefPos]; if (all == 0ULL) return 0.; return double(hits) / double(all); } const auto misses = getL2CacheMisses(before, after); const auto all = double(hits + misses); if (all == 0.0) return 0.; return double(hits) / all; } /*! \brief Computes L3 cache hit ratio \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return value between 0 and 1 */ template double getL3CacheHitRatio(const CounterStateType& before, const CounterStateType& after) // 0.0 - 1.0 { if (!PCM::getInstance()->isL3CacheHitRatioAvailable()) return 0; const auto hits = getL3CacheHits(before, after); const auto misses = getL3CacheMisses(before, after); const auto all = double(hits + misses); if (all == 0.0) return 0.; return double(hits) / all; } /*! \brief Computes number of L3 cache misses \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return number of misses */ template uint64 getL3CacheMisses(const CounterStateType & before, const CounterStateType & after) { if (!PCM::getInstance()->isL3CacheMissesAvailable()) return 0; return after.Event[BasicCounterState::L3MissPos] - before.Event[BasicCounterState::L3MissPos]; } /*! \brief Computes number of L2 cache misses \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return number of misses */ template uint64 getL2CacheMisses(const CounterStateType & before, const CounterStateType & after) { auto pcm = PCM::getInstance(); if (pcm->isL2CacheMissesAvailable() == false) return 0ULL; const auto cpu_family_model = pcm->getCPUFamilyModel(); if (pcm->useSkylakeEvents() || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::ELKHART_LAKE || cpu_family_model == PCM::JASPER_LAKE || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GRR || cpu_family_model == PCM::ADL || cpu_family_model == PCM::RPL || cpu_family_model == PCM::MTL || cpu_family_model == PCM::LNL || cpu_family_model == PCM::ARL ) { return after.Event[BasicCounterState::SKLL2MissPos] - before.Event[BasicCounterState::SKLL2MissPos]; } else if (pcm->isAtom() || cpu_family_model == PCM::KNL) { return after.Event[BasicCounterState::ArchLLCMissPos] - before.Event[BasicCounterState::ArchLLCMissPos]; } else if (pcm->memoryEventErrata()) { return after.Event[BasicCounterState::ArchLLCRefPos] - before.Event[BasicCounterState::ArchLLCRefPos]; } uint64 L3Miss = after.Event[BasicCounterState::L3MissPos] - before.Event[BasicCounterState::L3MissPos]; uint64 L3UnsharedHit = after.Event[BasicCounterState::L3UnsharedHitPos] - before.Event[BasicCounterState::L3UnsharedHitPos]; uint64 L2HitM = after.Event[BasicCounterState::L2HitMPos] - before.Event[BasicCounterState::L2HitMPos]; return L2HitM + L3UnsharedHit + L3Miss; } /*! \brief Computes number of L2 cache hits \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return number of hits */ template uint64 getL2CacheHits(const CounterStateType & before, const CounterStateType & after) { auto pcm = PCM::getInstance(); if (pcm->isL2CacheHitsAvailable() == false) return 0ULL; if (pcm->isAtom() || pcm->getCPUFamilyModel() == PCM::KNL) { uint64 L2Miss = after.Event[BasicCounterState::ArchLLCMissPos] - before.Event[BasicCounterState::ArchLLCMissPos]; uint64 L2Ref = after.Event[BasicCounterState::ArchLLCRefPos] - before.Event[BasicCounterState::ArchLLCRefPos]; return L2Ref - L2Miss; } else if (pcm->memoryEventErrata()) { const auto all = after.Event[BasicCounterState::HSXL2RefPos] - before.Event[BasicCounterState::HSXL2RefPos]; const auto misses = after.Event[BasicCounterState::HSXL2MissPos] - before.Event[BasicCounterState::HSXL2MissPos]; const auto hits = (all > misses) ? (all - misses) : 0ULL; return hits; } return after.Event[BasicCounterState::L2HitPos] - before.Event[BasicCounterState::L2HitPos]; } /*! \brief Computes L3 Cache Occupancy */ template uint64 getL3CacheOccupancy(const CounterStateType & now) { if (PCM::getInstance()->L3CacheOccupancyMetricAvailable() == false) return 0ULL; return now.L3Occupancy; } /*! \brief Computes Local Memory Bandwidth */ template uint64 getLocalMemoryBW(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->CoreLocalMemoryBWMetricAvailable() == false) return 0ULL; return after.MemoryBWLocal - before.MemoryBWLocal; } /*! \brief Computes Remote Memory Bandwidth */ template uint64 getRemoteMemoryBW(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->CoreRemoteMemoryBWMetricAvailable() == false) return 0ULL; const uint64 total = after.MemoryBWTotal - before.MemoryBWTotal; const uint64 local = getLocalMemoryBW(before, after); if (total > local) return total - local; return 0; } /*! \brief Computes number of L3 cache hits where no snooping in sibling L2 caches had to be done \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return number of hits */ template uint64 getL3CacheHitsNoSnoop(const CounterStateType & before, const CounterStateType & after) { if (!PCM::getInstance()->isL3CacheHitsNoSnoopAvailable()) return 0; return after.Event[BasicCounterState::L3UnsharedHitPos] - before.Event[BasicCounterState::L3UnsharedHitPos]; } /*! \brief Computes number of L3 cache hits where snooping in sibling L2 caches had to be done \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return number of hits */ template uint64 getL3CacheHitsSnoop(const CounterStateType & before, const CounterStateType & after) { auto pcm = PCM::getInstance(); if (!pcm->isL3CacheHitsSnoopAvailable()) return 0; const auto cpu_family_model = pcm->getCPUFamilyModel(); if (cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::GRR || cpu_family_model == PCM::ELKHART_LAKE || cpu_family_model == PCM::JASPER_LAKE || cpu_family_model == PCM::SRF || cpu_family_model == PCM::ADL || cpu_family_model == PCM::RPL || cpu_family_model == PCM::MTL || cpu_family_model == PCM::LNL || cpu_family_model == PCM::ARL ) { const int64 misses = getL3CacheMisses(before, after); const int64 refs = after.Event[BasicCounterState::ArchLLCRefPos] - before.Event[BasicCounterState::ArchLLCRefPos]; const int64 hits = refs - misses; return (hits > 0)? hits : 0; } if (pcm->useSkylakeEvents()) { return after.Event[BasicCounterState::SKLL3HitPos] - before.Event[BasicCounterState::SKLL3HitPos]; } return after.Event[BasicCounterState::L2HitMPos] - before.Event[BasicCounterState::L2HitMPos]; } /*! \brief Computes total number of L3 cache hits \param before CPU counter state before the experiment \param after CPU counter state after the experiment \warning Works only in the DEFAULT_EVENTS programming mode (see program() method) \return number of hits */ template uint64 getL3CacheHits(const CounterStateType & before, const CounterStateType & after) { auto * pcm = PCM::getInstance(); assert(pcm); if (!pcm->isL3CacheHitsAvailable()) return 0; else if (pcm->memoryEventErrata()) { uint64 LLCMiss = after.Event[BasicCounterState::ArchLLCMissPos] - before.Event[BasicCounterState::ArchLLCMissPos]; uint64 LLCRef = after.Event[BasicCounterState::ArchLLCRefPos] - before.Event[BasicCounterState::ArchLLCRefPos]; return (LLCRef > LLCMiss) ? (LLCRef - LLCMiss) : 0ULL; } return getL3CacheHitsSnoop(before, after) + getL3CacheHitsNoSnoop(before, after); } /*! \brief Computes number of invariant time stamp counter ticks This counter counts irrespectively of C-, P- or T-states \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return number of time stamp counter ticks */ template uint64 getInvariantTSC(const CounterStateType & before, const CounterStateType & after) { return after.InvariantTSC - before.InvariantTSC; } /*! \brief Computes residency in the core C-state \param state C-state \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return residence ratio (0..1): 0 - 0%, 1.0 - 100% */ template inline double getCoreCStateResidency(int state, const CounterStateType & before, const CounterStateType & after) { const double tsc = double(getInvariantTSC(before, after)); if (state == 0) return double(getRefCycles(before, after)) / tsc; if (state == 1) { PCM * m = PCM::getInstance(); double result = 1.0 - double(getRefCycles(before, after)) / tsc; // 1.0 - cC0 for (int i = 2; i <= PCM::MAX_C_STATE; ++i) if (m->isCoreCStateResidencySupported(state)) result -= (after.BasicCounterState::CStateResidency[i] - before.BasicCounterState::CStateResidency[i]) / tsc; if (result < 0.) result = 0.; // fix counter dissynchronization else if (result > 1.) result = 1.; // fix counter dissynchronization return result; } return (after.BasicCounterState::CStateResidency[state] - before.BasicCounterState::CStateResidency[state]) / tsc; } /*! \brief Reads raw residency counter for the core C-state \param state C-state # \param now CPU counter state \return raw residency value */ template inline uint64 getCoreCStateResidency(int state, const CounterStateType& now) { if (state == 0) return now.CpuClkUnhaltedRef.getRawData_NoOverflowProtection(); return now.BasicCounterState::CStateResidency[state]; } /*! \brief Computes residency in the package C-state \param state C-state \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return residence ratio (0..1): 0 - 0%, 1.0 - 100% */ template inline double getPackageCStateResidency(int state, const CounterStateType & before, const CounterStateType & after) { const double tsc = double(getInvariantTSC(before, after)); if (state == 0) { PCM * m = PCM::getInstance(); double result = 1.0; for (int i = 1; i <= PCM::MAX_C_STATE; ++i) if (m->isPackageCStateResidencySupported(state)) result -= (after.UncoreCounterState::CStateResidency[i] - before.UncoreCounterState::CStateResidency[i]) / tsc; if (result < 0.) result = 0.; // fix counter dissynchronization else if (result > 1.) result = 1.; // fix counter dissynchronization return result; } return double(after.UncoreCounterState::CStateResidency[state] - before.UncoreCounterState::CStateResidency[state]) / tsc; } /*! \brief Reads raw residency counter for the package C-state \param state C-state # \param now CPU counter state \return raw residency value */ template inline uint64 getPackageCStateResidency(int state, const CounterStateType& now) { return now.UncoreCounterState::CStateResidency[state]; } /*! \brief Computes number of bytes read from DRAM memory controllers \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getBytesReadFromMC(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->memoryTrafficMetricsAvailable()) return (after.UncMCNormalReads - before.UncMCNormalReads) * 64; return 0ULL; } /*! \brief Computes number of bytes written to DRAM memory controllers \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getBytesWrittenToMC(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->memoryTrafficMetricsAvailable()) return (after.UncMCFullWrites - before.UncMCFullWrites) * 64; return 0ULL; } /*! \brief Computes number of Near Memory Hits \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getNMHits(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->nearMemoryMetricsAvailable()) return (after.UncNMHit - before.UncNMHit); return 0ULL; } /*! \brief Computes number of Near Memory Misses \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of NMMisses */ template uint64 getNMMisses(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->nearMemoryMetricsAvailable()) return (after.UncNMMiss - before.UncNMMiss); return 0ULL; } /*! \brief Computes Near Memory Misses Bandwidth \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getNMMissBW(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->nearMemoryMetricsAvailable()) return (after.UncNMMiss - before.UncNMMiss)*64*2; return 0ULL; } /*! \brief Computes Near Memory Hit/Miss rate as a percentage \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template double getNMHitRate(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->nearMemoryMetricsAvailable()) { auto hit = (after.UncNMHit - before.UncNMHit); auto miss = (after.UncNMMiss - before.UncNMMiss); if((hit+miss) != 0 ) return (hit*100.0/(hit+miss));} return 0ULL; } /*! \brief Computes number of bytes read from PMM memory \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getBytesReadFromPMM(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->PMMTrafficMetricsAvailable()) return (after.UncPMMReads - before.UncPMMReads) * 64; return 0ULL; } /*! \brief Computes number of bytes written to PMM memory \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getBytesWrittenToPMM(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->PMMTrafficMetricsAvailable()) return (after.UncPMMWrites - before.UncPMMWrites) * 64; return 0ULL; } /*! \brief Computes number of bytes read from HBM memory controllers \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getBytesReadFromEDC(const CounterStateType & before, const CounterStateType & after) { auto m = PCM::getInstance(); assert(m); if (m->HBMmemoryTrafficMetricsAvailable()) return (after.UncEDCNormalReads - before.UncEDCNormalReads) * m->getHBMCASTransferSize(); return 0ULL; } /*! \brief Computes number of bytes written to HBM memory controllers \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getBytesWrittenToEDC(const CounterStateType & before, const CounterStateType & after) { auto m = PCM::getInstance(); assert(m); if (m->HBMmemoryTrafficMetricsAvailable()) return (after.UncEDCFullWrites - before.UncEDCFullWrites) * m->getHBMCASTransferSize(); return 0ULL; } /*! \brief Computes number of bytes of read/write requests from GT engine \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getGTRequestBytesFromMC(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->memoryIOTrafficMetricAvailable()) return (after.UncMCGTRequests - before.UncMCGTRequests) * 64; return 0ULL; } /*! \brief Computes number of bytes of read/write requests from all IA \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getIARequestBytesFromMC(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->memoryIOTrafficMetricAvailable()) return (after.UncMCIARequests - before.UncMCIARequests) * 64; return 0ULL; } /*! \brief Computes number of bytes of read/write requests from all IO sources \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getIORequestBytesFromMC(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->memoryIOTrafficMetricAvailable()) return (after.UncMCIORequests - before.UncMCIORequests) * 64; return 0ULL; } /*! \brief Returns the number of occurred system management interrupts \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of SMIs (system manegement interrupts) */ template uint64 getSMICount(const CounterStateType & before, const CounterStateType & after) { return after.SMICount - before.SMICount; } /*! \brief Returns the number of occurred custom core events Read number of events programmed with the \c CUSTOM_CORE_EVENTS \param eventCounterNr Event/counter number (value from 0 to 3) \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ template uint64 getNumberOfCustomEvents(int32 eventCounterNr, const CounterStateType & before, const CounterStateType & after) { return after.Event[eventCounterNr] - before.Event[eventCounterNr]; } /*! \brief Computes number of bytes Writen from CXL Cache \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ //template inline uint64 getCXLWriteCacheBytes(uint32 socket,const SystemCounterState & before,const SystemCounterState & after) { return (after.CXLWriteCache[socket] - before.CXLWriteCache[socket]) * 64; } /*! \brief Computes number of bytes Writen from CXL Memory \param before CPU counter state before the experiment \param after CPU counter state after the experiment \return Number of bytes */ //template inline uint64 getCXLWriteMemBytes(uint32 socket, const SystemCounterState & before,const SystemCounterState & after) { return (after.CXLWriteMem[socket] - before.CXLWriteMem[socket]) * 64; } /*! \brief Get estimation of QPI data traffic per incoming QPI link Returns an estimation of number of data bytes transferred to a socket over Intel(r) Quick Path Interconnect \param socketNr socket identifier \param linkNr linkNr \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Number of bytes */ inline uint64 getIncomingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after) { if (!PCM::getInstance()->incomingQPITrafficMetricsAvailable()) return 0ULL; uint64 b = before.incomingQPIPackets[socketNr][linkNr]; uint64 a = after.incomingQPIPackets[socketNr][linkNr]; // prevent overflows due to counter dissynchronisation return (a > b) ? (64 * (a - b)) : 0; } /*! \brief Get data utilization of incoming QPI link (0..1) Returns an estimation of utilization of QPI link by data traffic transferred to a socket over Intel(r) Quick Path Interconnect \param socketNr socket identifier \param linkNr linkNr \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return utilization (0..1) */ inline double getIncomingQPILinkUtilization(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after) { PCM * m = PCM::getInstance(); if (!(m->qpiUtilizationMetricsAvailable())) return 0.; const double bytes = (double)getIncomingQPILinkBytes(socketNr, linkNr, before, after); const uint64 max_speed = m->getQPILinkSpeed(socketNr, linkNr); const double max_bytes = (double)(double(max_speed) * double(getInvariantTSC(before, after) / double(m->getNumOnlineCores())) / double(m->getNominalFrequency())); return bytes / max_bytes; } /*! \brief Get utilization of outgoing QPI link (0..1) Returns an estimation of utilization of QPI link by (data+nondata) traffic transferred from a socket over Intel(r) Quick Path Interconnect \param socketNr socket identifier \param linkNr linkNr \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return utilization (0..1) */ inline double getOutgoingQPILinkUtilization(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after) { PCM * m = PCM::getInstance(); if (m->outgoingQPITrafficMetricsAvailable() == false) return 0.; if (m->hasBecktonUncore()) { const uint64 b = before.outgoingQPIFlits[socketNr][linkNr]; // idle flits const uint64 a = after.outgoingQPIFlits[socketNr][linkNr]; // idle flits // prevent overflows due to counter dissynchronisation const double idle_flits = (double)((a > b) ? (a - b) : 0); const uint64 bTSC = before.uncoreTSC; const uint64 aTSC = after.uncoreTSC; const double tsc = (double)((aTSC > bTSC) ? (aTSC - bTSC) : 0); if (idle_flits >= tsc) return 0.; // prevent overflows due to potential counter dissynchronization return (1. - (idle_flits / tsc)); } else if (m->hasPCICFGUncore()) { const uint64 b = before.outgoingQPIFlits[socketNr][linkNr]; // data + non-data flits or idle (null) flits const uint64 a = after.outgoingQPIFlits[socketNr][linkNr]; // data + non-data flits or idle (null) flits // prevent overflows due to counter dissynchronisation double flits = (double)((a > b) ? (a - b) : 0); const double max_flits = ((double(getInvariantTSC(before, after)) * double(m->getQPILinkSpeed(socketNr, linkNr)) / m->getBytesPerFlit()) / double(m->getNominalFrequency())) / double(m->getNumOnlineCores()); if(m->hasUPI()) { flits = flits/3.; } if (flits > max_flits) return 1.; // prevent overflows due to potential counter dissynchronization return (flits / max_flits); } return 0; } /*! \brief Get estimation of QPI (data+nondata) traffic per outgoing QPI link Returns an estimation of number of data bytes transferred from a socket over Intel(r) Quick Path Interconnect \param socketNr socket identifier \param linkNr linkNr \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Number of bytes */ inline uint64 getOutgoingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & before, const SystemCounterState & after) { PCM * m = PCM::getInstance(); if (!(m->outgoingQPITrafficMetricsAvailable())) return 0ULL; const double util = getOutgoingQPILinkUtilization(socketNr, linkNr, before, after); const double max_bytes = (double(m->getQPILinkSpeed(socketNr, linkNr)) * double(getInvariantTSC(before, after) / double(m->getNumOnlineCores())) / double(m->getNominalFrequency())); return (uint64)(max_bytes * util); } /*! \brief Get estimation of total QPI data traffic Returns an estimation of number of data bytes transferred to all sockets over all Intel(r) Quick Path Interconnect links \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Number of bytes */ inline uint64 getAllIncomingQPILinkBytes(const SystemCounterState & before, const SystemCounterState & after) { PCM * m = PCM::getInstance(); const uint32 ns = m->getNumSockets(); const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); uint64 sum = 0; for (uint32 s = 0; s < ns; ++s) for (uint32 q = 0; q < qpiLinks; ++q) sum += getIncomingQPILinkBytes(s, q, before, after); return sum; } /*! \brief Get estimation of total QPI data+nondata traffic Returns an estimation of number of data and non-data bytes transferred from all sockets over all Intel(r) Quick Path Interconnect links \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Number of bytes */ inline uint64 getAllOutgoingQPILinkBytes(const SystemCounterState & before, const SystemCounterState & after) { PCM * m = PCM::getInstance(); const uint32 ns = m->getNumSockets(); const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); uint64 sum = 0; for (uint32 s = 0; s < ns; ++s) for (uint32 q = 0; q < qpiLinks; ++q) sum += getOutgoingQPILinkBytes(s, q, before, after); return sum; } /*! \brief Return current value of the counter of QPI data traffic per incoming QPI link Returns the number of incoming data bytes to a socket over Intel(r) Quick Path Interconnect \param socketNr socket identifier \param linkNr linkNr \param now Current System CPU counter state \return Number of bytes */ inline uint64 getIncomingQPILinkBytes(uint32 socketNr, uint32 linkNr, const SystemCounterState & now) { if (PCM::getInstance()->incomingQPITrafficMetricsAvailable()) return 64 * now.incomingQPIPackets[socketNr][linkNr]; return 0ULL; } /*! \brief Get estimation of total QPI data traffic for this socket Returns an estimation of number of bytes transferred to this sockets over all Intel(r) Quick Path Interconnect links on this socket \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Number of bytes */ inline uint64 getSocketIncomingQPILinkBytes(uint32 socketNr, const SystemCounterState & now) { PCM * m = PCM::getInstance(); const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); uint64 sum = 0; for (uint32 q = 0; q < qpiLinks; ++q) sum += getIncomingQPILinkBytes(socketNr, q, now); return sum; } /*! \brief Get estimation of Socket QPI data traffic Returns an estimation of number of data bytes transferred to all sockets over all Intel(r) Quick Path Interconnect links \param now System CPU counter state \return Number of bytes */ inline uint64 getAllIncomingQPILinkBytes(const SystemCounterState & now) { PCM * m = PCM::getInstance(); const uint32 ns = m->getNumSockets(); uint64 sum = 0; for (uint32 s = 0; s < ns; ++s) sum += getSocketIncomingQPILinkBytes(s, now); return sum; } /*! \brief Get QPI data to Memory Controller traffic ratio Ideally for NUMA-optmized programs the ratio should be close to 0. \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Ratio */ inline double getQPItoMCTrafficRatio(const SystemCounterState & before, const SystemCounterState & after) { const uint64 totalQPI = getAllIncomingQPILinkBytes(before, after); uint64 memTraffic = getBytesReadFromMC(before, after) + getBytesWrittenToMC(before, after); if (PCM::getInstance()->PMMTrafficMetricsAvailable()) { memTraffic += getBytesReadFromPMM(before, after) + getBytesWrittenToPMM(before, after); } if (memTraffic == 0) return -1.; return double(totalQPI) / double(memTraffic); } /*! \brief Get local memory access ration measured in home agent \param before System CPU counter state before the experiment \param after System CPU counter state after the experiment \return Ratio */ template inline double getLocalMemoryRequestRatio(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->localMemoryRequestRatioMetricAvailable() == false) return -1.; const auto all = after.UncHARequests - before.UncHARequests; const auto local = after.UncHALocalRequests - before.UncHALocalRequests; DBG(4, 64*all/1e6 , " " , 64*local/1e6); return double(local)/double(all); } //! \brief Returns the raw count of events //! \param before counter state before the experiment //! \param after counter state after the experiment template inline uint64 getNumberOfEvents(const CounterType & before, const CounterType & after) { // prevent overflows due to counter dissynchronisation if (after.data < before.data) { return 0; } return after.data - before.data; } //! \brief Returns average last level cache read+prefetch miss latency in ns template inline double getLLCReadMissLatency(const CounterStateType & before, const CounterStateType & after) { auto * m = PCM::getInstance(); if (m->LLCReadMissLatencyMetricsAvailable() == false) return -1.; const double occupancy = double(after.TOROccupancyIAMiss) - double(before.TOROccupancyIAMiss); const double inserts = double(after.TORInsertsIAMiss) - double(before.TORInsertsIAMiss); const double unc_clocks = double(after.UncClocks) - double(before.UncClocks); const double seconds = double(getInvariantTSC(before, after)) / double(m->getNumOnlineCores()/m->getNumSockets()) / double(m->getNominalFrequency()); return 1e9*seconds*(occupancy/inserts)/unc_clocks; } template inline uint64 getAllSlots(const CounterStateType & before, const CounterStateType & after) { const int64 a = after.BackendBoundSlots - before.BackendBoundSlots; const int64 b = after.FrontendBoundSlots - before.FrontendBoundSlots; const int64 c = after.BadSpeculationSlots - before.BadSpeculationSlots; const int64 d = after.RetiringSlots - before.RetiringSlots; DBG(4, "before: " , before.FrontendBoundSlots , " " , before.BadSpeculationSlots , " ", before.BackendBoundSlots , " " , before.RetiringSlots); DBG(4, "afterG: " , after.FrontendBoundSlots , " " , after.BadSpeculationSlots , " " , after.BackendBoundSlots , " " , after.RetiringSlots); assert(a >= 0); assert(b >= 0); assert(c >= 0); assert(d >= 0); return a + b + c + d; } template inline uint64 getAllSlotsRaw(const CounterStateType& before, const CounterStateType& after) { return after.AllSlotsRaw - before.AllSlotsRaw; } //! \brief Returns unutilized pipeline slots where no uop was delivered due to lack of back-end resources as range 0..1 template inline double getBackendBound(const CounterStateType & before, const CounterStateType & after) { DBG(4, (after.BackendBoundSlots - before.BackendBoundSlots) , " " , getAllSlots(before, after)); if (PCM::getInstance()->isHWTMAL1Supported()) return double(after.BackendBoundSlots - before.BackendBoundSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns unutilized pipeline slots where no uop was delivered due to stalls on buffer, cache or memory resources as range 0..1 template inline double getMemoryBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return double(after.MemBoundSlots - before.MemBoundSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns unutilized pipeline slots where no uop was delivered due to lack of core resources as range 0..1 template inline double getCoreBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return getBackendBound(before, after) - getMemoryBound(before, after); return 0.; } //! \brief Returns unutilized pipeline slots where Front-end did not deliver a uop while back-end is ready as range 0..1 template inline double getFrontendBound(const CounterStateType & before, const CounterStateType & after) { DBG(4, (after.FrontendBoundSlots - before.FrontendBoundSlots) , " " , getAllSlots(before, after)); if (PCM::getInstance()->isHWTMAL1Supported()) return double(after.FrontendBoundSlots - before.FrontendBoundSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns unutilized pipeline slots where Front-end due to fetch latency constraints did not deliver a uop while back-end is ready as range 0..1 template inline double getFetchLatencyBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return double(after.FetchLatSlots - before.FetchLatSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns unutilized pipeline slots where Front-end due to fetch bandwidth constraints did not deliver a uop while back-end is ready as range 0..1 template inline double getFetchBandwidthBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return getFrontendBound(before, after) - getFetchLatencyBound(before, after); return 0.; } //! \brief Returns wasted pipeline slots due to incorrect speculation, covering whole penalty: Utilized by uops that do not retire, or Recovery Bubbles (unutilized slots) as range 0..1 template inline double getBadSpeculation(const CounterStateType & before, const CounterStateType & after) { DBG(4, (after.BadSpeculationSlots - before.BadSpeculationSlots) , " " , getAllSlots(before, after)); if (PCM::getInstance()->isHWTMAL1Supported()) return double(after.BadSpeculationSlots - before.BadSpeculationSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns wasted pipeline slots due to incorrect speculation (branch misprediction), covering whole penalty: Utilized by uops that do not retire, or Recovery Bubbles (unutilized slots) as range 0..1 template inline double getBranchMispredictionBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return double(after.BrMispredSlots - before.BrMispredSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns wasted pipeline slots due to incorrect speculation (machine clears), covering whole penalty: Utilized by uops that do not retire, or Recovery Bubbles (unutilized slots) as range 0..1 template inline double getMachineClearsBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return getBadSpeculation(before, after) - getBranchMispredictionBound(before, after); return 0.; } //! \brief Returns pipeline slots utilized by uops that eventually retire (commit) template inline double getRetiring(const CounterStateType & before, const CounterStateType & after) { DBG(4, (after.RetiringSlots - before.RetiringSlots) , " " , getAllSlots(before, after)); if (PCM::getInstance()->isHWTMAL1Supported()) return double(after.RetiringSlots - before.RetiringSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns pipeline slots utilized by uops that eventually retire (commit) - heavy operations template inline double getHeavyOperationsBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return double(after.HeavyOpsSlots - before.HeavyOpsSlots)/double(getAllSlots(before, after)); return 0.; } //! \brief Returns pipeline slots utilized by uops that eventually retire (commit) - light operations template inline double getLightOperationsBound(const CounterStateType & before, const CounterStateType & after) { if (PCM::getInstance()->isHWTMAL2Supported()) return getRetiring(before, after) - getHeavyOperationsBound(before, after); return 0.; } template inline std::vector getRegisterEvent(const PCM::RawEventEncoding& eventEnc, const ValuesType& beforeValues, const ValuesType& afterValues) { std::vector result{}; auto beforeIter = beforeValues.find(eventEnc); auto afterIter = afterValues.find(eventEnc); if (beforeIter != beforeValues.end() && afterIter != afterValues.end()) { const auto& beforeValues = beforeIter->second; const auto& afterValues = afterIter->second; assert(beforeValues.size() == afterValues.size()); const size_t sz = beforeValues.size(); for (size_t i = 0; i < sz; ++i) { switch (eventEnc[PCM::PCICFGEventPosition::type]) { case PCM::MSRType::Freerun: result.push_back(afterValues[i] - beforeValues[i]); break; case PCM::MSRType::Static: result.push_back(afterValues[i]); break; } } } return result; } inline std::vector getTPMIEvent(const PCM::RawEventEncoding & eventEnc, const SystemCounterState& before, const SystemCounterState& after) { return getRegisterEvent(eventEnc, before.TPMIValues, after.TPMIValues); } inline std::vector getPCICFGEvent(const PCM::RawEventEncoding & eventEnc, const SystemCounterState& before, const SystemCounterState& after) { return getRegisterEvent(eventEnc, before.PCICFGValues, after.PCICFGValues); } inline std::vector getMMIOEvent(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after) { return getRegisterEvent(eventEnc, before.MMIOValues, after.MMIOValues); } inline std::vector getPMTEvent(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after) { return getRegisterEvent(eventEnc, before.PMTValues, after.PMTValues); } template uint64 getMSREvent(const uint64& index, const PCM::MSRType& type, const CounterStateType& before, const CounterStateType& after) { switch (type) { case PCM::MSRType::Freerun: { const auto beforeIt = before.MSRValues.find(index); const auto afterIt = after.MSRValues.find(index); if (beforeIt != before.MSRValues.end() && afterIt != after.MSRValues.end()) { return afterIt->second - beforeIt->second; } break; } case PCM::MSRType::Static: { const auto result = after.MSRValues.find(index); if (result != after.MSRValues.end()) { return result->second; } break; } } return 0ULL; } } // namespace pcm #endif pcm-202502/src/daemon/000077500000000000000000000000001475730356400144355ustar00rootroot00000000000000pcm-202502/src/daemon/.cproject000066400000000000000000000274101475730356400162530ustar00rootroot00000000000000 pcm-202502/src/daemon/.gitignore000066400000000000000000000002031475730356400164200ustar00rootroot00000000000000.project client/.project client/.cproject client/Debug/client daemon/.project daemon/.cproject daemon/Debug/daemon daemon/test/testpcm-202502/src/daemon/common.h000066400000000000000000000237351475730356400161100ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2018,2022 Intel Corporation // written by Steven Briscoe #ifndef COMMON_H_ #define COMMON_H_ #include #include static const char DEFAULT_SHM_ID_LOCATION[] = "/tmp/opcm-daemon-shm-id"; static const char VERSION[] = "2.0.0"; #define MAX_CPU_CORES 4096 #define MAX_SOCKETS 256 #define MEMORY_MAX_IMC_CHANNELS (12) #define MEMORY_READ 0 #define MEMORY_WRITE 1 #define QPI_MAX_LINKS (MAX_SOCKETS * 4) #define VERSION_SIZE 12 #define ALIGNMENT 64 #define ALIGN(x) __attribute__((aligned((x)))) namespace PCMDaemon { typedef int int32; typedef long int64; typedef unsigned int uint32; typedef unsigned long uint64; struct PCMSystem { uint32 numOfCores; // the number of logical cores in the system uint32 numOfOnlineCores; // the number of online logical cores in the system uint32 numOfSockets; // the number of CPU sockets in the system uint32 numOfOnlineSockets; // the number of online CPU sockets in the system uint32 numOfQPILinksPerSocket; // the number of QPI or UPI (xPI) links per socket public: PCMSystem() : numOfCores(0), numOfOnlineCores(0), numOfSockets(0), numOfOnlineSockets(0), numOfQPILinksPerSocket(0) {} } ALIGN(ALIGNMENT); typedef struct PCMSystem PCMSystem; struct PCMCoreCounter { uint64 coreId = 0; // core ID int32 socketId = 0; // socket ID double instructionsPerCycle = 0.; // instructions per cycle metric uint64 cycles = 0; // cpu cycle metric uint64 instructionsRetired = 0; // number of retired instructions metric double execUsage = 0.; // instructions per nominal CPU cycle, i.e. in respect to the CPU frequency ignoring turbo and power saving double relativeFrequency = 0.; // frequency relative to nominal CPU frequency (“clockticks”/”invariant timer ticks”) double activeRelativeFrequency = 0.; // frequency relative to nominal CPU frequency excluding the time when the CPU is sleeping uint64 l3CacheMisses = 0; // L3 cache line misses uint64 l3CacheReference = 0; // L3 cache line references (accesses) uint64 l2CacheMisses = 0; // L2 cache line misses double l3CacheHitRatio = 0.; // L3 cache hit ratio double l2CacheHitRatio = 0.; // L2 cachhe hit ratio double l3CacheMPI = 0.; // number of L3 cache misses per retired instruction double l2CacheMPI = 0.; // number of L2 cache misses per retired instruction bool l3CacheOccupancyAvailable; // true if L3 cache occupancy metric is available uint64 l3CacheOccupancy; // L3 cache occupancy in KBytes bool localMemoryBWAvailable; // true if local memory bandwidth metric (L3 cache external bandwidth satisfied by local memory) is available uint64 localMemoryBW; // L3 cache external bandwidth satisfied by local memory (in MBytes) bool remoteMemoryBWAvailable; // true if remote memory bandwidth metric (L3 cache external bandwidth satisfied by remote memory) is available uint64 remoteMemoryBW; // L3 cache external bandwidth satisfied by remote memory (in MBytes) uint64 localMemoryAccesses = 0; // the number of local DRAM memory accesses uint64 remoteMemoryAccesses = 0; // the number of remote DRAM memory accesses int32 thermalHeadroom = 0; // thermal headroom in Kelvin (max design temperature – current temperature) public: PCMCoreCounter() : l3CacheOccupancyAvailable(false), l3CacheOccupancy(0), localMemoryBWAvailable(false), localMemoryBW(0), remoteMemoryBWAvailable(false), remoteMemoryBW(0) {} } ALIGN(ALIGNMENT); typedef struct PCMCoreCounter PCMCoreCounter; struct PCMCore { PCMCoreCounter cores[MAX_CPU_CORES]; bool packageEnergyMetricsAvailable; // true if CPU package (a.k.a. socket) energy metric is available double energyUsedBySockets[MAX_SOCKETS] ALIGN(ALIGNMENT); // energy consumed/used by CPU (socket) in Joules public: PCMCore() : packageEnergyMetricsAvailable(false) { for (int i = 0; i < MAX_SOCKETS; ++i) { energyUsedBySockets[i] = -1.0; } } } ALIGN(ALIGNMENT); typedef struct PCMCore PCMCore; struct PCMMemoryChannelCounter { float read; // DRAM read traffic in MBytes/sec float write; // DRAM write traffic in MBytes/sec float total; // total traffic in MBytes/sec public: PCMMemoryChannelCounter() : read(-1.0), write(-1.0), total(-1.0) {} } ALIGN(ALIGNMENT); typedef struct PCMMemoryChannelCounter PCMMemoryChannelCounter; struct PCMMemorySocketCounter { uint64 socketId = 0; // socket ID PCMMemoryChannelCounter channels[MEMORY_MAX_IMC_CHANNELS]; uint32 numOfChannels; // number of memory channels in the CPU socket float read; // DRAM read traffic in MBytes/sec float write; // DRAM write traffic in MBytes/sec float pmmRead; // PMM read traffic in MBytes/sec float pmmWrite; // PMM write traffic in MBytes/sec float total; // total traffic in MBytes/sec float memoryModeHitRate; // PMM memory mode hit rate estimation. Metric value range is [0..1] double dramEnergy; // energy consumed/used by DRAM memory in Joules public: PCMMemorySocketCounter() : numOfChannels(0), read(-1.0), write(-1.0), pmmRead(-1.0), pmmWrite(-1.0), total(-1.0), memoryModeHitRate(-1.0), dramEnergy(0.0) {} } ALIGN(ALIGNMENT); typedef struct PCMMemorySocketCounter PCMMemorySocketCounter; struct PCMMemorySystemCounter { float read; // DRAM read traffic in MBytes/sec float write; // DRAM write traffic in MBytes/sec float pmmRead; // PMM read traffic in MBytes/sec float pmmWrite; // PMM write traffic in MBytes/sec float total; // total traffic in MBytes/sec public: PCMMemorySystemCounter() : read(-1.0), write(-1.0), pmmRead(-1.0), pmmWrite(-1.0), total(-1.0) {} } ALIGN(ALIGNMENT); typedef struct PCMMemorySystemCounter PCMMemorySystemCounter; struct PCMMemory { PCMMemorySocketCounter sockets[MAX_SOCKETS]; PCMMemorySystemCounter system; bool dramEnergyMetricsAvailable; // true if DRAM energy metrics are available bool pmmMetricsAvailable; // true if PMM metrics are available public: PCMMemory() : dramEnergyMetricsAvailable(false), pmmMetricsAvailable(false) {} } ALIGN(ALIGNMENT); typedef struct PCMMemory PCMMemory; struct PCMQPILinkCounter { uint64 bytes; // bytes of certain traffic class transferred over QPI or UPI link double utilization; // utilization of the link caused by the certain traffic class public: PCMQPILinkCounter() : bytes(0), utilization(-1.0) {} } ALIGN(ALIGNMENT); typedef struct PCMQPILinkCounter PCMQPILinkCounter; struct PCMQPISocketCounter { uint64 socketId = 0; // socket ID PCMQPILinkCounter links[QPI_MAX_LINKS]; uint64 total; // total number of transferred bytes of a certain traffic class public: PCMQPISocketCounter() : total(0) {} } ALIGN(ALIGNMENT); typedef struct PCMQPISocketCounter PCMQPISocketCounter; struct PCMQPI { PCMQPISocketCounter incoming[MAX_SOCKETS]; // incoming data traffic class statistics uint64 incomingTotal; // incoming data traffic total bytes PCMQPISocketCounter outgoing[MAX_SOCKETS]; // outgoing data+"non-data" traffic class statistics uint64 outgoingTotal; // outgoing data+"non-data" traffic total bytes bool incomingQPITrafficMetricsAvailable; // true if incoming data traffic class statistics metrics are available bool outgoingQPITrafficMetricsAvailable; // true if outgoing data+"non-data" class statistics metrics are available public: PCMQPI() : incomingTotal(0), outgoingTotal(0), incomingQPITrafficMetricsAvailable(false), outgoingQPITrafficMetricsAvailable(false) {} } ALIGN(ALIGNMENT); typedef struct PCMQPI PCMQPI; struct SharedPCMCounters { PCMSystem system; PCMCore core; PCMMemory memory; PCMQPI qpi; } ALIGN(ALIGNMENT); typedef struct SharedPCMCounters SharedPCMCounters; struct SharedPCMState { char version[VERSION_SIZE]; // version (null-terminated string) uint64 lastUpdateTscBegin; // time stamp counter (TSC) obtained via rdtsc instruction *before* the state update uint64 timestamp; // monotonic time since some unspecified starting point in nanoseconds *after* the state update uint64 cyclesToGetPCMState; // time it took to update the state measured in TSC cycles uint32 pollMs; // the poll interval in shared memory in milliseconds SharedPCMCounters pcm; uint64 lastUpdateTscEnd; // time stamp counter (TSC) obtained via rdtsc instruction *after* the state update public: SharedPCMState() : lastUpdateTscBegin(0), timestamp(0), cyclesToGetPCMState(0), pollMs(-1), lastUpdateTscEnd(0) { std::fill(this->version, this->version + VERSION_SIZE, 0); } } ALIGN(ALIGNMENT); typedef struct SharedPCMState SharedPCMState; } #endif /* COMMON_H_ */ pcm-202502/src/daemon/daemon.cpp000066400000000000000000000707301475730356400164130ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2018,2022 Intel Corporation // written by Steven Briscoe #include #include #include #include #include #include #include #include #include #include #include #ifndef CLOCK_MONOTONIC_RAW #define CLOCK_MONOTONIC_RAW (4) /* needed for SLES11 */ #endif #include "daemon.h" #include "common.h" #include "pcm.h" namespace PCMDaemon { std::string Daemon::shmIdLocation_; int Daemon::sharedMemoryId_; SharedPCMState* Daemon::sharedPCMState_; Daemon::Daemon(int argc, char* argv[]) : debugMode_(false), pollIntervalMs_(0), groupName_(""), mode_(Mode::DIFFERENCE), pcmInstance_(NULL) { allowedSubscribers_.push_back("core"); allowedSubscribers_.push_back("memory"); allowedSubscribers_.push_back("qpi"); shmIdLocation_ = std::string(DEFAULT_SHM_ID_LOCATION); sharedMemoryId_ = 0; sharedPCMState_ = NULL; readApplicationArguments(argc, argv); setupSharedMemory(); setupPCM(); assert(sharedPCMState_); //Put the poll interval in shared memory so that the client knows sharedPCMState_->pollMs = pollIntervalMs_; collectionTimeAfter_ = 0; updatePCMState(&systemStatesBefore_, &socketStatesBefore_, &coreStatesBefore_, collectionTimeBefore_); systemStatesForQPIBefore_ = SystemCounterState(systemStatesBefore_); serverUncoreCounterStatesBefore_ = new ServerUncoreCounterState[pcmInstance_->getNumSockets()]; serverUncoreCounterStatesAfter_ = new ServerUncoreCounterState[pcmInstance_->getNumSockets()]; } int Daemon::run() { std::cout << "\n**** PCM Daemon Started *****\n"; while (true) { if (debugMode_) { time_t rawtime; struct tm timeinfo; char timeBuffer[200]; time(&rawtime); localtime_r(&rawtime, &timeinfo); snprintf(timeBuffer, 200, "[%02d %02d %04d %02d:%02d:%02d]", timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); std::cout << timeBuffer << "\tFetching counters...\n"; } // Here to make sure that any output elsewhere in this class or its callees is flushed before the sleep std::cout << std::flush; usleep(pollIntervalMs_ * 1000); getPCMCounters(); } return EXIT_SUCCESS; } Daemon::~Daemon() { deleteAndNullifyArray(serverUncoreCounterStatesBefore_); deleteAndNullifyArray(serverUncoreCounterStatesAfter_); } void Daemon::setupPCM() { pcmInstance_ = PCM::getInstance(); pcmInstance_->setBlocked(false); set_signal_handlers(); set_post_cleanup_callback(&Daemon::cleanup); checkAccessAndProgramPCM(); } void Daemon::checkAccessAndProgramPCM() { PCM::ErrorCode status; if (subscribers_.find("core") != subscribers_.end()) { EventSelectRegister defEventSelectRegister; defEventSelectRegister.value = 0; defEventSelectRegister.fields.usr = 1; defEventSelectRegister.fields.os = 1; defEventSelectRegister.fields.enable = 1; uint32 numOfCustomCounters = 4; EventSelectRegister regs[numOfCustomCounters]; PCM::ExtendedCustomCoreEventDescription conf; conf.nGPCounters = numOfCustomCounters; conf.gpCounterCfg = regs; try { pcmInstance_->setupCustomCoreEventsForNuma(conf); } catch (UnsupportedProcessorException& e) { std::cerr << "\nPCM daemon does not support your processor currently.\n\n"; exit(EXIT_FAILURE); } // Set default values for event select registers for (uint32 i(0); i < numOfCustomCounters; ++i) regs[i] = defEventSelectRegister; regs[0].fields.event_select = 0xB7; // OFFCORE_RESPONSE 0 event regs[0].fields.umask = 0x01; regs[1].fields.event_select = 0xBB; // OFFCORE_RESPONSE 1 event regs[1].fields.umask = 0x01; regs[2].fields.event_select = ARCH_LLC_MISS_EVTNR; regs[2].fields.umask = ARCH_LLC_MISS_UMASK; regs[3].fields.event_select = ARCH_LLC_REFERENCE_EVTNR; regs[3].fields.umask = ARCH_LLC_REFERENCE_UMASK; if (pcmInstance_->getMaxCustomCoreEvents() == 3) { conf.nGPCounters = 2; // drop LLC metrics } status = pcmInstance_->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf); } else { status = pcmInstance_->program(); } pcmInstance_->checkError(status); } void Daemon::readApplicationArguments(int argc, char* argv[]) { int opt; int counterCount(0); if (argc == 1) { printExampleUsageAndExit(argv); } std::cout << "\n"; while ((opt = getopt(argc, argv, "p:c:dg:m:s:")) != -1) { switch (opt) { case 'p': pollIntervalMs_ = atoi(optarg); std::cout << "Polling every " << pollIntervalMs_ << "ms\n"; break; case 'c': { std::string subscriber(optarg); if (subscriber == "all") { for (std::vector::const_iterator it = allowedSubscribers_.begin(); it != allowedSubscribers_.end(); ++it) { subscribers_.insert(std::pair(*it, 1)); ++counterCount; } } else { if (std::find(allowedSubscribers_.begin(), allowedSubscribers_.end(), subscriber) == allowedSubscribers_.end()) { printExampleUsageAndExit(argv); } subscribers_.insert(std::pair(subscriber, 1)); ++counterCount; } std::cout << "Listening to '" << subscriber << "' counters\n"; } break; case 'd': debugMode_ = true; std::cout << "Debug mode enabled\n"; break; case 'g': { groupName_ = std::string(optarg); std::cout << "Restricting to group: " << groupName_ << "\n"; } break; case 'm': { std::string mode = std::string(optarg); std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower); if (mode == "difference") { mode_ = Mode::DIFFERENCE; } else if (mode == "absolute") { mode_ = Mode::ABSOLUTE; } else { printExampleUsageAndExit(argv); } std::cout << "Operational mode: " << mode_ << " ("; if (mode_ == Mode::DIFFERENCE) std::cout << "difference"; else if (mode_ == Mode::ABSOLUTE) std::cout << "absolute"; std::cout << ")\n"; } break; case 's': { shmIdLocation_ = std::string(optarg); std::cout << "Shared memory ID location: " << shmIdLocation_ << "\n"; } break; default: printExampleUsageAndExit(argv); break; } } if (pollIntervalMs_ <= 0 || counterCount == 0) { printExampleUsageAndExit(argv); } std::cout << "PCM Daemon version: " << VERSION << "\n\n"; } void Daemon::printExampleUsageAndExit(char* argv[]) { std::cerr << "\n"; std::cerr << "-------------------------------------------------------------------\n"; std::cerr << "Example usage: " << argv[0] << " -p 50 -c numa -c memory\n"; std::cerr << "Poll every 50ms. Fetch counters for numa and memory\n\n"; std::cerr << "Example usage: " << argv[0] << " -p 250 -c all -g pcm -m absolute\n"; std::cerr << "Poll every 250ms. Fetch all counters (core, numa & memory).\n"; std::cerr << "Restrict access to user group 'pcm'. Store absolute values on each poll interval\n\n"; std::cerr << "-p for poll frequency\n"; std::cerr << "-c to request specific counters (Allowed counters: all "; for (std::vector::const_iterator it = allowedSubscribers_.begin(); it != allowedSubscribers_.end(); ++it) { std::cerr << *it; if (it + 1 != allowedSubscribers_.end()) { std::cerr << " "; } } std::cerr << ")"; std::cerr << "\n-d flag for debug output [optional]\n"; std::cerr << "-g to restrict access to group [optional]\n"; std::cerr << "-m stores differences or absolute values (Allowed: difference absolute) Default: difference [optional]\n"; std::cerr << "-s to store shared memory ID Default: " << std::string(DEFAULT_SHM_ID_LOCATION) << " [optional]\n"; std::cerr << "\n"; exit(EXIT_FAILURE); } void Daemon::setupSharedMemory() { int mode = 0660; int shmFlag = IPC_CREAT | mode; sharedMemoryId_ = shmget(IPC_PRIVATE, sizeof(SharedPCMState), shmFlag); if (sharedMemoryId_ < 0) { std::cerr << "Failed to allocate shared memory segment (errno=" << errno << ")\n"; exit(EXIT_FAILURE); } //Store shm id in a file (shmIdLocation_) int success = remove(shmIdLocation_.c_str()); if (success != 0) { std::cerr << "Failed to delete shared memory id location: " << shmIdLocation_ << " (errno=" << errno << ")\n"; } FILE* fp = fopen(shmIdLocation_.c_str(), "w"); if (!fp) { std::cerr << "Failed to create/write to shared memory key location: " << shmIdLocation_ << "\n"; exit(EXIT_FAILURE); } fprintf(fp, "%i", sharedMemoryId_); fclose(fp); if (groupName_.size() > 0) { ushort gid = (ushort)resolveGroupName(groupName_); struct shmid_ds shmData; shmData.shm_perm.gid = gid; shmData.shm_perm.mode = mode; success = shmctl(sharedMemoryId_, IPC_SET, &shmData); if (success < 0) { std::cerr << "Failed to IPC_SET (errno=" << errno << ")\n"; exit(EXIT_FAILURE); } //Change group of shared memory ID file uid_t uid = geteuid(); success = chown(shmIdLocation_.c_str(), uid, gid); if (success < 0) { std::cerr << "Failed to change ownership of shared memory key location: " << shmIdLocation_ << "\n"; exit(EXIT_FAILURE); } } sharedPCMState_ = (SharedPCMState*)shmat(sharedMemoryId_, NULL, 0); if (sharedPCMState_ == (void*)-1) { std::cerr << "Failed to attach shared memory segment (errno=" << errno << ")\n"; exit(EXIT_FAILURE); } //Clear out shared memory sharedPCMState_ = new (sharedPCMState_) SharedPCMState(); // use placement new operator } gid_t Daemon::resolveGroupName(const std::string& groupName) { struct group* group = getgrnam(groupName.c_str()); if (group == NULL) { std::cerr << "Failed to resolve group '" << groupName << "'\n"; exit(EXIT_FAILURE); } return group->gr_gid; } void Daemon::getPCMCounters() { std::copy(VERSION, VERSION + sizeof(VERSION), sharedPCMState_->version); sharedPCMState_->version[sizeof(VERSION)] = '\0'; sharedPCMState_->lastUpdateTscBegin = RDTSC(); updatePCMState(&systemStatesAfter_, &socketStatesAfter_, &coreStatesAfter_, collectionTimeAfter_); getPCMSystem(); if (subscribers_.find("core") != subscribers_.end()) { getPCMCore(); } if (subscribers_.find("memory") != subscribers_.end()) { getPCMMemory(); } bool fetchQPICounters = subscribers_.find("qpi") != subscribers_.end(); if (fetchQPICounters) { getPCMQPI(); } const auto lastUpdateTscEnd = RDTSC(); sharedPCMState_->cyclesToGetPCMState = lastUpdateTscEnd - sharedPCMState_->lastUpdateTscBegin; sharedPCMState_->timestamp = getTimestamp(); // As the client polls this timestamp (lastUpdateTsc) // All the data has to be in shm before sharedPCMState_->lastUpdateTscEnd = lastUpdateTscEnd; if (mode_ == Mode::DIFFERENCE) { swapPCMBeforeAfterState(); } if (fetchQPICounters) { systemStatesForQPIBefore_ = SystemCounterState(systemStatesAfter_); } std::swap(collectionTimeBefore_, collectionTimeAfter_); } void Daemon::updatePCMState(SystemCounterState* systemStates, std::vector* socketStates, std::vector* coreStates, uint64& t) { if (subscribers_.find("core") != subscribers_.end()) { pcmInstance_->getAllCounterStates(*systemStates, *socketStates, *coreStates); } else { if (subscribers_.find("memory") != subscribers_.end() || subscribers_.find("qpi") != subscribers_.end()) { pcmInstance_->getUncoreCounterStates(*systemStates, *socketStates); } } t = pcmInstance_->getTickCount(); } void Daemon::swapPCMBeforeAfterState() { //After state now becomes before state (for the next iteration) std::swap(coreStatesBefore_, coreStatesAfter_); std::swap(socketStatesBefore_, socketStatesAfter_); std::swap(systemStatesBefore_, systemStatesAfter_); std::swap(serverUncoreCounterStatesBefore_, serverUncoreCounterStatesAfter_); } void Daemon::getPCMSystem() { PCMSystem& system = sharedPCMState_->pcm.system; system.numOfCores = pcmInstance_->getNumCores(); system.numOfOnlineCores = pcmInstance_->getNumOnlineCores(); system.numOfSockets = pcmInstance_->getNumSockets(); system.numOfOnlineSockets = pcmInstance_->getNumOnlineSockets(); system.numOfQPILinksPerSocket = pcmInstance_->getQPILinksPerSocket(); } void Daemon::getPCMCore() { PCMCore& core = sharedPCMState_->pcm.core; const uint32 numCores = sharedPCMState_->pcm.system.numOfCores; uint32 onlineCoresI(0); for (uint32 coreI(0); coreI < numCores; ++coreI) { if (!pcmInstance_->isCoreOnline(coreI)) continue; PCMCoreCounter& coreCounters = core.cores[onlineCoresI]; int32 socketId = pcmInstance_->getSocketId(coreI); double instructionsPerCycle = getIPC(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); uint64 cycles = getCycles(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); uint64 instructionsRetired = getInstructionsRetired(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); double execUsage = getExecUsage(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); double relativeFrequency = getRelativeFrequency(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); double activeRelativeFrequency = getActiveRelativeFrequency(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); uint64 l3CacheMisses = getNumberOfCustomEvents(2, coreStatesBefore_[coreI], coreStatesAfter_[coreI]); uint64 l3CacheReference = getNumberOfCustomEvents(3, coreStatesBefore_[coreI], coreStatesAfter_[coreI]); uint64 l2CacheMisses = getL2CacheMisses(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); double l3CacheHitRatio = getL3CacheHitRatio(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); double l2CacheHitRatio = getL2CacheHitRatio(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); double l3CacheMPI = double(l3CacheMisses) / instructionsRetired; double l2CacheMPI = double(l2CacheMisses) / instructionsRetired; int32 thermalHeadroom = coreStatesAfter_[coreI].getThermalHeadroom(); coreCounters.coreId = coreI; coreCounters.socketId = socketId; coreCounters.instructionsPerCycle = instructionsPerCycle; coreCounters.cycles = cycles; coreCounters.instructionsRetired = instructionsRetired; coreCounters.execUsage = execUsage; coreCounters.relativeFrequency = relativeFrequency; coreCounters.activeRelativeFrequency = activeRelativeFrequency; coreCounters.l3CacheMisses = l3CacheMisses; coreCounters.l3CacheReference = l3CacheReference; coreCounters.l2CacheMisses = l2CacheMisses; coreCounters.l3CacheHitRatio = l3CacheHitRatio; coreCounters.l2CacheHitRatio = l2CacheHitRatio; coreCounters.l3CacheMPI = l3CacheMPI; coreCounters.l2CacheMPI = l2CacheMPI; coreCounters.thermalHeadroom = thermalHeadroom; coreCounters.l3CacheOccupancyAvailable = pcmInstance_->L3CacheOccupancyMetricAvailable(); if (coreCounters.l3CacheOccupancyAvailable) { uint64 l3CacheOccupancy = getL3CacheOccupancy(coreStatesAfter_[coreI]); coreCounters.l3CacheOccupancy = l3CacheOccupancy; } coreCounters.localMemoryBWAvailable = pcmInstance_->CoreLocalMemoryBWMetricAvailable(); if (coreCounters.localMemoryBWAvailable) { uint64 localMemoryBW = getLocalMemoryBW(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); coreCounters.localMemoryBW = localMemoryBW; } coreCounters.remoteMemoryBWAvailable = pcmInstance_->CoreRemoteMemoryBWMetricAvailable(); if (coreCounters.remoteMemoryBWAvailable) { uint64 remoteMemoryBW = getRemoteMemoryBW(coreStatesBefore_[coreI], coreStatesAfter_[coreI]); coreCounters.remoteMemoryBW = remoteMemoryBW; } coreCounters.localMemoryAccesses = getNumberOfCustomEvents(0, coreStatesBefore_[coreI], coreStatesAfter_[coreI]); coreCounters.remoteMemoryAccesses = getNumberOfCustomEvents(1, coreStatesBefore_[coreI], coreStatesAfter_[coreI]); ++onlineCoresI; } const uint32 numSockets = sharedPCMState_->pcm.system.numOfSockets; core.packageEnergyMetricsAvailable = pcmInstance_->packageEnergyMetricsAvailable(); if (core.packageEnergyMetricsAvailable) { for (uint32 i(0); i < numSockets; ++i) { core.energyUsedBySockets[i] = getConsumedJoules(socketStatesBefore_[i], socketStatesAfter_[i]); } } } void Daemon::getPCMMemory() { pcmInstance_->disableJKTWorkaround(); PCMMemory& memory = sharedPCMState_->pcm.memory; memory.dramEnergyMetricsAvailable = pcmInstance_->dramEnergyMetricsAvailable(); memory.pmmMetricsAvailable = pcmInstance_->PMMTrafficMetricsAvailable(); const uint32 numSockets = sharedPCMState_->pcm.system.numOfSockets; for (uint32 i(0); i < numSockets; ++i) { serverUncoreCounterStatesAfter_[i] = pcmInstance_->getServerUncoreCounterState(i); } uint64 elapsedTime = collectionTimeAfter_ - collectionTimeBefore_; float iMC_Rd_socket_chan[MAX_SOCKETS][MEMORY_MAX_IMC_CHANNELS]; float iMC_Wr_socket_chan[MAX_SOCKETS][MEMORY_MAX_IMC_CHANNELS]; float iMC_Rd_socket[MAX_SOCKETS]; float iMC_Wr_socket[MAX_SOCKETS]; float iMC_PMM_Rd_socket[MAX_SOCKETS]; float iMC_PMM_Wr_socket[MAX_SOCKETS]; for (uint32 skt(0); skt < numSockets; ++skt) { iMC_Rd_socket[skt] = 0.0; iMC_Wr_socket[skt] = 0.0; iMC_PMM_Rd_socket[skt] = 0.0; iMC_PMM_Wr_socket[skt] = 0.0; auto toBW = [&elapsedTime](const uint64 bytes) { return (float)(bytes / 1000000.0 / (elapsedTime / 1000.0)); }; if (memory.pmmMetricsAvailable) { iMC_PMM_Rd_socket[skt] = toBW(getBytesReadFromPMM(socketStatesBefore_[skt], socketStatesAfter_[skt])); iMC_PMM_Wr_socket[skt] = toBW(getBytesWrittenToPMM(socketStatesBefore_[skt], socketStatesAfter_[skt])); } for (uint32 channel(0); channel < MEMORY_MAX_IMC_CHANNELS; ++channel) { //In case of JKT-EN, there are only three channels. Skip one and continue. const bool memoryReadNotAvailable = getMCCounter(channel, MEMORY_READ, serverUncoreCounterStatesBefore_[skt], serverUncoreCounterStatesAfter_[skt]) == 0; const bool memoryWriteNotAvailable = getMCCounter(channel, MEMORY_WRITE, serverUncoreCounterStatesBefore_[skt], serverUncoreCounterStatesAfter_[skt]) == 0; if (memoryReadNotAvailable && memoryWriteNotAvailable) { iMC_Rd_socket_chan[skt][channel] = -1.0; iMC_Wr_socket_chan[skt][channel] = -1.0; continue; } iMC_Rd_socket_chan[skt][channel] = (float)(toBW(getMCCounter(channel, MEMORY_READ, serverUncoreCounterStatesBefore_[skt], serverUncoreCounterStatesAfter_[skt]) * 64)); iMC_Wr_socket_chan[skt][channel] = (float)(toBW(getMCCounter(channel, MEMORY_WRITE, serverUncoreCounterStatesBefore_[skt], serverUncoreCounterStatesAfter_[skt]) * 64)); iMC_Rd_socket[skt] += iMC_Rd_socket_chan[skt][channel]; iMC_Wr_socket[skt] += iMC_Wr_socket_chan[skt][channel]; } } float systemRead(0.0); float systemWrite(0.0); float systemPMMRead(0.0); float systemPMMWrite(0.0); uint32 onlineSocketsI(0); for (uint32 skt(0); skt < numSockets; ++skt) { if (!pcmInstance_->isSocketOnline(skt)) continue; uint64 currentChannelI(0); for (uint64 channel(0); channel < MEMORY_MAX_IMC_CHANNELS; ++channel) { //If the channel read neg. value, the channel is not working; skip it. if (iMC_Rd_socket_chan[skt][channel] < 0.0 && iMC_Wr_socket_chan[skt][channel] < 0.0) continue; const float socketChannelRead = iMC_Rd_socket_chan[skt][channel]; const float socketChannelWrite = iMC_Wr_socket_chan[skt][channel]; memory.sockets[onlineSocketsI].channels[currentChannelI].read = socketChannelRead; memory.sockets[onlineSocketsI].channels[currentChannelI].write = socketChannelWrite; memory.sockets[onlineSocketsI].channels[currentChannelI].total = socketChannelRead + socketChannelWrite; ++currentChannelI; } memory.sockets[onlineSocketsI].socketId = skt; memory.sockets[onlineSocketsI].numOfChannels = currentChannelI; memory.sockets[onlineSocketsI].read = iMC_Rd_socket[skt]; memory.sockets[onlineSocketsI].write = iMC_Wr_socket[skt]; memory.sockets[onlineSocketsI].pmmRead = iMC_PMM_Rd_socket[skt]; memory.sockets[onlineSocketsI].pmmWrite = iMC_PMM_Wr_socket[skt]; memory.sockets[onlineSocketsI].total = iMC_Rd_socket[skt] + iMC_Wr_socket[skt] + iMC_PMM_Rd_socket[skt] + iMC_PMM_Wr_socket[skt]; const auto all = memory.sockets[onlineSocketsI].total; memory.sockets[onlineSocketsI].memoryModeHitRate = (all == 0.0) ? -1.0 : ((iMC_Rd_socket[skt] + iMC_Wr_socket[skt]) / all); // simplified approximation if (memory.dramEnergyMetricsAvailable) { memory.sockets[onlineSocketsI].dramEnergy = getDRAMConsumedJoules(socketStatesBefore_[skt], socketStatesAfter_[skt]); } systemRead += iMC_Rd_socket[skt]; systemWrite += iMC_Wr_socket[skt]; systemPMMRead += iMC_PMM_Rd_socket[skt]; systemPMMWrite += iMC_PMM_Wr_socket[skt]; ++onlineSocketsI; } memory.system.read = systemRead; memory.system.write = systemWrite; memory.system.total = systemRead + systemWrite; } void Daemon::getPCMQPI() { PCMQPI& qpi = sharedPCMState_->pcm.qpi; const uint32 numSockets = sharedPCMState_->pcm.system.numOfSockets; const uint32 numLinksPerSocket = sharedPCMState_->pcm.system.numOfQPILinksPerSocket; qpi.incomingQPITrafficMetricsAvailable = pcmInstance_->incomingQPITrafficMetricsAvailable(); if (qpi.incomingQPITrafficMetricsAvailable) { uint32 onlineSocketsI(0); for (uint32 i(0); i < numSockets; ++i) { if (!pcmInstance_->isSocketOnline(i)) continue; qpi.incoming[onlineSocketsI].socketId = i; uint64 total(0); for (uint32 l(0); l < numLinksPerSocket; ++l) { uint64 bytes = getIncomingQPILinkBytes(i, l, systemStatesBefore_, systemStatesAfter_); qpi.incoming[onlineSocketsI].links[l].bytes = bytes; qpi.incoming[onlineSocketsI].links[l].utilization = getIncomingQPILinkUtilization(i, l, systemStatesForQPIBefore_, systemStatesAfter_); total += bytes; } qpi.incoming[i].total = total; ++onlineSocketsI; } qpi.incomingTotal = getAllIncomingQPILinkBytes(systemStatesBefore_, systemStatesAfter_); } qpi.outgoingQPITrafficMetricsAvailable = pcmInstance_->outgoingQPITrafficMetricsAvailable(); if (qpi.outgoingQPITrafficMetricsAvailable) { uint32 onlineSocketsI(0); for (uint32 i(0); i < numSockets; ++i) { if (!pcmInstance_->isSocketOnline(i)) continue; qpi.outgoing[onlineSocketsI].socketId = i; uint64 total(0); for (uint32 l(0); l < numLinksPerSocket; ++l) { uint64 bytes = getOutgoingQPILinkBytes(i, l, systemStatesBefore_, systemStatesAfter_); qpi.outgoing[onlineSocketsI].links[l].bytes = bytes; qpi.outgoing[onlineSocketsI].links[l].utilization = getOutgoingQPILinkUtilization(i, l, systemStatesForQPIBefore_, systemStatesAfter_); total += bytes; } qpi.outgoing[i].total = total; ++onlineSocketsI; } qpi.outgoingTotal = getAllOutgoingQPILinkBytes(systemStatesBefore_, systemStatesAfter_); } } uint64 Daemon::getTimestamp() { struct timespec now; clock_gettime(CLOCK_MONOTONIC_RAW, &now); uint64 epoch = (uint64)now.tv_sec * 1E9; epoch += (uint64)now.tv_nsec; return epoch; } void Daemon::cleanup() { if (sharedPCMState_ != NULL) { //Detach shared memory segment int success = shmdt(sharedPCMState_); if (success != 0) { std::cerr << "Failed to detach the shared memory segment (errno=" << errno << ")\n"; } else { // Delete segment success = shmctl(sharedMemoryId_, IPC_RMID, NULL); if (success != 0) { std::cerr << "Failed to delete the shared memory segment (errno=" << errno << ")\n"; } } //Delete shared memory ID file success = remove(shmIdLocation_.c_str()); if (success != 0) { std::cerr << "Failed to delete shared memory id location: " << shmIdLocation_ << " (errno=" << errno << ")\n"; } } } } pcm-202502/src/daemon/daemon.h000066400000000000000000000036041475730356400160540ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe #ifndef DAEMON_H_ #define DAEMON_H_ #include #include #include #include #include "common.h" #include "pcm.h" namespace PCMDaemon { enum Mode { DIFFERENCE, ABSOLUTE }; class Daemon { public: Daemon(int argc, char *argv[]); ~Daemon(); int run(); Daemon (const Daemon &) = delete; Daemon & operator = (const Daemon &) = delete; private: void setupPCM(); void checkAccessAndProgramPCM(); void readApplicationArguments(int argc, char *argv[]); void printExampleUsageAndExit(char *argv[]); void setupSharedMemory(); gid_t resolveGroupName(const std::string& groupName); void getPCMCounters(); void updatePCMState(SystemCounterState* systemStates, std::vector* socketStates, std::vector* coreStates, uint64 & t); void swapPCMBeforeAfterState(); void getPCMSystem(); void getPCMCore(); void getPCMMemory(); void getPCMQPI(); uint64 getTimestamp(); static void cleanup(); bool debugMode_; uint32 pollIntervalMs_; std::string groupName_; Mode mode_; static std::string shmIdLocation_; static int sharedMemoryId_; static SharedPCMState* sharedPCMState_; PCM* pcmInstance_; std::map subscribers_; std::vector allowedSubscribers_; //Data for core, socket and system state uint64 collectionTimeBefore_{0ULL}, collectionTimeAfter_{0ULL}; std::vector coreStatesBefore_, coreStatesAfter_; std::vector socketStatesBefore_, socketStatesAfter_; SystemCounterState systemStatesBefore_, systemStatesForQPIBefore_, systemStatesAfter_; ServerUncoreCounterState* serverUncoreCounterStatesBefore_; ServerUncoreCounterState* serverUncoreCounterStatesAfter_; }; } #endif /* DAEMON_H_ */ pcm-202502/src/daemon/main.cpp000066400000000000000000000004071475730356400160660ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe #include "daemon.h" PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { PCMDaemon::Daemon daemon(argc, argv); return daemon.run(); } pcm-202502/src/daemon/pcm.h000066400000000000000000000007411475730356400153670ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe #ifndef PCM_H_ #define PCM_H_ #ifdef _MSC_VER #include #include "../../../PCM_Win/windriver.h" #else #include #include #include // for gettimeofday() #endif #include "../cpucounters.h" #include "../utils.h" #ifdef _MSC_VER #include "../../freegetopt/getopt.h" #endif using namespace pcm; #endif /* PCM_H_ */ pcm-202502/src/dashboard.cpp000066400000000000000000001002131475730356400156220ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #include #include #include #include "pcm-accel-common.h" #include "dashboard.h" namespace pcm { class Target { public: virtual std::string operator () (const std::string& refId) const = 0; virtual ~Target() {} }; class InfluxDBTarget : public Target { std::string alias; std::string metric; InfluxDBTarget() = delete; public: InfluxDBTarget(const std::string & alias_, const std::string & metric_) : alias(alias_), metric(metric_) {} std::string operator () (const std::string & refId) const { std::string result; result += R"PCMDELIMITER( { "alias": ")PCMDELIMITER"; result += alias; result += R"PCMDELIMITER(", "groupBy": [ { "params": [ "$__interval" ], "type": "time" }, { "params": [ "null" ], "type": "fill" } ], "measurement": "http", "orderByTime": "ASC", "policy": "default", "query": "SELECT )PCMDELIMITER"; result += metric; result += R"PCMDELIMITER( FROM \"http\" WHERE (\"url\" = '$node') AND $timeFilter GROUP BY time($__interval) fill(null)", "rawQuery": true, "refId": ")PCMDELIMITER"; result += refId; result += R"PCMDELIMITER(", "resultFormat": "time_series", "select": [ [ { "params": [ "value" ], "type": "field" }, { "params": [], "type": "mean" } ] ], "tags": [] })PCMDELIMITER"; return result; } }; class PrometheusTarget : public Target { std::string legend, expr; PrometheusTarget() = delete; public: PrometheusTarget(const std::string& legend_, const std::string& expr_) : legend(legend_), expr(expr_) {} std::string operator () (const std::string& refId) const { std::string result; result += R"PCMDELIMITER( { "expr": ")PCMDELIMITER"; result += expr; result += R"PCMDELIMITER(", "instant": false, "interval": "", "legendFormat": ")PCMDELIMITER"; result += legend; result += R"PCMDELIMITER(", "refId": ")PCMDELIMITER"; result += refId; result += R"PCMDELIMITER(" })PCMDELIMITER"; return result; } }; const char * defaultDataSource = "null"; class Panel { int x, y, w, h; std::string title; std::vector> targets; Panel() = delete; protected: std::string getHeader(const int id) const { std::string result; result += R"PCMDELIMITER( { "datasource": )PCMDELIMITER"; result += defaultDataSource; result += R"PCMDELIMITER(, "interval": "2s", "gridPos": { )PCMDELIMITER"; result += " \"x\": " + std::to_string(x) + ",\n"; result += " \"y\": " + std::to_string(y) + ",\n"; result += " \"w\": " + std::to_string(w) + ",\n"; result += " \"h\": " + std::to_string(h); result += R"PCMDELIMITER( }, "title": ")PCMDELIMITER"; result += title; result += "\",\n \"id\": " + std::to_string(id) + ",\n \"targets\": ["; char refId[] = "A"; for (size_t i = 0; i< targets.size(); ++i, ++(refId[0])) { if (i > 0) { result += ","; } result += targets[i]->operator()(refId); } result += "\n ],\n"; return result; } public: Panel(int x_, int y_, int w_, int h_, const std::string & title_) : x(x_), y(y_), w(w_), h(h_), title(title_) {} void push(const std::shared_ptr & t) { targets.push_back(t); } virtual std::string operator () (const int id) const = 0; virtual ~Panel() {} }; class GaugePanel : public Panel { GaugePanel() = delete; public: GaugePanel(int x_, int y_, int w_, int h_, const std::string & title_) : Panel(x_, y_, w_, h_, title_) {} std::string operator () (const int id) const { std::string result = Panel::getHeader(id); result += R"PCMDELIMITER( "options": { "fieldOptions": { "calcs": [ "lastNotNull" ], "defaults": { "mappings": [], "max": 100, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 70 } ] }, "unit": "%" }, "overrides": [], "values": false }, "orientation": "auto", "showThresholdLabels": false, "showThresholdMarkers": true }, "pluginVersion": "6.7.2", "timeFrom": null, "timeShift": null, "type": "gauge" })PCMDELIMITER"; return result; } }; class BarGaugePanel : public Panel { BarGaugePanel() = delete; public: BarGaugePanel(int x_, int y_, int w_, int h_, const std::string & title_) : Panel(x_, y_, w_, h_, title_) {} std::string operator () (const int id) const { std::string result = Panel::getHeader(id); result += R"PCMDELIMITER( "cacheTimeout": null, "links": [ { "title": "", "url": "" } ], "options": { "displayMode": "lcd", "fieldOptions": { "calcs": [ "lastNotNull" ], "defaults": { "mappings": [ { "$$hashKey": "object:413", "id": 0, "op": "=", "text": "N/A", "type": 1, "value": "null" } ], "nullValueMode": "connected", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [], "values": false }, "orientation": "vertical", "showUnfilled": true }, "pluginVersion": "6.7.2", "timeFrom": null, "timeShift": null, "type": "bargauge" })PCMDELIMITER"; return result; } }; class TimeSeriesPanel : public Panel { std::string yAxisLabel; bool stack; TimeSeriesPanel() = delete; public: TimeSeriesPanel(int x_, int y_, int w_, int h_, const std::string & title_, const std::string & yAxisLabel_, bool stack_) : Panel(x_, y_, w_, h_, title_) , yAxisLabel(yAxisLabel_) , stack(stack_) { } std::string operator () (const int id) const { std::string result = Panel::getHeader(id); result += R"PCMDELIMITER( "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "fill": 1, "fillGradient": 0, "hiddenSeries": false, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [ { "title": "", "url": "" } ], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "10.4.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": )PCMDELIMITER"; result += stack? "true" : "false"; result += R"PCMDELIMITER(, "steppedLine": false, "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "timeseries", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:2758", "format": "none", "label": ")PCMDELIMITER"; result += yAxisLabel; result += R"PCMDELIMITER(", "logBase": 1, "max": null, "min": "0", "show": true }, { "$$hashKey": "object:2759", "format": "none", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } })PCMDELIMITER"; return result; } }; class Dashboard { std::string title; PCMDashboardType type; std::vector> panels; Dashboard() = delete; public: Dashboard(const std::string & title_,PCMDashboardType type_) : title(title_), type(type_) {} void push(const std::shared_ptr & p) { panels.push_back(p); } std::string operator () () const { std::string result; std::string definition,query; if(type==InfluxDB){ definition = "\"SHOW TAG VALUES WITH KEY = \\\"url\\\"\""; query = "\"SHOW TAG VALUES WITH KEY = \\\"url\\\"\""; } else{ definition = "\"label_values(Number_of_sockets,instance)\""; query = "{\"query\": \"label_values(Number_of_sockets,instance)\",\"refId\": \"StandardVariableQuery\"}"; } result += R"PCMDELIMITER({ "annotations": { "list": [ { "$$hashKey": "object:2661", "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "gnetId": null, "graphTooltip": 0, "id": 1, "links": [], "panels": [)PCMDELIMITER"; for (size_t i=0; i < panels.size(); ++i) { if (i > 0) { result += ","; } result += panels[i]->operator()(i + 2); } result += R"PCMDELIMITER( ], "refresh": "1s", "schemaVersion": 22, "style": "dark", "tags": [], "templating": { "list": [ { "current": { "selected": false, "text": "ip addr:port", "value": "ip addr:port" }, "datasource": null, "definition": )PCMDELIMITER"; result +=definition; result += R"PCMDELIMITER(, "hide": 0, "includeAll": false, "label": "Host", "multi": false, "name": "node", "options": [], "query":)PCMDELIMITER"; result+=query; result += R"PCMDELIMITER(, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 0, "type": "query" } ] }, "time": { "from": "now-5m", "to": "now" }, "timepicker": { "refresh_intervals": [ "1s", "2s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ] }, "timezone": "", "title": ")PCMDELIMITER"; result += title; result += R"PCMDELIMITER(", "uid": "A_CvwTCWk", "variables": { "list": [] }, "version": 1 })PCMDELIMITER"; return result; } }; std::string prometheusMetric(const std::string& m) { auto result = m; for (char& c : result) { if (c == ' ' || c == '-') { c = '_'; } } return result; } std::string prometheusSystem() { return "{instance=\\\"$node\\\", aggregate=\\\"system\\\"}"; } std::string prometheusSocket(const std::string& S, const bool aggregate = true) { if (aggregate) return "{instance=\\\"$node\\\", aggregate=\\\"socket\\\", socket=\\\"" + S + "\\\"}"; return "{instance=\\\"$node\\\", socket=\\\"" + S + "\\\"}"; } std::string prometheusSystem(const std::string& S) { return "{instance=\\\"$node\\\", aggregate=\\\"system\\\", socket=\\\"" + S + "\\\"}"; } std::string influxDB_Counters(const std::string& S, const std::string& m, const char * domain) { return std::string("mean(\\\"Sockets_") + S + "_" + domain + "_" + m + "\\\")"; } std::string influxDB_Counters(const std::string& m, const char* domain) { return std::string("mean(\\\"") + domain + "_" + m + "\\\")"; } std::string influxDBCore_Aggregate_Core_Counters(const std::string& S, const std::string& m) { return influxDB_Counters(S, m, "Core Aggregate_Core Counters"); } std::string influxDBAccel_Counters(const std::string& S, const std::string& m) { AcceleratorCounterState * accs = AcceleratorCounterState::getInstance(); return std::string("mean(\\\"Sockets_") + S + "_Accelerators_" +accs->getAccelCounterName()+" Counters Device_" + m + "\\\")"; } std::string influxDBCore_Aggregate_Core_Counters(const std::string& m) { return influxDB_Counters(m, "Core Aggregate_Core Counters"); } std::string influxDBUncore_Uncore_Counters(const std::string& S, const std::string& m) { return influxDB_Counters(S, m, "Uncore_Uncore Counters"); } const char* interval = "[4s]"; std::string prometheusCounters(const std::string& S, const std::string& m, const bool aggregate = true) { return std::string("rate(") + prometheusMetric(m) + prometheusSocket(S, aggregate) + interval + ")"; } std::string prometheusCounters(const std::string& m) { return std::string("rate(") + prometheusMetric(m) + prometheusSystem() + interval + ")"; } std::mutex dashboardGenMutex; std::string getPCMDashboardJSON(const PCMDashboardType type, int ns, int nu, int nc) { auto pcm = PCM::getInstance(); auto accs = AcceleratorCounterState::getInstance(); std::lock_guard dashboardGenGuard(dashboardGenMutex); const size_t NumSockets = (ns < 0) ? pcm->getNumSockets() : ns; const size_t NumUPILinksPerSocket = (nu < 0) ? pcm->getQPILinksPerSocket() : nu; const size_t maxCState = (nc < 0) ? PCM::MAX_C_STATE : nc; constexpr int height = 5; constexpr int width = 15; constexpr int max_width = 24; int y = 0; if (type == Prometheus_Default) { interval = "[60s]"; defaultDataSource = "\"prometheus\""; } else { interval = "[4s]"; defaultDataSource = "null"; } char buffer[64]; std::string hostname = "unknown hostname"; if (gethostname(buffer, 63) == 0) { hostname = buffer; } Dashboard dashboard("Intel(r) Performance Counter Monitor (Intel(r) PCM) Dashboard - " + hostname,type); auto createTarget = [type](const std::string& title, const std::string& inluxdbMetric, const std::string& prometheusExpr) -> std::shared_ptr { std::shared_ptr t; if (type == InfluxDB) t = std::make_shared(title, inluxdbMetric); else t = std::make_shared(title, prometheusExpr); return t; }; auto scaled = [&] (const char * m, const char * unit, const char * op, const bool total = true) { auto panel = std::make_shared(0, y, width, height, std::string(m), unit, false); auto panel1 = std::make_shared(width, y, max_width - width, height, std::string(m) + " (" + unit + ")"); y += height; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto t = createTarget("Socket" + S, influxDBCore_Aggregate_Core_Counters(S, m) + op, prometheusCounters(S, m) + op); panel->push(t); panel1->push(t); } if (total) { auto t = createTarget("Total", influxDBCore_Aggregate_Core_Counters(m) + op, prometheusCounters(m) + op); panel->push(t); panel1->push(t); dashboard.push(panel); dashboard.push(panel1); } }; if (type == InfluxDB) { scaled("Core Frequency", "GHz", "/1000000000"); } for (size_t s = 0; type == InfluxDB && s < NumSockets; ++s) { const char * op = "/1000000000"; const auto S = std::to_string(s); auto panel = std::make_shared(0, y, width, height, std::string("Socket") + S + " Uncore Frequencies", "GHz", false); auto panel1 = std::make_shared(width, y, max_width - width, height, std::string("Current Socket") + S + " Uncore Frequencies (GHz)"); y += height; for (size_t d = 0; d < (std::max)(pcm->getNumUFSDies(), (size_t)1ULL); ++d) { auto m = std::string("Uncore Frequency Die ") + std::to_string(d); auto t = createTarget(m, influxDBUncore_Uncore_Counters(S, m) + op, prometheusCounters(S, m, false) + op); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); } for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto panel = std::make_shared(0, y, width, height, std::string("Socket") + S + " Energy Consumption", "Watt", false); auto panel1 = std::make_shared(width, y, max_width - width, height, std::string("Current Socket") + S + " Energy Consumption (Watt)"); y += height; for (auto &m : {"Package Joules Consumed", "DRAM Joules Consumed", "PP0 Joules Consumed", "PP1 Joules Consumed"}) { auto t = createTarget(m, influxDBUncore_Uncore_Counters(S, m), prometheusCounters(S, m, false)); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); } { auto panel = std::make_shared(0, y, width, height, "Memory Bandwidth", "MByte/sec", false); auto panel1 = std::make_shared(width, y, max_width - width, height, "Memory Bandwidth (MByte/sec)"); y += height; auto genAll = [type](const std::string& special) -> std::string { std::string all; for (auto& m : { "DRAM Reads", "DRAM Writes", "Persistent Memory Reads", "Persistent Memory Writes" }) { if (all.size() > 0) { all += " + "; } if (type == InfluxDB) all += special + "_Uncore Counters_" + m + "\\\")/1048576"; else all += std::string("rate(") + prometheusMetric(m) + special + interval + ")/1048576"; } return all; }; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto t = createTarget("Socket" + S, genAll("mean(\\\"Sockets_" + S + "_Uncore"), genAll(prometheusSocket(S, false))); panel->push(t); panel1->push(t); } auto t = createTarget("Total", genAll("mean(\\\"Uncore Aggregate"), genAll(prometheusSystem())); panel->push(t); panel1->push(t); dashboard.push(panel); dashboard.push(panel1); }; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto panel = std::make_shared(0, y, width, height, std::string("Socket") + S + " Memory Bandwidth", "MByte/sec", false); auto panel1 = std::make_shared(width, y, max_width - width, height, std::string("Current Socket") + S + " Memory Bandwidth (MByte/sec)"); y += height; for (auto& m : { "DRAM Reads", "DRAM Writes", "Persistent Memory Reads", "Persistent Memory Writes" }) { auto t = createTarget(m, influxDBUncore_Uncore_Counters(S, m) + "/1048576", prometheusCounters(S, m, false) + "/1048576"); panel->push(t); panel1->push(t); } for (auto& m : {"CXL Write Mem","CXL Write Cache" }){ auto t = createTarget(m, "mean(\\\"QPI/UPI Links_QPI Counters Socket " + S + "_" + m + "\\\")/1048576", prometheusCounters(S, m, false) + "/1048576"); panel->push(t); panel1->push(t); } for (std::string m : { "DRAM ", "Persistent Memory " }) { auto t = createTarget(m + "Total", "(" + influxDBUncore_Uncore_Counters(S, m + "Writes") + "+" + influxDBUncore_Uncore_Counters(S, m + "Reads") + ")/1048576", "(" + prometheusCounters(S, m + "Writes", false) + "+" + prometheusCounters(S, m + "Reads", false) + ")/1048576"); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); } if(pcm->nearMemoryMetricsAvailable()){ // Near Memory statistics y += height; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto panel = std::make_shared(0, y, width, height,std::string("Socket") + S + " Near Memory Hit Miss", "M/s", false); auto panel1 = std::make_shared(width, y, max_width - width, height,std::string("Current Socket") + S + "Near Memory Hit/Miss"); for (auto& m : {"NM Hits","NM Misses","NM Miss Bw"}) { auto t = createTarget(m, influxDBUncore_Uncore_Counters(S, m) + "/1048576", prometheusCounters(S, m, false) + "/1048576"); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); } auto NMpanel = std::make_shared(0, y, width, height, "Near Memory Hit Rate", "NM Hit Rate", false); auto NMpanel1 = std::make_shared(width, y, max_width - width, height, "Near Memory HitRate"); y += height; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto t = createTarget("Socket " + S, influxDBUncore_Uncore_Counters(S, "NM HitRate") + "/1048576", prometheusCounters(S, "NM HitRate", false) + "/1048576"); NMpanel->push(t); NMpanel1->push(t); } dashboard.push(NMpanel); dashboard.push(NMpanel1); } auto panel = std::make_shared(0, y, width, height, "PMEM/DRAM Bandwidth Ratio", "PMEM/DRAM", false); auto panel1 = std::make_shared(width, y, max_width - width, height, "PMEM/DRAM Bandwidth Ratio"); y += height; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto t = createTarget("Socket" + S, "(" + influxDBUncore_Uncore_Counters(S, "Persistent Memory Writes") + "+" + influxDBUncore_Uncore_Counters(S, "Persistent Memory Reads") + ")/" + "(" + influxDBUncore_Uncore_Counters(S, "DRAM Writes") + "+" + influxDBUncore_Uncore_Counters(S, "DRAM Reads") + ")", "(" + prometheusCounters(S, "Persistent Memory Writes", false) + "+" + prometheusCounters(S, "Persistent Memory Reads", false) + ")/" + "(" + prometheusCounters(S, "DRAM Writes", false) + "+" + prometheusCounters(S, "DRAM Reads", false) +")"); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); auto stacked = [&] (const char * m, std::vector metrics, size_t s, const bool core = false) { const auto S = std::to_string(s); auto my_height = 3 * height / 2; auto panel = std::make_shared(0, y, width, my_height, "Socket" + S + " " + std::string(m), "stacked %", true); auto panel1 = std::make_shared(width, y, max_width - width, my_height, std::string("Current ") + m + " (%)"); y += my_height; for (auto & metric : metrics) { std::shared_ptr t; if (core) { t = createTarget(metric, influxDBCore_Aggregate_Core_Counters(S, metric), ""); } else { t = createTarget(metric, influxDBUncore_Uncore_Counters(S, metric), ""); } panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); }; for (size_t s = 0; type == InfluxDB && s < NumSockets; ++s) { stacked("Memory Request Ratio", {"Local Memory Request Ratio", "Remote Memory Request Ratio"}, s); } auto upi = [&](const std::string & m, const bool utilization) { for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto panel = std::make_shared(0, y, width, height, std::string("Socket") + S + " " + pcm->xPI() + " " + m, utilization?"%": "MByte/sec", false); std::shared_ptr panel1; if (utilization) panel1 = std::make_shared(width, y, max_width - width, height, std::string("Current Socket") + S + " UPI " + m + " (%)"); else panel1 = std::make_shared(width, y, max_width - width, height, std::string("Current Socket") + S + " UPI " + m + " (MByte/sec)"); y += height; const auto suffix = utilization ? "*100" : "/1048576"; for (size_t l = 0; l < NumUPILinksPerSocket; ++l) { const auto L = std::to_string(l); auto t = createTarget(pcm->xPI() + std::to_string(l), "mean(\\\"QPI/UPI Links_QPI Counters Socket " + S + "_" + m + " On Link " + L + "\\\")" + suffix, "rate(" + prometheusMetric(m) + "_On_Link_" + L + prometheusSystem(S) + interval + ")" + suffix); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); } }; for (auto &m : {"Utilization Outgoing Data And Non-Data Traffic", "Utilization Incoming Data Traffic"}) { upi(m, true); } for (auto & m : {"Outgoing Data And Non-Data Traffic", "Incoming Data Traffic"}) { upi(m, false); } auto cstate = [&] (const char * m, const char * tPrefix, const char * source) { auto my_height = 3 * height / 2; auto panel = std::make_shared(0, y, width, my_height, std::string(m) + " C-state residency", "stacked %", true); auto panel1 = std::make_shared(width, y, max_width - width, my_height, std::string("Current ") + m + " C-state residency (%)"); y += my_height; auto prometheusCStateExpression = [](const std::string& source, const size_t c) -> std::string { auto C = std::to_string(c); return std::string("100 * rate(RawCStateResidency{ instance=\\\"$node\\\", aggregate = \\\"system\\\", index = \\\"") + C + "\\\", source = \\\"" + source + "\\\" }" + interval + ") / ignoring(source, index) rate(Invariant_TSC{ instance=\\\"$node\\\", aggregate = \\\"system\\\" }" + interval + ")"; }; auto prometheusComputedCStateExpression = [&maxCState, &prometheusCStateExpression](const std::string& source, const size_t e) -> std::string { std::string result = "100"; for (size_t c = 0; c < maxCState + 1; ++c) { if (e != c) { result = result + " - (" + prometheusCStateExpression(source, c) + ") "; } } return result; }; for (size_t c = 0; c < maxCState + 1; ++c) { auto C = std::to_string(c); auto pExpr = prometheusCStateExpression(source, c); if ((std::string(source) == "core" && c == 1) || (std::string(source) == "uncore" && c == 0)) { pExpr = prometheusComputedCStateExpression(source, c); } auto t = createTarget("C" + C, std::string("mean(\\\"") + tPrefix + " Counters_CStateResidency[" + C + "]\\\")*100", pExpr); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); }; cstate("Core", "Core Aggregate_Energy", "core"); cstate("Package", "Uncore Aggregate_Uncore", "uncore"); auto derived = [&](const std::string & fullName, const std::string & shortName, const std::string & dividend, const std::string & divisor) { auto panel = std::make_shared(0, y, width, height, fullName, shortName, false); auto panel1 = std::make_shared(width, y, max_width - width, height, fullName); y += height; for (size_t s = 0; s < NumSockets; ++s) { const auto S = std::to_string(s); auto t = createTarget("Socket" + S, influxDBCore_Aggregate_Core_Counters(S, dividend) + "/" + influxDBCore_Aggregate_Core_Counters(S, divisor), prometheusCounters(S, dividend) + "/" + prometheusCounters(S, divisor)); panel->push(t); panel1->push(t); } auto t = createTarget("Total", influxDBCore_Aggregate_Core_Counters(dividend) + "/" + influxDBCore_Aggregate_Core_Counters(divisor), prometheusCounters(dividend) + "/" + prometheusCounters(divisor) ); panel->push(t); panel1->push(t); dashboard.push(panel); dashboard.push(panel1); }; derived("Instructions Per Cycle", "IPC", "Instructions Retired Any", "Clock Unhalted Thread"); for (size_t s = 0; type == InfluxDB && s < NumSockets; ++s) { stacked("Core Stalls", { "Frontend Bound", "Bad Speculation", "Backend Bound", "Retiring", "Fetch Latency Bound", "Fetch Bandwidth Bound", "Branch Misprediction Bound", "Machine Clears Bound", "Memory Bound", "Core Bound", "Heavy Operations Bound", "Light Operations Bound" }, s, true); } derived("Active Frequency Ratio", "AFREQ", "Clock Unhalted Thread", "Clock Unhalted Ref"); derived("L3 Cache Misses Per Instruction", "L3 MPI", "L3 Cache Misses", "Instructions Retired Any"); derived("L2 Cache Misses Per Instruction", "L2 MPI", "L2 Cache Misses", "Instructions Retired Any"); for (auto & m : {"Instructions Retired Any", "Clock Unhalted Thread", "L2 Cache Hits", "L2 Cache Misses", "L3 Cache Hits", "L3 Cache Misses"}) { scaled(m, "Million", "/1000000"); } if (pcm->getAccel() != ACCEL_NOCONFIG){ auto accelCounters = [&](const std::string & m) { auto panel = std::make_shared(0, y, width, height, accs->getAccelCounterName() + " " + m,"Byte/sec", false); std::shared_ptr panel1; panel1 = std::make_shared(width, y, max_width - width, height, std::string("Current ") +accs->getAccelCounterName() + " (Byte/sec)"); y += height; for (size_t s = 0; s < accs->getNumOfAccelDevs(); ++s) { const auto S = std::to_string(s); const auto suffix = "/1"; auto t = createTarget("Device "+S, "mean(\\\"Accelerators_"+accs->getAccelCounterName()+" Counters Device " + S + "_" + m + "\\\")" + suffix, "rate(" + prometheusMetric(accs->remove_string_inside_use(m)) + "{instance=\\\"$node\\\", aggregate=\\\"system\\\", source=\\\"accel\\\" ,"+accs->getAccelCounterName()+"device=\\\"" + S + "\\\"}" + interval + ")" + suffix); panel->push(t); panel1->push(t); } dashboard.push(panel); dashboard.push(panel1); }; for (int j =0;jgetNumberOfCounters();j++) { accelCounters(accs->getAccelIndexCounterName(j)); } } return dashboard(); } } // namespace pcm pcm-202502/src/dashboard.h000066400000000000000000000005061475730356400152730ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #pragma once #include namespace pcm { enum PCMDashboardType { InfluxDB, Prometheus, Prometheus_Default }; std::string getPCMDashboardJSON(const PCMDashboardType type, int ns = -1, int nu = -1, int nc = -1); } // namespace pcm pcm-202502/src/dashboardtest.cpp000066400000000000000000000003641475730356400165300ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #include "dashboard.h" #include int main() { std::cout << pcm::getPCMDashboardJSON(pcm::Prometheus, 2, 3, 10) << std::endl; return 0; } pcm-202502/src/debug.cpp000066400000000000000000000004231475730356400147630ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation namespace pcm { namespace debug { int currentDebugLevel = 0; void dyn_debug_level( int debugLevel ) { debug::currentDebugLevel = debugLevel; } } } // namespace pcm pcm-202502/src/debug.h000066400000000000000000000042321475730356400144320ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #pragma once #include #include #include #ifdef _MSC_VER #include #define ssize_t SSIZE_T #define __PRETTY_FUNCTION__ __FUNCSIG__ #endif namespace pcm { namespace debug { extern int currentDebugLevel; template void dyn_debug_output_helper( std::stringstream& out, T t ) { out << t << "\n"; } template void dyn_debug_output_helper( std::stringstream& out, const T & t, Args... args ) { out << t; dyn_debug_output_helper( out, args... ); } template void dyn_debug_output( std::ostream& out, LVL level, PF pretty_function, F file, L line, Args... args ) { std::stringstream ss; auto now = time(nullptr); ss << "DBG(" << std::dec << level << "): File '" << file << "', line '" << std::dec << line << "' :\n"; ss << "DBG(" << std::dec << level << "): " << pretty_function << ":\n"; ss << "DBG(" << std::dec << level << ") " << std::put_time( localtime(&now), "%F_%T: " ); // Next code line will continue printing on this output line dyn_debug_output_helper( ss, args... ); out << ss.str() << std::flush; } template void dyn_hex_table_output( int debugLevel, std::ostream& out, ssize_t len, T* inputBuffer_ ) { std::stringstream ss; if ( debug::currentDebugLevel < debugLevel ) return; for ( ssize_t i = 0; i < len; ++i ) { constexpr int DHTO_CHARS_PER_LINE = 16; ss << std::hex << std::internal << std::setfill('0') << std::setw(2) << std::abs(inputBuffer_[i]) << " "; if ( (i % DHTO_CHARS_PER_LINE) == (DHTO_CHARS_PER_LINE - 1) ) ss << "\n"; } out << ss.str() << std::flush; } void dyn_debug_level( int debugLevel ); } #define DBG( level, ... ) \ if ( debug::currentDebugLevel >= level ) \ debug::dyn_debug_output( std::cerr, level, __PRETTY_FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) } // namespace pcm pcm-202502/src/exceptions/000077500000000000000000000000001475730356400153535ustar00rootroot00000000000000pcm-202502/src/exceptions/unsupported_processor_exception.hpp000066400000000000000000000004361475730356400246340ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Steven Briscoe #include class UnsupportedProcessorException: public std::exception { virtual const char* what() const throw() { return "Unsupported processor"; } }; pcm-202502/src/favicon.ico.h000066400000000000000000000035451475730356400155500ustar00rootroot00000000000000signed char favicon_ico[] = { 0x00_uc, 0x00_uc, 0x01_uc, 0x00_uc, 0x01_uc, 0x00_uc, 0x10_uc, 0x10_uc, 0x02_uc, 0x00_uc, 0x01_uc, 0x00_uc, 0x01_uc, 0x00_uc, 0xb0_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x16_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x28_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x10_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x20_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x01_uc, 0x00_uc, 0x01_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x40_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x02_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x02_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0xff_uc, 0xff_uc, 0xff_uc, 0x00_uc, 0xff_uc, 0xff_uc, 0x00_uc, 0x00_uc, 0x7e_uc, 0x6e_uc, 0x00_uc, 0x00_uc, 0x7d_uc, 0xae_uc, 0x00_uc, 0x00_uc, 0x7d_uc, 0xae_uc, 0x00_uc, 0x00_uc, 0x7d_uc, 0xee_uc, 0x00_uc, 0x00_uc, 0x7d_uc, 0xee_uc, 0x00_uc, 0x00_uc, 0x7d_uc, 0xee_uc, 0x00_uc, 0x00_uc, 0x7d_uc, 0xee_uc, 0x00_uc, 0x00_uc, 0x0d_uc, 0xee_uc, 0x00_uc, 0x00_uc, 0x75_uc, 0xea_uc, 0x00_uc, 0x00_uc, 0x75_uc, 0xea_uc, 0x00_uc, 0x00_uc, 0x75_uc, 0xea_uc, 0x00_uc, 0x00_uc, 0x75_uc, 0xa4_uc, 0x00_uc, 0x00_uc, 0x75_uc, 0xa4_uc, 0x00_uc, 0x00_uc, 0x0e_uc, 0x6e_uc, 0x00_uc, 0x00_uc, 0xff_uc, 0xff_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc, 0x00_uc }; unsigned int favicon_ico_len = 198; pcm-202502/src/freegetopt/000077500000000000000000000000001475730356400153365ustar00rootroot00000000000000pcm-202502/src/freegetopt/ChangeLog000066400000000000000000000020141475730356400171050ustar00rootroot000000000000002003-10-25 Mark K. Kim {getopt*cbreak.org} * *: freegetopt 0.11 released. 2003-10-25 Mark K. Kim {getopt*cbreak.org} * ChangeLog: ChangeLog file added. * LICENSE: License file added. License is now officially BSD. * README: Updated to reflect the new license. CVS tracking data added. * getopt.h, getopt.c: License text added to the beginning of each file. CVS tracking data added. * test.c: License text added to the beginning of the file. CVS tracking data added. 2003-10-20 Mark K. Kim {getopt*cbreak.org} * *: Sourceforge project approval. Name changed to freegetopt. License is now officially BSD. 2003-09-29 Jon Higdon {jhigdon*nni.com} * test.c: GCC 3.x gives compound statement warning. Fixed. 2003.01.12 Mark K. Kim {getopt*cbreak.org} * *: Getopt 1.0 released! Supports only short options, but I think it's fully compatible with GNU getopt except for the value of optind while getopt is still running. The value of optind is same after getopt finishes running, however. pcm-202502/src/freegetopt/LICENSE000066400000000000000000000027701475730356400163510ustar00rootroot00000000000000Free Getopt Copyright (c)2002-2003 Mark K. Kim All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the original author of this software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pcm-202502/src/freegetopt/README000066400000000000000000000034601475730356400162210ustar00rootroot00000000000000Free Getopt ****************************************************************************** Please read the file LICENSE for the terms of use and distribution of this software. ****************************************************************************** "getopt" is a library that allows a parsing of arguments passed to a program. This is a useful library used in many software. There are many versions of the getopt library available, two popular versions being the BSD getopt and the GNU getopt. BSD getopt is somewhat old, dated, and isn't very user-friendly. The GNU getopt is great, except the user license doesn't let you statically link the library to a proprietary software. This is usually not a problem on modern operating systems that allow dynamic links to libraries, but sometimes you just gotta link the library statically for one reason or another. That's where Free Getopt steps in. Functionally, this getopt library is equivalent to GNU's getopt library (the short option version, not the long one) in almost every aspect. The only exception is how the "optind" variable increments. Apparently due to different algorithms used by my program and the GNU getopt, the "optind" changes quite differently between our two software. I personally find my algorithm to be quite elegant; I couldn't tell you about the GNU version since I never looked at its source. GNU's getopt_long support is in progress. This library was deliberately left in non-library (not in *.lib, *.so, or *.a) form because it's most likely to be statically-linked in various platforms, and linking it directly from source is probably the most straight-forward way to use the software in any platform. I hope you find this software useful. Mark K. Kim $Header: /cvsroot/freegetopt/freegetopt/README,v 1.2 2003/10/26 03:10:19 vindaci Exp $ pcm-202502/src/freegetopt/getopt.cpp000066400000000000000000000145421475730356400173520ustar00rootroot00000000000000/***************************************************************************** * getopt.c - competent and free getopt library. * $Header: /cvsroot/freegetopt/freegetopt/getopt.c,v 1.2 2003/10/26 03:10:20 vindaci Exp $ * * Copyright (c)2002-2003 Mark K. Kim * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the original author of this software nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "getopt.h" //static const char* ID = "$Id: getopt.c,v 1.2 2003/10/26 03:10:20 vindaci Exp $"; char* optarg = NULL; int optind = 0; int opterr = 1; int optopt = '?'; static char** prev_argv = NULL; /* Keep a copy of argv and argc to */ static int prev_argc = 0; /* tell if getopt params change */ static int argv_index = 0; /* Option we're checking */ static int argv_index2 = 0; /* Option argument we're checking */ static int opt_offset = 0; /* Index into compounded "-option" */ static int dashdash = 0; /* True if "--" option reached */ static int nonopt = 0; /* How many nonopts we've found */ static void increment_index() { /* Move onto the next option */ if(argv_index < argv_index2) { while(prev_argv[++argv_index] && prev_argv[argv_index][0] != '-' && argv_index < argv_index2+1); } else argv_index++; opt_offset = 1; } /* * Permutes argv[] so that the argument currently being processed is moved * to the end. */ static int permute_argv_once() { /* Movability check */ if(argv_index + nonopt >= prev_argc) return 1; /* Move the current option to the end, bring the others to front */ else { char* tmp = prev_argv[argv_index]; /* Move the data */ memmove(&prev_argv[argv_index], &prev_argv[argv_index+1], sizeof(char**) * (prev_argc - argv_index - 1)); prev_argv[prev_argc - 1] = tmp; nonopt++; return 0; } } int getopt(int argc, char** argv, char* optstr) { int c = 0; /* If we have new argv, reinitialize */ if(prev_argv != argv || prev_argc != argc) { /* Initialize variables */ prev_argv = argv; prev_argc = argc; argv_index = 1; argv_index2 = 1; opt_offset = 1; dashdash = 0; nonopt = 0; } /* Jump point in case we want to ignore the current argv_index */ getopt_top: /* Misc. initializations */ optarg = NULL; /* Dash-dash check */ if(argv[argv_index] && !strcmp(argv[argv_index], "--")) { dashdash = 1; increment_index(); } /* If we're at the end of argv, that's it. */ if(argv[argv_index] == NULL) { c = -1; } /* Are we looking at a string? Single dash is also a string */ else if(dashdash || argv[argv_index][0] != '-' || !strcmp(argv[argv_index], "-")) { /* If we want a string... */ if(optstr[0] == '-') { c = 1; optarg = argv[argv_index]; increment_index(); } /* If we really don't want it (we're in POSIX mode), we're done */ else if(optstr[0] == '+' || getenv("POSIXLY_CORRECT")) { c = -1; /* Everything else is a non-opt argument */ nonopt = argc - argv_index; } /* If we mildly don't want it, then move it back */ else { if(!permute_argv_once()) goto getopt_top; else c = -1; } } /* Otherwise we're looking at an option */ else { char* opt_ptr = NULL; /* Grab the option */ c = argv[argv_index][opt_offset++]; /* Is the option in the optstr? */ if(optstr[0] == '-') opt_ptr = strchr(optstr+1, c); else opt_ptr = strchr(optstr, c); /* Invalid argument */ if(!opt_ptr) { if(opterr) { fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c); } optopt = c; c = '?'; /* Move onto the next option */ increment_index(); } /* Option takes argument */ else if(opt_ptr[1] == ':') { /* ie, -oARGUMENT, -xxxoARGUMENT, etc. */ if(argv[argv_index][opt_offset] != '\0') { optarg = &argv[argv_index][opt_offset]; increment_index(); } /* ie, -o ARGUMENT (only if it's a required argument) */ else if(opt_ptr[2] != ':') { /* One of those "you're not expected to understand this" moment */ if(argv_index2 < argv_index) argv_index2 = argv_index; while(argv[++argv_index2] && argv[argv_index2][0] == '-'); optarg = argv[argv_index2]; /* Don't cross into the non-option argument list */ if(argv_index2 + nonopt >= prev_argc) optarg = NULL; /* Move onto the next option */ increment_index(); } else { /* Move onto the next option */ increment_index(); } /* In case we got no argument for an option with required argument */ if(optarg == NULL && opt_ptr[2] != ':') { optopt = c; c = '?'; if(opterr) { fprintf(stderr,"%s: option requires an argument -- %c\n", argv[0], optopt); } } } /* Option does not take argument */ else { /* Next argv_index */ if(argv[argv_index][opt_offset] == '\0') { increment_index(); } } } /* Calculate optind */ if(c == -1) { optind = argc - nonopt; } else { optind = argv_index; } return c; } /* vim:ts=3 */ pcm-202502/src/freegetopt/getopt.h000066400000000000000000000040331475730356400170110ustar00rootroot00000000000000/***************************************************************************** * getopt.h - competent and free getopt library. * $Header: /cvsroot/freegetopt/freegetopt/getopt.h,v 1.2 2003/10/26 03:10:20 vindaci Exp $ * * Copyright (c)2002-2003 Mark K. Kim * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the original author of this software nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #ifndef GETOPT_H_ #define GETOPT_H_ #ifdef __cplusplus extern "C" { #endif extern char* optarg; extern int optind; extern int opterr; extern int optopt; int getopt(int argc, char** argv, char* optstr); #ifdef __cplusplus } #endif #endif /* GETOPT_H_ */ /* vim:ts=3 */ pcm-202502/src/lspci.h000066400000000000000000000342251475730356400144630ustar00rootroot00000000000000#ifndef CPUCounters_LSPCI_H #define CPUCounters_LSPCI_H #include #include #include #include "cpucounters.h" #if defined(_MSC_VER) #define PCI_IDS_PATH "pci.ids" #define PCI_IDS_NOT_FOUND "pci.ids file is not available. Download it from" \ " https://raw.githubusercontent.com/pciutils/pciids/master/pci.ids." #elif defined (__FreeBSD__) || defined(__DragonFly__) #define PCI_IDS_PATH "/usr/local/share/pciids/pci.ids" #define PCI_IDS_NOT_FOUND "/usr/local/share/pciids/pci.ids file is not available." \ " Ensure that the \"pciids\" package is properly installed or download" \ " https://raw.githubusercontent.com/pciutils/pciids/master/pci.ids and" \ " copy it to the current directory." #else // different distributions put it in different places #define PCI_IDS_PATH "/usr/share/hwdata/pci.ids" #define PCI_IDS_PATH2 "/usr/share/misc/pci.ids" #define PCI_IDS_NOT_FOUND "/usr/share/hwdata/pci.ids file is not available." \ " Ensure that the \"hwdata\" package is properly installed or download" \ " https://raw.githubusercontent.com/pciutils/pciids/master/pci.ids and" \ " copy it to the current directory." #endif #define PCI_IDS_PATH2 "/usr/share/misc/pci.ids" namespace pcm { typedef uint32_t h_id; typedef uint32_t v_id; typedef std::map,uint64_t> ctr_data; typedef std::vector stack_content; typedef std::vector result_content; struct ccr_config { static constexpr uint64_t EVENT_SELECT_MASK = 0xFFULL; static constexpr uint8_t UMASK_SHIFT = 8; static constexpr uint64_t UMASK_MASK = 0xFFULL << UMASK_SHIFT; static constexpr uint8_t RESET_SHIFT = 17; static constexpr uint64_t RESET_MASK = 0x01ULL << RESET_SHIFT; static constexpr uint8_t EDGE_SHIFT = 18; static constexpr uint64_t EDGE_MASK = 0x01ULL << EDGE_SHIFT; static constexpr uint8_t OV_EN_SHIFT = 20; static constexpr uint64_t OV_EN_MASK = 0x01ULL << OV_EN_SHIFT; static constexpr uint8_t ENABLE_SHIFT = 22; static constexpr uint64_t ENABLE_MASK = 0x01ULL << ENABLE_SHIFT; static constexpr uint8_t INVERT_SHIFT = 23; static constexpr uint64_t INVERT_MASK = 0x01ULL << INVERT_SHIFT; static constexpr uint8_t THRESH_SHIFT = 24; static constexpr uint64_t THRESH_MASK = 0xFFFULL << THRESH_SHIFT; static constexpr uint8_t CH_MASK_SHIFT = 36; uint8_t FC_MASK_SHIFT; uint64_t FC_MASK; uint64_t CH_MASK; ccr_config(uint8_t fc_mask_shift, uint64_t ch_mask) : FC_MASK_SHIFT(fc_mask_shift), FC_MASK(0x07ULL << fc_mask_shift), CH_MASK(ch_mask << CH_MASK_SHIFT) {} }; class ccr { public: enum class ccr_type { skx, icx }; ccr(uint64_t &v, const ccr_type &type) : ccr_value(v), config(type == ccr_type::skx ? skx_ccrs : icx_ccrs) { } ccr() = delete; ~ccr() = default; uint64_t get_event_select() const { return (ccr_value & config.EVENT_SELECT_MASK); } void set_event_select(uint64_t value) { ccr_value = (ccr_value & ~config.EVENT_SELECT_MASK) | (value & config.EVENT_SELECT_MASK); } uint64_t get_umask() const { return (ccr_value & config.UMASK_MASK) >> config.UMASK_SHIFT; } void set_umask(uint64_t value) { ccr_value = (ccr_value & ~config.UMASK_MASK) | ((value << config.UMASK_SHIFT) & config.UMASK_MASK); } uint64_t get_reset() const { return (ccr_value & config.RESET_MASK) >> config.RESET_SHIFT; } void set_reset(uint64_t value) { ccr_value = (ccr_value & ~config.RESET_MASK) | ((value << config.RESET_SHIFT) & config.RESET_MASK); } uint64_t get_edge() const { return (ccr_value & config.EDGE_MASK) >> config.EDGE_SHIFT; } void set_edge(uint64_t value) { ccr_value = (ccr_value & ~config.EDGE_MASK) | ((value << config.EDGE_SHIFT) & config.EDGE_MASK); } uint64_t get_ov_en() const { return (ccr_value & config.OV_EN_MASK) >> config.OV_EN_SHIFT; } void set_ov_en(uint64_t value) { ccr_value = (ccr_value & ~config.OV_EN_MASK) | ((value << config.OV_EN_SHIFT) & config.OV_EN_MASK); } uint64_t get_enable() const { return (ccr_value & config.ENABLE_MASK) >> config.ENABLE_SHIFT; } void set_enable(uint64_t value) { ccr_value = (ccr_value & ~config.ENABLE_MASK) | ((value << config.ENABLE_SHIFT) & config.ENABLE_MASK); } uint64_t get_invert() const { return (ccr_value & config.INVERT_MASK) >> config.INVERT_SHIFT; } void set_invert(uint64_t value) { ccr_value = (ccr_value & ~config.INVERT_MASK) | ((value << config.INVERT_SHIFT) & config.INVERT_MASK); } uint64_t get_thresh() const { return (ccr_value & config.THRESH_MASK) >> config.THRESH_SHIFT; } void set_thresh(uint64_t value) { ccr_value = (ccr_value & ~config.THRESH_MASK) | ((value << config.THRESH_SHIFT) & config.THRESH_MASK); } uint64_t get_ch_mask() const { return (ccr_value & config.CH_MASK) >> config.CH_MASK_SHIFT; } void set_ch_mask(uint64_t value) { ccr_value = (ccr_value & ~config.CH_MASK) | ((value << config.CH_MASK_SHIFT) & config.CH_MASK); } uint64_t get_fc_mask() const { return (ccr_value & config.FC_MASK) >> config.FC_MASK_SHIFT; } void set_fc_mask(uint64_t value) { ccr_value = (ccr_value & ~config.FC_MASK) | ((value << config.FC_MASK_SHIFT) & config.FC_MASK); } uint64_t get_ccr_value() const { return ccr_value; } void set_ccr_value(uint64_t value) { ccr_value = value; } private: uint64_t &ccr_value; const ccr_config &config; static const ccr_config skx_ccrs; static const ccr_config icx_ccrs; }; const ccr_config ccr::skx_ccrs(44, 0xFFULL); const ccr_config ccr::icx_ccrs(48, 0xFFFULL); struct bdf { uint32_t domainno; uint8_t busno; uint8_t devno; uint8_t funcno; bdf() : domainno(0), busno(0), devno(0), funcno(0) {} bdf(uint32_t domain, uint8_t bus, uint8_t device, uint8_t function) : domainno(domain), busno(bus), devno(device), funcno(function) {} bdf(uint8_t bus, uint8_t device, uint8_t function) : domainno(0), busno(bus), devno(device), funcno(function) {} }; struct pci { bool exist = false; struct bdf bdf; union { struct { uint16_t vendor_id; uint16_t device_id; }; uint32_t offset_0; }; int8_t header_type; union { struct { uint8_t primary_bus_number; uint8_t secondary_bus_number; uint8_t subordinate_bus_number; uint8_t junk; }; uint32_t offset_18; }; union { struct { uint16_t link_ctrl; union { struct { uint16_t link_speed : 4; uint16_t link_width : 6; uint16_t undefined : 1; uint16_t link_trained : 1; }; uint16_t link_sta; }; }; uint32_t link_info; }; std::vector parts_no; std::vector child_pci_devs; pci() : exist(false), offset_0(0), header_type(0), offset_18(0), link_info(0), parts_no{}, child_pci_devs{} {} pci(uint32_t domain, uint8_t bus, uint8_t device, uint8_t function) : exist(false), bdf(domain, bus, device, function), offset_0(0), header_type(0), offset_18(0), link_info(0), parts_no{}, child_pci_devs{} {} pci(uint8_t bus, uint8_t device, uint8_t function) : exist(false), bdf(bus, device, function), offset_0(0), header_type(0), offset_18(0), link_info(0), parts_no{}, child_pci_devs{} {} pci(const struct bdf &address) : exist(false), bdf(address), offset_0(0), header_type(0), offset_18(0), link_info(0), parts_no{}, child_pci_devs{} {} bool hasChildDevices() const { return (child_pci_devs.size() != 0); } bool isIntelDevice() const { return (vendor_id == PCM_INTEL_PCI_VENDOR_ID); } }; struct iio_skx { struct { struct { struct pci root_pci_dev; /* single device represent root port */ std::vector child_pci_devs; /* Contain child switch and end-point devices */ } parts[4]{}; /* part 0, 1, 2, 3 */ uint8_t busno{}; /* holding busno for each IIO stack */ std::string stack_name{}; std::vector values{}; bool flipped = false; } stacks[6]; /* iio stack 0, 1, 2, 3, 4, 5 */ uint32_t socket_id{}; }; struct iio_bifurcated_part { int part_id{0}; /* single device represent root port */ struct pci root_pci_dev; /* Contain child switch and end-point devices */ std::vector child_pci_devs; }; struct iio_stack { std::vector parts{}; uint32_t iio_unit_id{}; std::string stack_name{}; std::vector values{}; bool flipped = false; /* holding busno for each IIO stack */ uint32_t domain{}; uint8_t busno{}; iio_stack() : iio_unit_id(0), stack_name(""), domain(0), busno(0) {} }; bool operator<(const iio_stack& lh, const iio_stack& rh) { return lh.iio_unit_id < rh.iio_unit_id; } struct iio_stacks_on_socket { std::vector stacks{}; uint32_t socket_id{}; }; bool operator<(const bdf &l, const bdf &r) { if (l.domainno < r.domainno) return true; if (l.domainno > r.domainno) return false; if (l.busno < r.busno) return true; if (l.busno > r.busno) return false; if (l.devno < r.devno) return true; if (l.devno > r.devno) return false; if (l.funcno < r.funcno) return true; if (l.funcno > r.funcno) return false; return false; // bdf == bdf }; void probe_capability_pci_express(struct pci *p, uint32_t cap_ptr) { struct cap { union { struct { uint8_t id; union { uint8_t next; uint8_t cap_ptr; }; uint16_t junk; }; uint32 dw0; }; } cap; uint32 value; PciHandleType h(0, p->bdf.busno, p->bdf.devno, p->bdf.funcno); h.read32(cap_ptr, &value); //Capability pointer cap.dw0 = value; if (cap.id != 0x10 && cap.next != 0x00) { probe_capability_pci_express(p, cap.cap_ptr); } else { if (cap.id == 0x10) { // We're in PCI express capability structure h.read32(cap_ptr+0x10, &value); p->link_info = value; } else { /*Finish recursive searching but cannot find PCI express capability structure*/ } } } bool probe_pci(struct pci *p) { uint32 value; p->exist = false; struct bdf *bdf = &p->bdf; if (PciHandleType::exists(bdf->domainno, bdf->busno, bdf->devno, bdf->funcno)) { PciHandleType h(bdf->domainno, bdf->busno, bdf->devno, bdf->funcno); // VID:DID h.read32(0x0, &value); // Invalid VID::DID if (value != (std::numeric_limits::max)()) { p->offset_0 = value; h.read32(0xc, &value); p->header_type = (value >> 16) & 0x7f; if (p->header_type == 0) { // Status register h.read32(0x4, &value); // Capability list == true if (value & 0x100000) { // Capability pointer h.read32(0x34, &value); probe_capability_pci_express(p, value); } } else if (p->header_type == 1) { h.read32(0x18, &value); p->offset_18 = value; } p->exist = true; } } return p->exist; } /* first : [vendorID] -> vencor name second : [vendorID][deviceID] -> device name */ typedef std::pair< std::map ,std::map< int, std::map > > PCIDB; void print_pci(struct pci p, const PCIDB & pciDB) { printf("Parent bridge info:"); printf("%x:%x.%d [%04x:%04x] %s %s %d P:%x S:%x S:%x ", p.bdf.busno, p.bdf.devno, p.bdf.funcno, p.vendor_id, p.device_id, (pciDB.first.count(p.vendor_id) > 0)?pciDB.first.at(p.vendor_id).c_str():"unknown vendor", (pciDB.second.count(p.vendor_id) > 0 && pciDB.second.at(p.vendor_id).count(p.device_id) > 0)?pciDB.second.at(p.vendor_id).at(p.device_id).c_str():"unknown device", p.header_type, p.primary_bus_number, p.secondary_bus_number, p.subordinate_bus_number); printf("Device info:"); printf("%x:%x.%d [%04x:%04x] %s %s %d Gen%d x%d\n", p.bdf.busno, p.bdf.devno, p.bdf.funcno, p.vendor_id, p.device_id, (pciDB.first.count(p.vendor_id) > 0)?pciDB.first.at(p.vendor_id).c_str():"unknown vendor", (pciDB.second.count(p.vendor_id) > 0 && pciDB.second.at(p.vendor_id).count(p.device_id) > 0)?pciDB.second.at(p.vendor_id).at(p.device_id).c_str():"unknown device", p.header_type, p.link_speed, p.link_width); } void load_PCIDB(PCIDB & pciDB) { std::ifstream in(PCI_IDS_PATH); std::string line, item; if (!in.is_open()) { #ifndef _MSC_VER // On Unix, try PCI_IDS_PATH2 in.open(PCI_IDS_PATH2); } if (!in.is_open()) { // On Unix, try the current directory if the default path failed in.open("pci.ids"); } if (!in.is_open()) { #endif std::cerr << PCI_IDS_NOT_FOUND << "\n"; return; } int vendorID = -1; while (std::getline(in, line)) { // Ignore any line starting with # if (line.size() == 0 || line[0] == '#') continue; if (line[0] == '\t' && line[1] == '\t') { // subvendor subdevice subsystem_name continue; } if (line[0] == '\t') { int deviceID = stoi(line.substr(1,4),0,16); //std::cout << vendorID << ";" << vendorName << ";" << deviceID << ";" << line.substr(7) << "\n"; pciDB.second[vendorID][deviceID] = line.substr(7); continue; } // vendor vendorID = stoi(line.substr(0,4),0,16); pciDB.first[vendorID] = line.substr(6); } } } // namespace pcm #endif pcm-202502/src/memoptest.cpp000066400000000000000000000053731475730356400157230ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // #include "cpucounters.h" #include #include #include #include #include #include #include using std::cout; inline double my_timestamp() { struct timeval tp; gettimeofday(&tp, NULL); return double(tp.tv_sec) + tp.tv_usec / 1000000.; } struct T { int key[1] = { 0 }; int data[3] = { 0, 0, 0 }; T() { } T(int a) { key[0] = a; } bool operator == (const T & k) const { return k.key[0] == key[0]; } }; template void write_intensive_task(Y * p, Y * e, int value) { __m128i i = _mm_set_epi32(value, value, value, value); #if 0 while (p != e) { *p = value; ++p; } #else while (p != e) { _mm_store_si128((__m128i *)p++, i); } #endif } template void stream_write_task(Y * p, Y * e, int value) { __m128i i = _mm_set_epi32(value, value, value, value); while (p != e) { _mm_stream_si128((__m128i *)p++, i); } } template void read_intensive_task(Y * p, Y * e, int value) { // cppcheck-suppress ignoredReturnValue std::find(p, e, -1); } int main(int argc, char * argv[]) { assert((argc > 1) && "Need operation type as parameter: 0 - read, 1 - write, 2 - streaming write "); int op = atoi(argv[1]); T * vector; int nelements = 1024 * 1024 * 1024 / sizeof(T); vector = new T[nelements]; int i = 0; cout << "Elements data size: " << sizeof(T) * nelements / 1024 << " KB\n"; for ( ; i < nelements; ++i) { vector[i].key[0] = 10; } double before_ts, after_ts; while (1) { before_ts = my_timestamp(); switch (op) { case 1: cout << "Writing memory\n"; break; case 0: cout << "Reading memory\n"; break; default: cout << "Streaming to memory\n"; } cout << std::flush; int niter = 32; i = niter; int r = rand(); while (i--) { switch (op) { case 1: write_intensive_task(vector, vector + nelements, r); break; case 0: read_intensive_task(vector, vector + nelements, r); break; default: stream_write_task(vector, vector + nelements, r); } after_ts = my_timestamp(); } cout << "Bandwidth: " << (sizeof(T) * nelements * niter) / ((after_ts - before_ts) * 1024 * 1024) << " MByte/sec\n" << std::flush; } deleteAndNullifyArray(vector); return 0; } pcm-202502/src/mmio.cpp000066400000000000000000000237451475730356400146520ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev, // Patrick Konsor // #include #include #ifndef _MSC_VER #include #endif #include #include #include "pci.h" #include "mmio.h" #ifndef _MSC_VER #include #include #endif #ifdef _MSC_VER #include #endif #include "utils.h" #include #include namespace pcm { #ifdef _MSC_VER class PCMPmem : public WinPmem { protected: virtual int load_driver_() { SYSTEM_INFO sys_info; SecureZeroMemory(&sys_info, sizeof(sys_info)); GetCurrentDirectory(MAX_PATH - 10, driver_filename); GetNativeSystemInfo(&sys_info); switch (sys_info.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_AMD64: _tcscat_s(driver_filename, MAX_PATH, TEXT("\\winpmem_x64.sys")); if (GetFileAttributes(driver_filename) == INVALID_FILE_ATTRIBUTES) { std::cerr << "ERROR: winpmem_x64.sys not found in current directory. Download it from https://github.com/Velocidex/WinPmem/blob/f044f340dd05658d026b0f293cdfa92876159872/kernel/binaries/winpmem_x64.sys .\n"; std::cerr << "ERROR: Memory bandwidth statistics will not be available.\n"; } break; case PROCESSOR_ARCHITECTURE_INTEL: _tcscat_s(driver_filename, MAX_PATH, TEXT("\\winpmem_x86.sys")); if (GetFileAttributes(driver_filename) == INVALID_FILE_ATTRIBUTES) { std::cerr << "ERROR: winpmem_x86.sys not found in current directory. Download it from https://github.com/Velocidex/WinPmem/blob/f044f340dd05658d026b0f293cdfa92876159872/kernel/binaries/winpmem_x86.sys .\n"; std::cerr << "ERROR: Memory bandwidth statistics will not be available.\n"; } break; default: return -1; } return 1; } virtual int write_crashdump_header_(struct PmemMemoryInfo * info) { return -1; } }; std::shared_ptr WinPmemMMIORange::pmem; Mutex WinPmemMMIORange::mutex; bool WinPmemMMIORange::writeSupported = false; WinPmemMMIORange::WinPmemMMIORange(uint64 baseAddr_, uint64 /* size_ */, bool readonly_) : startAddr(baseAddr_), readonly(readonly_) { mutex.lock(); if (pmem.get() == NULL) { pmem = std::make_shared(); pmem->install_driver(false); pmem->set_acquisition_mode(PMEM_MODE_IOSPACE); writeSupported = pmem->toggle_write_mode() >= 0; // since it is a global object enable write mode just in case someone needs it } mutex.unlock(); } MMIORange::MMIORange(const uint64 baseAddr_, const uint64 size_, const bool readonly_, const bool silent_, const int core) : silent(silent_) { auto hDriver = openMSRDriver(); if (hDriver != INVALID_HANDLE_VALUE) { DWORD reslength = 0; uint64 result = 0; const BOOL status = DeviceIoControl(hDriver, IO_CTL_MMAP_SUPPORT, NULL, 0, &result, sizeof(uint64), &reslength, NULL); CloseHandle(hDriver); if (status == TRUE && reslength == sizeof(uint64) && result == 1) { impl = std::make_shared(baseAddr_, size_, readonly_, core); return; } else { if (!silent) { std::cerr << "MSR.sys does not support mmap operations\n"; } } } if (core >= 0) { throw std::runtime_error("WinPmem does not support core affinity"); } impl = std::make_shared(baseAddr_, size_, readonly_); } OwnMMIORange::OwnMMIORange( const uint64 baseAddr_, const uint64 size_, const bool /* readonly_ */, const int core_) : core(core_) { hDriver = openMSRDriver(); MMAP_Request req{}; uint64 result = 0; DWORD reslength = 0; req.address.QuadPart = baseAddr_; req.size = size_; const BOOL status = DeviceIoControl(hDriver, IO_CTL_MMAP, &req, sizeof(MMAP_Request), &result, sizeof(uint64), &reslength, NULL); if (status == FALSE || result == 0) { std::cerr << "Error mapping address 0x" << std::hex << req.address.QuadPart << " with size " << std::dec << req.size << "\n"; throw std::runtime_error("OwnMMIORange error"); } mmapAddr = (char*)result; } uint32 OwnMMIORange::read32(uint64 offset) { CoreAffinityScope _(core); return *((uint32*)(mmapAddr + offset)); } uint64 OwnMMIORange::read64(uint64 offset) { CoreAffinityScope _(core); return *((uint64*)(mmapAddr + offset)); } void OwnMMIORange::write32(uint64 offset, uint32 val) { CoreAffinityScope _(core); *((uint32*)(mmapAddr + offset)) = val; } void OwnMMIORange::write64(uint64 offset, uint64 val) { CoreAffinityScope _(core); *((uint64*)(mmapAddr + offset)) = val; } OwnMMIORange::~OwnMMIORange() { MMAP_Request req{}; uint64 result = 0; DWORD reslength = 0; req.address.QuadPart = (LONGLONG)mmapAddr; req.size = 0; DeviceIoControl(hDriver, IO_CTL_MUNMAP, &req, sizeof(MMAP_Request), &result, sizeof(uint64), &reslength, NULL); CloseHandle(hDriver); } #elif __APPLE__ #include "PCIDriverInterface.h" MMIORange::MMIORange(const uint64 physical_address, const uint64 size_, const bool, const bool silent_, const int core_) : mmapAddr(NULL), size(size_), silent(silent_), core(core_) { if (core_ >= 0) { throw std::runtime_error("MMIORange on MacOSX does not support core affinity"); } if (size > 4096) { if (!silent) { std::cerr << "PCM Error: the driver does not support mapping of regions > 4KB\n"; } return; } if (physical_address) { PCIDriver_mapMemory((uint32_t)physical_address, (uint8_t **)&mmapAddr); } } uint32 MMIORange::read32(uint64 offset) { warnAlignment<4>("MMIORange::read32", silent, offset); uint32 val = 0; PCIDriver_readMemory32((uint8_t *)mmapAddr + offset, &val); return val; } uint64 MMIORange::read64(uint64 offset) { warnAlignment<8>("MMIORange::read64", silent, offset); uint64 val = 0; PCIDriver_readMemory64((uint8_t *)mmapAddr + offset, &val); return val; } void MMIORange::write32(uint64 offset, uint32 val) { std::cerr << "PCM Error: the driver does not support writing to MMIORange\n"; } void MMIORange::write64(uint64 offset, uint64 val) { std::cerr << "PCM Error: the driver does not support writing to MMIORange\n"; } MMIORange::~MMIORange() { if(mmapAddr) PCIDriver_unmapMemory((uint8_t *)mmapAddr); } #elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) MMIORange::MMIORange(const uint64 baseAddr_, const uint64 size_, const bool readonly_, const bool silent_, const int core_) : fd(-1), mmapAddr(NULL), size(size_), readonly(readonly_), silent(silent_), core(core_) { const int oflag = readonly ? O_RDONLY : O_RDWR; int handle = ::open("/dev/mem", oflag); if (handle < 0) { std::ostringstream strstr; strstr << "opening /dev/mem failed: errno is " << errno << " (" << strerror(errno) << ")\n"; if (!silent) { std::cerr << strstr.str(); } throw std::runtime_error(strstr.str()); } fd = handle; const int prot = readonly ? PROT_READ : (PROT_READ | PROT_WRITE); mmapAddr = (char *)mmap(NULL, size, prot, MAP_SHARED, fd, baseAddr_); if (mmapAddr == MAP_FAILED) { std::ostringstream strstr; strstr << "mmap failed: errno is " << errno << " (" << strerror(errno) << ")\n"; if (1 == errno) { strstr << "Try to add 'iomem=relaxed' parameter to the kernel boot command line and reboot.\n"; } if (!silent) { std::cerr << strstr.str(); } throw std::runtime_error(strstr.str()); } } uint32 MMIORange::read32(uint64 offset) { warnAlignment<4>("MMIORange::read32", silent, offset); CoreAffinityScope _(core); return *((uint32 *)(mmapAddr + offset)); } uint64 MMIORange::read64(uint64 offset) { warnAlignment<8>("MMIORange::read64", silent, offset); CoreAffinityScope _(core); return *((uint64 *)(mmapAddr + offset)); } void MMIORange::write32(uint64 offset, uint32 val) { warnAlignment<4>("MMIORange::write32", silent, offset); CoreAffinityScope _(core); if (readonly) { std::cerr << "PCM Error: attempting to write to a read-only MMIORange\n"; return; } *((uint32 *)(mmapAddr + offset)) = val; } void MMIORange::write64(uint64 offset, uint64 val) { warnAlignment<8>("MMIORange::write64", silent, offset); CoreAffinityScope _(core); if (readonly) { std::cerr << "PCM Error: attempting to write to a read-only MMIORange\n"; return; } *((uint64 *)(mmapAddr + offset)) = val; } MMIORange::~MMIORange() { if (mmapAddr) munmap(mmapAddr, size); if (fd >= 0) ::close(fd); } #endif void mmio_memcpy(void * dest_, const uint64 src, const size_t n, const bool checkFailures, const bool silent) { assert((src % sizeof(uint32)) == 0); assert((n % sizeof(uint32)) == 0); const uint64 end = src + n; const uint64 mapBegin = roundDownTo4K(src); const uint64 mapSize = roundUpTo4K(end) - mapBegin; uint32 * dest = (uint32 *)dest_; MMIORange range(mapBegin, mapSize, true, silent); for (uint64 i = src; i < end; i += sizeof(uint32), ++dest) { const auto value = range.read32(i - mapBegin); if (checkFailures && value == ~uint32(0)) { // a bad read std::ostringstream strstr; strstr << "Failed to read memory at 0x" << std::hex << i << std::dec << "\n"; if (!silent) { std::cerr << strstr.str(); } throw std::runtime_error(strstr.str()); } *dest = value; } } } // namespace pcm pcm-202502/src/mmio.h000066400000000000000000000120421475730356400143030ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012-2022, Intel Corporation // written by Roman Dementiev // Patrick Konsor // #pragma once /*! \file mmio.h \brief Interface to access memory mapped IO registers */ #include "types.h" #ifdef _MSC_VER #include "windows.h" #include "winpmem\winpmem.h" #include "Winmsrdriver\msrstruct.h" #else #include #endif #include "mutex.h" #include "utils.h" #include namespace pcm { class CoreAffinityScope // sets core affinity if core >= 0, nop otherwise { std::shared_ptr affinity{nullptr}; CoreAffinityScope(const CoreAffinityScope&) = delete; CoreAffinityScope& operator = (const CoreAffinityScope&) = delete; public: CoreAffinityScope(const int core) : affinity((core >= 0) ? std::make_shared(core) : nullptr) { } }; #ifdef _MSC_VER class MMIORangeInterface { public: virtual uint32 read32(uint64 offset) = 0; virtual uint64 read64(uint64 offset) = 0; virtual void write32(uint64 offset, uint32 val) = 0; virtual void write64(uint64 offset, uint64 val) = 0; virtual ~MMIORangeInterface() {} }; class WinPmemMMIORange : public MMIORangeInterface { static std::shared_ptr pmem; static Mutex mutex; static bool writeSupported; uint64 startAddr; template void writeInternal(uint64 offset, T val) { if (!writeSupported) { std::cerr << "PCM Error: MMIORange writes are not supported by the driver\n"; return; } if (readonly) { std::cerr << "PCM Error: attempting to write to a read-only MMIORange\n"; return; } mutex.lock(); pmem->write(startAddr + offset, val); mutex.unlock(); } template void readInternal(uint64 offset, T & res) { mutex.lock(); pmem->read(startAddr + offset, res); mutex.unlock(); } const bool readonly; public: WinPmemMMIORange(uint64 baseAddr_, uint64 size_, bool readonly_ = true); uint32 read32(uint64 offset) { uint32 result = 0; readInternal(offset, result); return result; } uint64 read64(uint64 offset) { uint64 result = 0; readInternal(offset, result); return result; } void write32(uint64 offset, uint32 val) { writeInternal(offset, val); } void write64(uint64 offset, uint64 val) { writeInternal(offset, val); } }; class OwnMMIORange : public MMIORangeInterface { HANDLE hDriver; char * mmapAddr; const int core; OwnMMIORange(const OwnMMIORange&) = delete; OwnMMIORange& operator = (const OwnMMIORange&) = delete; public: OwnMMIORange( const uint64 baseAddr_, const uint64 size_, const bool readonly_ = true, const int core_ = -1); uint32 read32(uint64 offset); uint64 read64(uint64 offset); void write32(uint64 offset, uint32 val); void write64(uint64 offset, uint64 val); ~OwnMMIORange(); }; class MMIORange { std::shared_ptr impl; const bool silent; MMIORange(const MMIORange &) = delete; MMIORange & operator = (const MMIORange &) = delete; public: MMIORange( const uint64 baseAddr_, const uint64 size_, const bool readonly_ = true, const bool silent_ = false, const int core = -1); uint32 read32(uint64 offset) { warnAlignment<4>("MMIORange::read32", silent, offset); return impl->read32(offset); } uint64 read64(uint64 offset) { warnAlignment<8>("MMIORange::read64", silent, offset); return impl->read64(offset); } void write32(uint64 offset, uint32 val) { warnAlignment<4>("MMIORange::write32", silent, offset); impl->write32(offset, val); } void write64(uint64 offset, uint64 val) { warnAlignment<8>("MMIORange::write64", silent, offset); impl->write64(offset, val); } }; #elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) class MMIORange { #ifndef __APPLE__ int32 fd; #endif char * mmapAddr; const uint64 size; #ifndef __APPLE__ const bool readonly; #endif const bool silent; const int core; MMIORange(const MMIORange &) = delete; MMIORange & operator = (const MMIORange &) = delete; public: MMIORange( const uint64 baseAddr_, const uint64 size_, const bool readonly_ = true, const bool silent_ = false, const int core_ = -1); uint32 read32(uint64 offset); uint64 read64(uint64 offset); void write32(uint64 offset, uint32 val); void write64(uint64 offset, uint64 val); ~MMIORange(); }; #endif void mmio_memcpy(void * dest, const uint64 src, const size_t n, const bool checkFailures, const bool silent = false); } // namespace pcm pcm-202502/src/msr.cpp000066400000000000000000000143101475730356400144760ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // Austen Ott // Jim Harris (FreeBSD) #include #include #include #include #ifndef _MSC_VER #include #endif #include "types.h" #include "msr.h" #include "utils.h" #include #ifdef _MSC_VER #include #include "utils.h" #include "Winmsrdriver\msrstruct.h" #include "winring0/OlsApiInitExt.h" #endif #if defined(__FreeBSD__) || defined(__DragonFly__) #include #include #endif #include namespace pcm { #ifdef _MSC_VER extern HMODULE hOpenLibSys; // here comes an implementation for Windows MsrHandle::MsrHandle(uint32 cpu) : cpu_id(cpu) { hDriver = openMSRDriver(); if (hDriver == INVALID_HANDLE_VALUE && hOpenLibSys == NULL) throw std::exception(); } MsrHandle::~MsrHandle() { if (hDriver != INVALID_HANDLE_VALUE) CloseHandle(hDriver); } int32 MsrHandle::write(uint64 msr_number, uint64 value) { if (hDriver != INVALID_HANDLE_VALUE) { MSR_Request req; ULONG64 result; DWORD reslength = 0; req.core_id = cpu_id; req.msr_address = msr_number; req.write_value = value; BOOL status = DeviceIoControl(hDriver, IO_CTL_MSR_WRITE, &req, sizeof(MSR_Request), &result, sizeof(uint64), &reslength, NULL); assert(status && "Error in DeviceIoControl"); return status ? sizeof(uint64) : 0; } cvt_ds cvt; cvt.ui64 = value; ThreadGroupTempAffinity affinity(cpu_id); DWORD status = Wrmsr((DWORD)msr_number, cvt.ui32.low, cvt.ui32.high); return status ? sizeof(uint64) : 0; } int32 MsrHandle::read(uint64 msr_number, uint64 * value) { if (hDriver != INVALID_HANDLE_VALUE) { MSR_Request req; // ULONG64 result; DWORD reslength = 0; req.core_id = cpu_id; req.msr_address = msr_number; BOOL status = DeviceIoControl(hDriver, IO_CTL_MSR_READ, &req, sizeof(MSR_Request), value, sizeof(uint64), &reslength, NULL); assert(status && "Error in DeviceIoControl"); return (int32)reslength; } cvt_ds cvt; cvt.ui64 = 0; ThreadGroupTempAffinity affinity(cpu_id); DWORD status = Rdmsr((DWORD)msr_number, &(cvt.ui32.low), &(cvt.ui32.high)); if (status) *value = cvt.ui64; return status ? sizeof(uint64) : 0; } #elif __APPLE__ // OSX Version MSRAccessor * MsrHandle::driver = NULL; int MsrHandle::num_handles = 0; MsrHandle::MsrHandle(uint32 cpu) { cpu_id = cpu; if (!driver) { driver = new MSRAccessor(); MsrHandle::num_handles = 1; } else { MsrHandle::num_handles++; } } MsrHandle::~MsrHandle() { MsrHandle::num_handles--; if (MsrHandle::num_handles == 0) { deleteAndNullify(driver); } } int32 MsrHandle::write(uint64 msr_number, uint64 value) { return driver->write(cpu_id, msr_number, value); } int32 MsrHandle::read(uint64 msr_number, uint64 * value) { return driver->read(cpu_id, msr_number, value); } int32 MsrHandle::buildTopology(uint32 num_cores, void * ptr) { return driver->buildTopology(num_cores, ptr); } uint32 MsrHandle::getNumInstances() { return driver->getNumInstances(); } uint32 MsrHandle::incrementNumInstances() { return driver->incrementNumInstances(); } uint32 MsrHandle::decrementNumInstances() { return driver->decrementNumInstances(); } #elif defined(__FreeBSD__) || defined(__DragonFly__) MsrHandle::MsrHandle(uint32 cpu) : fd(-1), cpu_id(cpu) { char path[200]; snprintf(path, 200, "/dev/cpuctl%d", cpu); int handle = ::open(path, O_RDWR); if (handle < 0) throw std::exception(); fd = handle; } MsrHandle::~MsrHandle() { if (fd >= 0) ::close(fd); } int32 MsrHandle::write(uint64 msr_number, uint64 value) { cpuctl_msr_args_t args; int ret; args.msr = msr_number; args.data = value; ret = ::ioctl(fd, CPUCTL_WRMSR, &args); if (ret) return ret; return sizeof(value); } int32 MsrHandle::read(uint64 msr_number, uint64 * value) { cpuctl_msr_args_t args; int32 ret; args.msr = msr_number; ret = ::ioctl(fd, CPUCTL_RDMSR, &args); if (ret) return ret; *value = args.data; return sizeof(*value); } #else // here comes a Linux version bool noMSRMode() { static int noMSR = -1; if (noMSR < 0) { noMSR = (safe_getenv("PCM_NO_MSR") == std::string("1")) ? 1 : 0; } return 1 == noMSR; } MsrHandle::MsrHandle(uint32 cpu) : fd(-1), cpu_id(cpu) { if (noMSRMode()) return; constexpr auto allowWritesPath = "/sys/module/msr/parameters/allow_writes"; static bool writesEnabled = false; if (writesEnabled == false) { if (readSysFS(allowWritesPath, true).length() > 0) { writeSysFS(allowWritesPath, "on", false); } writesEnabled = true; } char * path = new char[200]; if (!path) throw std::runtime_error("Allocation of 200 bytes failed."); snprintf(path, 200, "/dev/cpu/%d/msr", cpu); int handle = ::open(path, O_RDWR); if (handle < 0) { // try Android msr device path snprintf(path, 200, "/dev/msr%d", cpu); handle = ::open(path, O_RDWR); } deleteAndNullifyArray(path); if (handle < 0) { std::cerr << "PCM Error: can't open MSR handle for core " << cpu << " (" << strerror(errno) << ")\n"; std::cerr << "Try no-MSR mode by setting env variable PCM_NO_MSR=1\n"; throw std::exception(); } fd = handle; } MsrHandle::~MsrHandle() { if (fd >= 0) ::close(fd); } int32 MsrHandle::write(uint64 msr_number, uint64 value) { #if 0 static std::mutex m; std::lock_guard g(m); std::cout << "DEBUG: writing MSR 0x" << std::hex << msr_number << " value 0x" << value << " on cpu " << std::dec << cpu_id << std::endl; #endif if (fd < 0) return 0; return ::pwrite(fd, (const void *)&value, sizeof(uint64), msr_number); } int32 MsrHandle::read(uint64 msr_number, uint64 * value) { if (fd < 0) return 0; return ::pread(fd, (void *)value, sizeof(uint64), msr_number); } #endif #ifndef __linux__ bool noMSRMode() { return false; } #endif } // namespace pcm pcm-202502/src/msr.h000066400000000000000000000056321475730356400141520ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // Austen Ott #ifndef CPUCounters_MSR_H #define CPUCounters_MSR_H /*! \file msr.h \brief Low level interface to access hardware model specific registers Implemented and tested for Linux and 64-bit Windows 7 */ #include "types.h" #ifdef _MSC_VER #include "windows.h" #elif __APPLE__ #include #endif #include "mutex.h" #include namespace pcm { bool noMSRMode(); class MsrHandle { #ifdef _MSC_VER HANDLE hDriver; #elif __APPLE__ static MSRAccessor * driver; static int num_handles; #else int32 fd; #endif uint32 cpu_id; MsrHandle(); // forbidden MsrHandle(const MsrHandle &); // forbidden MsrHandle & operator = (const MsrHandle &); // forbidden public: MsrHandle(uint32 cpu); int32 read(uint64 msr_number, uint64 * value); int32 write(uint64 msr_number, uint64 value); int32 getCoreId() { return (int32)cpu_id; } #ifdef __APPLE__ int32 buildTopology(uint32 num_cores, void *); uint32 getNumInstances(); uint32 incrementNumInstances(); uint32 decrementNumInstances(); #endif virtual ~MsrHandle(); }; class SafeMsrHandle { std::shared_ptr pHandle; Mutex mutex; SafeMsrHandle(const SafeMsrHandle &); // forbidden SafeMsrHandle & operator = (const SafeMsrHandle &); // forbidden public: SafeMsrHandle() { } SafeMsrHandle(uint32 core_id) : pHandle(new MsrHandle(core_id)) { } int32 read(uint64 msr_number, uint64 * value) { if (pHandle) return pHandle->read(msr_number, value); *value = 0; return (int32)sizeof(uint64); } int32 write(uint64 msr_number, uint64 value) { if (pHandle) return pHandle->write(msr_number, value); return (int32)sizeof(uint64); } int32 getCoreId() { if (pHandle) return pHandle->getCoreId(); throw std::runtime_error("Core is offline"); } void lock() { mutex.lock(); } void unlock() { mutex.unlock(); } #ifdef __APPLE__ int32 buildTopology(uint32 num_cores, void * p) { if (pHandle) return pHandle->buildTopology(num_cores, p); throw std::exception(); } uint32 getNumInstances() { if (pHandle) return pHandle->getNumInstances(); throw std::exception(); } uint32 incrementNumInstances() { if (pHandle) return pHandle->incrementNumInstances(); throw std::exception(); } uint32 decrementNumInstances() { if (pHandle) return pHandle->decrementNumInstances(); throw std::exception(); } #endif virtual ~SafeMsrHandle() { } }; } // namespace pcm #endif pcm-202502/src/msrtest.cpp000066400000000000000000000052121475730356400153770ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // Austen Ott #include #include #include #include "msr.h" #define NUM_CORES 16 int main() { uint32 i = 0; uint32 res; MsrHandle * cpu_msr[NUM_CORES]; for (i = 0; i < NUM_CORES; ++i) { cpu_msr[i] = new MsrHandle(i); assert(cpu_msr[i]); FixedEventControlRegister ctrl_reg; res = cpu_msr[i]->read(IA32_CR_FIXED_CTR_CTRL, &ctrl_reg.value); assert(res >= 0); ctrl_reg.fields.os0 = 1; ctrl_reg.fields.usr0 = 1; ctrl_reg.fields.any_thread0 = 0; ctrl_reg.fields.enable_pmi0 = 0; ctrl_reg.fields.os1 = 1; ctrl_reg.fields.usr1 = 1; ctrl_reg.fields.any_thread1 = 0; ctrl_reg.fields.enable_pmi1 = 0; ctrl_reg.fields.os2 = 1; ctrl_reg.fields.usr2 = 1; ctrl_reg.fields.any_thread2 = 0; ctrl_reg.fields.enable_pmi2 = 0; res = cpu_msr[i]->write(IA32_CR_FIXED_CTR_CTRL, ctrl_reg.value); assert(res >= 0); // start counting uint64 value = (1ULL << 0) + (1ULL << 1) + (1ULL << 2) + (1ULL << 3) + (1ULL << 32) + (1ULL << 33) + (1ULL << 34); res = cpu_msr[i]->write(IA32_CR_PERF_GLOBAL_CTRL, value); assert(res >= 0); } uint64 counters_before[NUM_CORES][3]; uint64 counters_after[NUM_CORES][3]; for (i = 0; i < NUM_CORES; ++i) { res = cpu_msr[i]->read(INST_RETIRED_ADDR, &counters_before[i][0]); assert(res >= 0); res = cpu_msr[i]->read(CPU_CLK_UNHALTED_THREAD_ADDR, &counters_before[i][1]); assert(res >= 0); res = cpu_msr[i]->read(CPU_CLK_UNHALTED_REF_ADDR, &counters_before[i][2]); assert(res >= 0); } //sleep for some time ::sleep(1); for (i = 0; i < NUM_CORES; ++i) { res = cpu_msr[i]->read(INST_RETIRED_ADDR, &counters_after[i][0]); assert(res >= 0); res = cpu_msr[i]->read(CPU_CLK_UNHALTED_THREAD_ADDR, &counters_after[i][1]); assert(res >= 0); res = cpu_msr[i]->read(CPU_CLK_UNHALTED_REF_ADDR, &counters_after[i][2]); assert(res >= 0); } for (i = 0; i < NUM_CORES; ++i) deleteAndNullify(cpu_msr[i]); for (i = 0; i < NUM_CORES; ++i) std::cout << "Core " << i << "\t Instructions: " << (counters_after[i][0] - counters_before[i][0]) << "\t Cycles: " << (counters_after[i][1] - counters_before[i][1]) << "\t IPC: " << double(counters_after[i][0] - counters_before[i][0]) / double(counters_after[i][1] - counters_before[i][1]) << "\n"; } pcm-202502/src/mutex.h000066400000000000000000000027651475730356400145170ustar00rootroot00000000000000#ifndef MUTEX_HEADER_ #define MUTEX_HEADER_ #ifdef _MSC_VER #include #else #include #endif #include namespace pcm { class Mutex { Mutex(const Mutex&) = delete; Mutex& operator = (const Mutex&) = delete; #ifdef _MSC_VER HANDLE mutex_; #else pthread_mutex_t mutex_; #endif public: Mutex() { #ifdef _MSC_VER mutex_ = CreateMutex(NULL, FALSE, NULL); #else pthread_mutex_init(&mutex_, NULL); #endif } virtual ~Mutex() { #ifdef _MSC_VER CloseHandle(mutex_); #else if (pthread_mutex_destroy(&mutex_) != 0) std::cerr << "pthread_mutex_destroy failed\n"; #endif } void lock() { #ifdef _MSC_VER WaitForSingleObject(mutex_, INFINITE); #else if (pthread_mutex_lock(&mutex_) != 0) std::cerr << "pthread_mutex_lock failed\n";; #endif } void unlock() { #ifdef _MSC_VER ReleaseMutex(mutex_); #else if(pthread_mutex_unlock(&mutex_) != 0) std::cerr << "pthread_mutex_unlock failed\n"; #endif } class Scope { Mutex & m; Scope() = delete; Scope(const Scope &) = delete; Scope & operator = (const Scope &) = delete; public: Scope(Mutex & m_) : m(m_) { m.lock(); } ~Scope() { m.unlock(); } }; }; } #endif pcm-202502/src/opCode-6-106.txt000066400000000000000000000114671475730356400156240ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 (2nd x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 (4th x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 (2nd x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 (4th x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 (2nd x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 (4th x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 (2nd x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 (4th x4) # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 (1st x16/x8/x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 (2nd x4) ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 (2nd x8/3rd x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 (4th x4) ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 (1st x16/x8/x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 (2nd x4) ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 (2nd x8/3rd x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 (4th x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 (1st x16/x8/x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 (2nd x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 (2nd x8/3rd x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 (4th x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 (1st x16/x8/x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 (2nd x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 (2nd x8/3rd x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 (4th x4) # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=4K Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x40,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-134.txt000066400000000000000000000114661475730356400156240ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 (2nd x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 (4th x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 (2nd x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 (4th x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 (2nd x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 (4th x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 (2nd x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 (4th x4) # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 (1st x16/x8/x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 (2nd x4) ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 (2nd x8/3rd x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 (4th x4) ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 (1st x16/x8/x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 (2nd x4) ctr=2,ev_sel=0x83,umask=0x80,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 (2nd x8/3rd x4) ctr=3,ev_sel=0x83,umask=0x80,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 (4th x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 (1st x16/x8/x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 (2nd x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 (2nd x8/3rd x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 (4th x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 (1st x16/x8/x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 (2nd x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 (2nd x8/3rd x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 (4th x4) # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=4K Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x40,en=1,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Totalpcm-202502/src/opCode-6-143-accel.txt000066400000000000000000000025401475730356400166620ustar00rootroot00000000000000# IAA events ctr=0,ev_sel=0x1,ev_cat=0x1,en=1,multiplier=32,divider=1,hname=IAA,vname=Inbound_BW(Bps) ctr=1,ev_sel=0x2,ev_cat=0x1,en=1,multiplier=512,divider=1,hname=IAA,vname=Outbound_BW(Bps) ctr=2,ev_sel=0x5,ev_cat=0x0,en=1,multiplier=1,divider=1,hname=IAA,vname=ShareWQ_ReqNb ctr=3,ev_sel=0x10,ev_cat=0x0,en=1,multiplier=1,divider=1,hname=IAA,vname=DedicateWQ_ReqNb # DSA events ctr=0,ev_sel=0x1,ev_cat=0x1,en=1,multiplier=32,divider=1,hname=DSA,vname=Inbound_BW(Bps) ctr=1,ev_sel=0x2,ev_cat=0x1,en=1,multiplier=32,divider=1,hname=DSA,vname=Outbound_BW(Bps) ctr=2,ev_sel=0x5,ev_cat=0x0,en=1,multiplier=1,divider=1,hname=DSA,vname=ShareWQ_ReqNb ctr=3,ev_sel=0x10,ev_cat=0x0,en=1,multiplier=1,divider=1,hname=DSA,vname=DedicateWQ_ReqNb # QAT events ctr=0,ev_sel=0x6,ev_cat=0x1,en=1,multiplier=1048576,divider=1,hname=QAT,vname=Inbound_BW(Bps) ctr=1,ev_sel=0x7,ev_cat=0x1,en=1,multiplier=1048576,divider=1,hname=QAT,vname=Outbound_BW(Bps) ctr=2,ev_sel=0xB,ev_cat=0x1,en=1,multiplier=1,divider=1,hname=QAT,vname=util_comp0(%) ctr=3,ev_sel=0xC,ev_cat=0x1,en=1,multiplier=1,divider=1,hname=QAT,vname=util_decomp0(%) ctr=4,ev_sel=0xD,ev_cat=0x1,en=1,multiplier=1,divider=1,hname=QAT,vname=util_decomp1(%) ctr=5,ev_sel=0xE,ev_cat=0x1,en=1,multiplier=1,divider=1,hname=QAT,vname=util_decomp2(%) ctr=6,ev_sel=0xF,ev_cat=0x1,en=1,multiplier=1,divider=1,hname=QAT,vname=util_xlt0(%) pcm-202502/src/opCode-6-143.txt000066400000000000000000000103411475730356400156130ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0x83,umask=0x80,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 ctr=2,ev_sel=0x83,umask=0x80,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 ctr=2,ev_sel=0x83,umask=0x80,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 ctr=2,ev_sel=0x83,umask=0x80,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=256T Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-173.txt000066400000000000000000000103311475730356400156150ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=256T Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-174.txt000066400000000000000000000103311475730356400156160ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=256T Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-175.txt000066400000000000000000000103311475730356400156170ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=256T Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-182.txt000066400000000000000000000103311475730356400156150ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=256T Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-207.txt000066400000000000000000000103411475730356400156140ustar00rootroot00000000000000#Clockticks #ctr=0,ev_sel=0x1,umask=0x0,en=1,ch_mask=0,fc_mask=0x0,multiplier=1,divider=1,hname=Clockticks,vname=Total # Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part0 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part1 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part2 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part3 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part4 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part5 ctr=0,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part6 ctr=1,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB write,vname=Part7 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part0 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part1 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part2 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part3 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part4 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part5 ctr=0,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part6 ctr=1,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=IB read,vname=Part7 # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0x83,umask=0x80,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part0 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part1 ctr=2,ev_sel=0x83,umask=0x80,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part2 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part3 ctr=2,ev_sel=0x83,umask=0x80,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part4 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part5 ctr=2,ev_sel=0x83,umask=0x80,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part6 ctr=3,ev_sel=0x83,umask=0x80,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB read,vname=Part7 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part0 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part1 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part2 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part3 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part4 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part5 ctr=2,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part6 ctr=3,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,divider=1,hname=OB write,vname=Part7 # IOMMU events ctr=0,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Lookup,vname=Total ctr=1,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=Ctxt Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=256T Cache Hit,vname=Total ctr=0,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=512G Cache Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=1G Cache Hit,vname=Total ctr=2,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=2M Cache Hit,vname=Total ctr=3,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,divider=1,hname=IOMMU Mem Access,vname=Total pcm-202502/src/opCode-6-85.txt000066400000000000000000000055161475730356400155500ustar00rootroot00000000000000# Inbound (PCIe device DMA into system) payload events ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB write (bytes),vname=Part0 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB write (bytes),vname=Part1 (2nd x4) ctr=0,ev_sel=0x83,umask=0x1,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB write (bytes),vname=Part2 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x1,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB write (bytes),vname=Part3 (4th x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=IB read (bytes),vname=Part0 (1st x16/x8/x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=IB read (bytes),vname=Part1 (2nd x4) ctr=0,ev_sel=0x83,umask=0x4,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=IB read (bytes),vname=Part2 (2nd x8/3rd x4) ctr=1,ev_sel=0x83,umask=0x4,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=IB read (bytes),vname=Part3 (4th x4) # Outbound (CPU MMIO to the PCIe device) payload events ctr=2,ev_sel=0xc0,umask=0x4,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB read (bytes),vname=Part0 (1st x16/x8/x4) ctr=3,ev_sel=0xc0,umask=0x4,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB read (bytes),vname=Part1 (2nd x4) ctr=2,ev_sel=0xc0,umask=0x4,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB read (bytes),vname=Part2 (2nd x8/3rd x4) ctr=3,ev_sel=0xc0,umask=0x4,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB read (bytes),vname=Part3 (4th x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=4,divider=1,hname=OB write (bytes),vname=Part0 (1st x16/x8/x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=2,fc_mask=0x7,multiplier=4,divider=1,hname=OB write (bytes),vname=Part1 (2nd x4) ctr=2,ev_sel=0xc0,umask=0x1,en=1,ch_mask=4,fc_mask=0x7,multiplier=4,divider=1,hname=OB write (bytes),vname=Part2 (2nd x8/3rd x4) ctr=3,ev_sel=0xc0,umask=0x1,en=1,ch_mask=8,fc_mask=0x7,multiplier=4,divider=1,hname=OB write (bytes),vname=Part3 (4th x4) # VTd events ctr=0,ev_sel=0x41,umask=0x1,en=1,ch_mask=1,fc_mask=0x7,multiplier=1,divider=1,hname=IOTLB Hit,vname=Total ctr=1,ev_sel=0x41,umask=0x20,en=1,ch_mask=1,fc_mask=0x7,multiplier=1,divider=1,hname=IOTLB Miss,vname=Total ctr=2,ev_sel=0x41,umask=0x02,en=1,ch_mask=4,fc_mask=0x7,multiplier=1,divider=1,hname=VT-d CTXT Miss,vname=Total ctr=3,ev_sel=0x41,umask=0x04,en=1,ch_mask=4,fc_mask=0x7,multiplier=1,divider=1,hname=VT-d L1 Miss,vname=Total ctr=0,ev_sel=0x41,umask=0x08,en=1,ch_mask=4,fc_mask=0x7,multiplier=1,divider=1,hname=VT-d L2 Miss,vname=Total ctr=1,ev_sel=0x41,umask=0x10,en=1,ch_mask=1,fc_mask=0x7,multiplier=1,divider=1,hname=VT-d L3 Miss,vname=Total ctr=2,ev_sel=0x84,umask=0x04,en=1,ch_mask=0x10,fc_mask=0x7,multiplier=1,divider=1,hname=VT-d Mem Read,vname=Total pcm-202502/src/pci.cpp000066400000000000000000000465331475730356400144640ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev, // Pat Fay // Austen Ott // Jim Harris (FreeBSD) #include #include #include #include #include #include #include "pci.h" #ifndef _MSC_VER #include #include #include #endif #ifdef _MSC_VER #include #include "Winmsrdriver\msrstruct.h" #include "winring0/OlsDef.h" #include "winring0/OlsApiInitExt.h" #endif #include "utils.h" #if defined (__FreeBSD__) || defined(__DragonFly__) #include #endif namespace pcm { #ifdef _MSC_VER extern HMODULE hOpenLibSys; static char * nonZeroGroupErrMsg = "Non-zero PCI group segments are not supported in Winring0 driver, make sure MSR.sys driver can be used."; PciHandle::PciHandle(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) : hDriver(openMSRDriver()), bus((groupnr_ << 8) | bus_), device(device_), function(function_), pciAddress(PciBusDevFunc(bus_, device_, function_)) { DBG(3, "Creating PCI Config space handle at g:b:d:f ", groupnr_, ":", bus_, ":", device_, ":", function_); if (groupnr_ != 0 && hDriver == INVALID_HANDLE_VALUE) { std::cerr << nonZeroGroupErrMsg << '\n'; throw std::runtime_error(nonZeroGroupErrMsg); } if (hDriver == INVALID_HANDLE_VALUE && hOpenLibSys == NULL) { throw std::runtime_error("MSR and Winring0 drivers can't be opened"); } } bool PciHandle::exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) { DBG(3, "Checking PCI Config space at g:b:d:f ", groupnr_, ":", bus_, ":", device_, ":", function_); HANDLE tempHandle = openMSRDriver(); if (tempHandle != INVALID_HANDLE_VALUE) { // TODO: check device availability CloseHandle(tempHandle); return true; } if (groupnr_ != 0) { std::cerr << nonZeroGroupErrMsg << '\n'; return false; } if (hOpenLibSys != NULL) { DWORD addr(PciBusDevFunc(bus_, device_, function_)); DWORD result = 0; return ReadPciConfigDwordEx(addr, 0, &result)?true:false; } return false; } int32 PciHandle::read32(uint64 offset, uint32 * value) { warnAlignment<4>("PciHandle::read32", false, offset); if (hDriver != INVALID_HANDLE_VALUE) { PCICFG_Request req; ULONG64 result = 0; DWORD reslength = 0; req.bus = (ULONG)bus; req.dev = (ULONG)device; req.func = (ULONG)function; req.bytes = sizeof(uint32); req.reg = (ULONG)offset; BOOL status = DeviceIoControl(hDriver, IO_CTL_PCICFG_READ, &req, (DWORD)sizeof(PCICFG_Request), &result, (DWORD)sizeof(uint64), &reslength, NULL); *value = (uint32)result; if (!status) { DBG(3, "Error reading PCI Config space at bus ", bus, " dev ", device , " function ", function ," offset ", offset , " size ", req.bytes , ". Windows error: ", GetLastError()); } return (int32)reslength; } DWORD result = 0; if (ReadPciConfigDwordEx(pciAddress, (DWORD)offset, &result)) { *value = result; return (int32)sizeof(uint32); } return 0; } int32 PciHandle::write32(uint64 offset, uint32 value) { warnAlignment<4>("PciHandle::write32", false, offset); if (hDriver != INVALID_HANDLE_VALUE) { PCICFG_Request req; ULONG64 result; DWORD reslength = 0; req.bus = bus; req.dev = device; req.func = function; req.bytes = sizeof(uint32); req.reg = (uint32)offset; req.write_value = value; BOOL status = DeviceIoControl(hDriver, IO_CTL_PCICFG_WRITE, &req, (DWORD)sizeof(PCICFG_Request), &result, (DWORD)sizeof(uint64), &reslength, NULL); if (!status) { DBG(3, "Error writing PCI Config space at bus " , bus, " dev ", device, " function ", function ," offset ", offset , " size ", req.bytes , ". Windows error: ", GetLastError()); return 0; } return (int32)sizeof(uint32); } return (WritePciConfigDwordEx(pciAddress, (DWORD)offset, value)) ? sizeof(uint32) : 0; } int32 PciHandle::read64(uint64 offset, uint64 * value) { warnAlignment<4>("PciHandle::read64", false, offset); if (hDriver != INVALID_HANDLE_VALUE) { if (offset & 7) { // this driver supports only 8-byte aligned reads // use read32 for unaligned reads uint32* value32Ptr = (uint32*)value; return read32(offset, value32Ptr) + read32(offset + sizeof(uint32), value32Ptr + 1); } PCICFG_Request req; DWORD reslength = 0; req.bus = bus; req.dev = device; req.func = function; req.bytes = sizeof(uint64); req.reg = (uint32)offset; BOOL status = DeviceIoControl(hDriver, IO_CTL_PCICFG_READ, &req, (DWORD)sizeof(PCICFG_Request), value, (DWORD)sizeof(uint64), &reslength, NULL); if (!status) { DBG(3, "Error reading PCI Config space at bus ", bus, " dev ", device, " function ", function ," offset ", offset , " size ", req.bytes , ". Windows error: ", GetLastError()); } return (int32)reslength; } cvt_ds cvt; cvt.ui64 = 0; BOOL status = ReadPciConfigDwordEx(pciAddress, (DWORD)offset, &(cvt.ui32.low)); status &= ReadPciConfigDwordEx(pciAddress, ((DWORD)offset) + sizeof(uint32), &(cvt.ui32.high)); if (status) { *value = cvt.ui64; return (int32)sizeof(uint64); } return 0; } PciHandle::~PciHandle() { if (hDriver != INVALID_HANDLE_VALUE) CloseHandle(hDriver); } #elif __APPLE__ PciHandle::PciHandle(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) : bus(bus_), device(device_), function(function_) { } bool PciHandle::exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) { if (groupnr_ != 0) { std::cerr << "Non-zero PCI group segments are not supported in PCM/APPLE OSX\n"; return false; } uint32_t pci_address = FORM_PCI_ADDR(bus_, device_, function_, 0); uint32_t value = 0; PCIDriver_read32(pci_address, &value); uint32_t vendor_id = value & 0xffff; uint32_t device_id = (value >> 16) & 0xffff; //if (vendor_id == PCM_INTEL_PCI_VENDOR_ID) { if (vendor_id != 0xffff && device_id != 0xffff) { return true; } else { return false; } } int32 PciHandle::read32(uint64 offset, uint32 * value) { warnAlignment<4>("PciHandle::read32", false, offset); uint32_t pci_address = FORM_PCI_ADDR(bus, device, function, (uint32_t)offset); return PCIDriver_read32(pci_address, value); } int32 PciHandle::write32(uint64 offset, uint32 value) { warnAlignment<4>("PciHandle::write32", false, offset); uint32_t pci_address = FORM_PCI_ADDR(bus, device, function, (uint32_t)offset); return PCIDriver_write32(pci_address, value); } int32 PciHandle::read64(uint64 offset, uint64 * value) { warnAlignment<4>("PciHandle::read64", false, offset); uint32_t pci_address = FORM_PCI_ADDR(bus, device, function, (uint32_t)offset); return PCIDriver_read64(pci_address, value); } PciHandle::~PciHandle() { } #elif defined (__FreeBSD__) || defined(__DragonFly__) PciHandle::PciHandle(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) : fd(-1), groupnr(groupnr_), bus(bus_), device(device_), function(function_) { int handle = ::open("/dev/pci", O_RDWR); if (handle < 0) throw std::exception(); fd = handle; } bool PciHandle::exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) { struct pci_conf_io pc; struct pci_match_conf pattern; struct pci_conf conf[4]; int fd; int ret; fd = ::open("/dev/pci", O_RDWR, 0); if (fd < 0) return false; bzero(&pc, sizeof(pc)); pattern.pc_sel.pc_domain = groupnr_; pattern.pc_sel.pc_bus = bus_; pattern.pc_sel.pc_dev = device_; pattern.pc_sel.pc_func = function_; pattern.flags = (pci_getconf_flags)(PCI_GETCONF_MATCH_DOMAIN | PCI_GETCONF_MATCH_BUS | PCI_GETCONF_MATCH_DEV | PCI_GETCONF_MATCH_FUNC); pc.pat_buf_len = sizeof(pattern); pc.patterns = &pattern; pc.num_patterns = 1; pc.match_buf_len = sizeof(conf); pc.matches = conf; ret = ioctl(fd, PCIOCGETCONF, &pc); ::close(fd); if (ret) return false; if (pc.status != PCI_GETCONF_LAST_DEVICE) return false; if (pc.num_matches > 0) return true; return false; } int32 PciHandle::read32(uint64 offset, uint32 * value) { warnAlignment<4>("PciHandle::read32", false, offset); struct pci_io pi; int ret; pi.pi_sel.pc_domain = groupnr; pi.pi_sel.pc_bus = bus; pi.pi_sel.pc_dev = device; pi.pi_sel.pc_func = function; pi.pi_reg = offset; pi.pi_width = 4; ret = ioctl(fd, PCIOCREAD, &pi); if (ret) return ret; *value = pi.pi_data; return sizeof(*value); } int32 PciHandle::write32(uint64 offset, uint32 value) { warnAlignment<4>("PciHandle::write32", false, offset); struct pci_io pi; int ret; pi.pi_sel.pc_domain = groupnr; pi.pi_sel.pc_bus = bus; pi.pi_sel.pc_dev = device; pi.pi_sel.pc_func = function; pi.pi_reg = offset; pi.pi_width = 4; pi.pi_data = value; ret = ioctl(fd, PCIOCWRITE, &pi); if (ret) return ret; return sizeof(value); } int32 PciHandle::read64(uint64 offset, uint64 * value) { warnAlignment<4>("PciHandle::read64", false, offset); struct pci_io pi; int32 ret; pi.pi_sel.pc_domain = groupnr; pi.pi_sel.pc_bus = bus; pi.pi_sel.pc_dev = device; pi.pi_sel.pc_func = function; pi.pi_reg = offset; pi.pi_width = 4; ret = ioctl(fd, PCIOCREAD, &pi); if (ret) return ret; *value = pi.pi_data; pi.pi_reg += 4; ret = ioctl(fd, PCIOCREAD, &pi); if (ret) return ret; *value += ((uint64)pi.pi_data << 32); return sizeof(value); } PciHandle::~PciHandle() { if (fd >= 0) ::close(fd); } #else // Linux implementation int openHandle(uint32 groupnr_, uint32 bus, uint32 device, uint32 function) { std::ostringstream path(std::ostringstream::out); path << std::hex << "/proc/bus/pci/"; if (groupnr_) { path << std::setw(4) << std::setfill('0') << groupnr_ << ":"; } path << std::setw(2) << std::setfill('0') << bus << "/" << std::setw(2) << std::setfill('0') << device << "." << function; // std::cout << "PciHandle: Opening "<("PciHandle::read32", false, offset); return ::pread(fd, (void *)value, sizeof(uint32), offset); } int32 PciHandle::write32(uint64 offset, uint32 value) { warnAlignment<4>("PciHandle::write32", false, offset); return ::pwrite(fd, (const void *)&value, sizeof(uint32), offset); } int32 PciHandle::read64(uint64 offset, uint64 * value) { warnAlignment<4>("PciHandle::read64", false, offset); size_t res = ::pread(fd, (void *)value, sizeof(uint64), offset); if(res != sizeof(uint64)) { std::cerr << " ERROR: pread from " << fd << " with offset 0x" << std::hex << offset << std::dec << " returned " << res << " bytes \n"; } return res; } PciHandle::~PciHandle() { if (fd >= 0) ::close(fd); } int PciHandle::openMcfgTable() { const std::vector base_paths = {"/sys/firmware/acpi/tables/MCFG", "/sys/firmware/acpi/tables/MCFG1"}; std::vector paths = base_paths; for (const auto & p: base_paths) { paths.push_back(std::string("/pcm") + p); } int handle = -1; for (const auto & p: paths) { if (handle < 0) { handle = ::open(p.c_str(), O_RDONLY); } } if (handle < 0) { for (const auto & p: paths) { std::cerr << "Can't open MCFG table. Check permission of " << p << "\n"; } } return handle; } #ifndef PCM_USE_PCI_MM_LINUX PciHandleM::PciHandleM(uint32 bus_, uint32 device_, uint32 function_) : fd(-1), bus(bus_), device(device_), function(function_), base_addr(0) { int handle = ::open("/dev/mem", O_RDWR); if (handle < 0) throw std::exception(); fd = handle; int mcfg_handle = PciHandle::openMcfgTable(); if (mcfg_handle < 0) throw std::runtime_error("Cannot open any of /[pcm]/sys/firmware/acpi/tables/MCFG* files!"); int32 result = ::pread(mcfg_handle, (void *)&base_addr, sizeof(uint64), 44); if (result != sizeof(uint64)) { ::close(mcfg_handle); throw std::exception(); } unsigned char max_bus = 0; result = ::pread(mcfg_handle, (void *)&max_bus, sizeof(unsigned char), 55); ::close(mcfg_handle); if (result != sizeof(unsigned char)) { throw std::exception(); } if (bus > (unsigned)max_bus) { std::cout << "WARNING: Requested bus number " << bus << " is larger than the max bus number " << (unsigned)max_bus << "\n"; throw std::exception(); } // std::cout << "PCI config base addr: "<< std::hex << base_addr<< "\n" << std::dec; base_addr += (bus * 1024ULL * 1024ULL + device * 32ULL * 1024ULL + function * 4ULL * 1024ULL); } bool PciHandleM::exists(uint32 /*groupnr_*/, uint32 /* bus_*/, uint32 /* device_ */, uint32 /* function_ */) { int handle = ::open("/dev/mem", O_RDWR); if (handle < 0) { perror("error opening /dev/mem"); return false; } ::close(handle); handle = PciHandle::openMcfgTable(); if (handle < 0) { return false; } ::close(handle); return true; } int32 PciHandleM::read32(uint64 offset, uint32 * value) { warnAlignment<4>("PciHandleM::read32", false, offset); return ::pread(fd, (void *)value, sizeof(uint32), offset + base_addr); } int32 PciHandleM::write32(uint64 offset, uint32 value) { warnAlignment<4>("PciHandleM::write32", false, offset); return ::pwrite(fd, (const void *)&value, sizeof(uint32), offset + base_addr); } int32 PciHandleM::read64(uint64 offset, uint64 * value) { warnAlignment<4>("PciHandleM::read64", false, offset); return ::pread(fd, (void *)value, sizeof(uint64), offset + base_addr); } PciHandleM::~PciHandleM() { if (fd >= 0) ::close(fd); } #endif // PCM_USE_PCI_MM_LINUX // mmapped I/O version MCFGHeader PciHandleMM::mcfgHeader; std::vector PciHandleMM::mcfgRecords; const std::vector & PciHandleMM::getMCFGRecords() { readMCFG(); return mcfgRecords; } void PciHandleMM::readMCFG() { if (mcfgRecords.size() > 0) return; // already initialized int mcfg_handle = PciHandle::openMcfgTable(); if (mcfg_handle < 0) throw std::runtime_error("cannot open any of /[pcm]/sys/firmware/acpi/tables/MCFG* files!"); ssize_t read_bytes = ::read(mcfg_handle, (void *)&mcfgHeader, sizeof(MCFGHeader)); if (read_bytes == 0) { ::close(mcfg_handle); const auto msg = "PCM Error: Cannot read MCFG-table"; std::cerr << msg; std::cerr << "\n"; throw std::runtime_error(msg); } const unsigned segments = mcfgHeader.nrecords(); #ifdef PCM_DEBUG mcfgHeader.print(); std::cout << "PCM Debug: total segments: " << segments << "\n"; #endif for (unsigned int i = 0; i < segments; ++i) { MCFGRecord record; read_bytes = ::read(mcfg_handle, (void *)&record, sizeof(MCFGRecord)); if (read_bytes == 0) { ::close(mcfg_handle); const auto msg = "PCM Error: Cannot read MCFG-table (2)"; std::cerr << msg; std::cerr << "\n"; throw std::runtime_error(msg); } #ifdef PCM_DEBUG std::cout << "PCM Debug: segment " << std::dec << i << " "; record.print(); #endif mcfgRecords.push_back(record); } ::close(mcfg_handle); } PciHandleMM::PciHandleMM(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) : fd(-1), mmapAddr(NULL), bus(bus_), device(device_), function(function_), base_addr(0) { int handle = ::open("/dev/mem", O_RDWR); if (handle < 0) throw std::exception(); fd = handle; readMCFG(); unsigned segment = 0; for ( ; segment < mcfgRecords.size(); ++segment) { if (mcfgRecords[segment].PCISegmentGroupNumber == groupnr_ && mcfgRecords[segment].startBusNumber <= bus_ && bus <= mcfgRecords[segment].endBusNumber) break; } if (segment == mcfgRecords.size()) { std::cerr << "PCM Error: (group " << groupnr_ << ", bus " << bus_ << ") not found in the MCFG table.\n"; throw std::exception(); } else { #ifdef PCM_DEBUG std::cout << "PCM Debug: (group " << groupnr_ << ", bus " << bus_ << ") found in the MCFG table in segment " << segment << "\n"; #endif } base_addr = mcfgRecords[segment].baseAddress; base_addr += (bus * 1024ULL * 1024ULL + device * 32ULL * 1024ULL + function * 4ULL * 1024ULL); mmapAddr = (char *)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr); if (mmapAddr == MAP_FAILED) { std::cout << "mmap failed: errno is " << errno << "\n"; throw std::exception(); } } bool PciHandleMM::exists(uint32 /*groupnr_*/, uint32 /*bus_*/, uint32 /*device_*/, uint32 /*function_*/) { int handle = ::open("/dev/mem", O_RDWR); if (handle < 0) { perror("error opening /dev/mem"); return false; } ::close(handle); handle = PciHandle::openMcfgTable(); if (handle < 0) { return false; } ::close(handle); return true; } int32 PciHandleMM::read32(uint64 offset, uint32 * value) { warnAlignment<4>("PciHandleMM::read32", false, offset); *value = *((uint32 *)(mmapAddr + offset)); return sizeof(uint32); } int32 PciHandleMM::write32(uint64 offset, uint32 value) { warnAlignment<4>("PciHandleMM::write32", false, offset); *((uint32 *)(mmapAddr + offset)) = value; return sizeof(uint32); } int32 PciHandleMM::read64(uint64 offset, uint64 * value) { warnAlignment<4>("PciHandleMM::read64", false, offset); read32(offset, (uint32 *)value); read32(offset + sizeof(uint32), ((uint32 *)value) + 1); return sizeof(uint64); } PciHandleMM::~PciHandleMM() { if (mmapAddr) munmap(mmapAddr, 4096); if (fd >= 0) ::close(fd); } #endif } // namespace pcm pcm-202502/src/pci.h000066400000000000000000000204321475730356400141170ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // Pat Fay // Jim Harris (FreeBSD) #ifndef CPUCounters_PCI_H #define CPUCounters_PCI_H /*! \file pci.h \brief Low level interface to access PCI configuration space */ #include "types.h" #include "debug.h" #include "utils.h" #ifdef _MSC_VER #include "windows.h" #else #include #endif #ifdef __APPLE__ #include "PCIDriverInterface.h" #endif #include namespace pcm { class PciHandle { #ifdef _MSC_VER HANDLE hDriver; #else int32 fd; #endif #if defined(__FreeBSD__) || defined(__DragonFly__) uint32 groupnr; #endif uint32 bus; uint32 device; uint32 function; #ifdef _MSC_VER DWORD pciAddress; #endif friend class PciHandleM; friend class PciHandleMM; PciHandle(); // forbidden PciHandle(const PciHandle &); // forbidden PciHandle & operator = (const PciHandle &); // forbidden public: PciHandle(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_); static bool exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_); int32 read32(uint64 offset, uint32 * value); int32 write32(uint64 offset, uint32 value); int32 read64(uint64 offset, uint64 * value); virtual ~PciHandle(); protected: static int openMcfgTable(); }; #ifdef _MSC_VER typedef PciHandle PciHandleType; #elif __APPLE__ // This may need to change if it can be implemented for OSX typedef PciHandle PciHandleType; #elif defined(__FreeBSD__) || defined(__DragonFly__) typedef PciHandle PciHandleType; #else // read/write PCI config space using physical memory class PciHandleM { #ifdef _MSC_VER #else int32 fd; #endif uint32 bus; uint32 device; uint32 function; uint64 base_addr; PciHandleM() = delete; // forbidden PciHandleM(PciHandleM &) = delete; // forbidden PciHandleM & operator = (PciHandleM &) = delete; // forbidden public: PciHandleM(uint32 bus_, uint32 device_, uint32 function_); static bool exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_); int32 read32(uint64 offset, uint32 * value); int32 write32(uint64 offset, uint32 value); int32 read64(uint64 offset, uint64 * value); virtual ~PciHandleM(); }; #ifndef _MSC_VER // read/write PCI config space using physical memory using mmapped file I/O class PciHandleMM { int32 fd; char * mmapAddr; uint32 bus; uint32 device; uint32 function; uint64 base_addr; #ifdef __linux__ static MCFGHeader mcfgHeader; static std::vector mcfgRecords; static void readMCFG(); #endif PciHandleMM() = delete; // forbidden PciHandleMM(const PciHandleMM &) = delete; // forbidden PciHandleMM & operator = (const PciHandleMM &) = delete; public: PciHandleMM(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_); static bool exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_); int32 read32(uint64 offset, uint32 * value); int32 write32(uint64 offset, uint32 value); int32 read64(uint64 offset, uint64 * value); virtual ~PciHandleMM(); #ifdef __linux__ static const std::vector & getMCFGRecords(); #endif }; #ifdef PCM_USE_PCI_MM_LINUX #define PciHandleType PciHandleMM #else #define PciHandleType PciHandle #endif #endif // _MSC_VER #endif template inline void forAllIntelDevices(F f, int requestedDevice = -1, int requestedFunction = -1) { std::vector mcfg; getMCFGRecords(mcfg); auto probe = [&f](const uint32 group, const uint32 bus, const uint32 device, const uint32 function) { DBG(3, "Probing " , std::hex , group , ":" , bus , ":" , device , ":" , function , " " , std::dec); uint32 value = 0; try { PciHandleType h(group, bus, device, function); h.read32(0, &value); } catch(...) { // invalid bus:devicei:function return; } const uint32 vendor_id = value & 0xffff; const uint32 device_id = (value >> 16) & 0xffff; DBG(3, "Found dev " , std::hex , vendor_id , ":" , device_id , std::dec); if (vendor_id != PCM_INTEL_PCI_VENDOR_ID) { return; } f(group, bus, device, function, device_id); }; for (uint32 s = 0; s < (uint32)mcfg.size(); ++s) { const auto group = mcfg[s].PCISegmentGroupNumber; for (uint32 bus = (uint32)mcfg[s].startBusNumber; bus <= (uint32)mcfg[s].endBusNumber; ++bus) { auto forAllFunctions = [requestedFunction,&probe](const uint32 group, const uint32 bus, const uint32 device) { if (requestedFunction < 0) { for (uint32 function = 0 ; function < 8; ++function) { probe(group, bus, device, function); } } else { probe(group, bus, device, requestedFunction); } }; if (requestedDevice < 0) { for (uint32 device = 0 ; device < 32; ++device) { forAllFunctions(group, bus, device); } } else { forAllFunctions(group, bus, requestedDevice); } } } } union VSEC { struct { uint64 cap_id:16; uint64 cap_version:4; uint64 cap_next:12; uint64 vsec_id:16; uint64 vsec_version:4; uint64 vsec_length:12; uint64 entryID:16; uint64 NumEntries:8; uint64 EntrySize:8; uint64 tBIR:3; uint64 Address:29; } fields; uint64 raw_value64[2]; uint32 raw_value32[4]; }; template void processDVSEC(MatchFunc matchFunc, ProcessFunc processFunc) { forAllIntelDevices([&](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 device_id) { DBG(2, "Intel device scan.found " , std::hex , group , ":" , bus , " : " , device , " : " , function , " " , device_id); uint32 status{0}; PciHandleType h(group, bus, device, function); h.read32(4, &status); // read status if (status & 0x100000) // has capability list { DBG(2, "Intel device scan. found ", std::hex , group , ":" , bus , ":" , device , ":" , function , " " , device_id , " with capability list"); VSEC header; uint64 offset = 0x100; do { if (offset == 0 || h.read32(offset, &header.raw_value32[0]) != sizeof(uint32) || header.raw_value32[0] == 0) { return; } if (h.read64(offset, &header.raw_value64[0]) != sizeof(uint64) || h.read64(offset + sizeof(uint64), &header.raw_value64[1]) != sizeof(uint64)) { return; } DBG(2, "offset 0x" , std::hex , offset , " cap_id: 0x" , header.fields.cap_id , " vsec_id: 0x", header.fields.vsec_id, " entryID: 0x" , std::hex , header.fields.entryID , std::dec); if (matchFunc(header)) { DBG(2, ".... found match"); auto barOffset = 0x10 + header.fields.tBIR * 4; uint32 bar = 0; if (h.read32(barOffset, &bar) == sizeof(uint32) && bar != 0) // read bar { bar &= ~4095; processFunc(bar, header); } else { std::cerr << "Error: can't read bar from offset " << barOffset << " \n"; } } const uint64 lastOffset = offset; offset = header.fields.cap_next & ~3; if (lastOffset == offset) // the offset did not change { DBG(2, " lastOffset == offset ", lastOffset , "==", offset); return; // deadlock protection } } while (1); } }); } } // namespace pcm #endif pcm-202502/src/pcm-accel-common.cpp000066400000000000000000000366521475730356400170240ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2022-2023, Intel Corporation // written by White.Hu, Pavithran P #include "pcm-accel-common.h" #include "cpucounters.h" #include idx_ccr* idx_get_ccr(uint64_t& ccr) { return new spr_idx_ccr(ccr); } uint32_t AcceleratorCounterState::getNumOfAccelDevs() { uint32_t dev_count = 0; if (evt_ctx.accel >= ACCEL_MAX || evt_ctx.m == NULL) return 0; switch (evt_ctx.accel) { case ACCEL_IAA: dev_count = evt_ctx.m->getNumOfIDXAccelDevs(PCM::IDX_IAA); break; case ACCEL_DSA: dev_count = evt_ctx.m->getNumOfIDXAccelDevs(PCM::IDX_DSA); break; case ACCEL_QAT: dev_count = evt_ctx.m->getNumOfIDXAccelDevs(PCM::IDX_QAT); break; default: dev_count = 0; break; } return dev_count; } uint32_t AcceleratorCounterState::getMaxNumOfAccelCtrs() { uint32_t ctr_count = 0; if (evt_ctx.accel >= ACCEL_MAX || evt_ctx.m == NULL) return 0; switch (evt_ctx.accel) { case ACCEL_IAA: case ACCEL_DSA: case ACCEL_QAT: ctr_count = evt_ctx.m->getMaxNumOfIDXAccelCtrs(evt_ctx.accel); break; default: ctr_count = 0; break; } return ctr_count; } int32_t AcceleratorCounterState::programAccelCounters() { std::vector rawEvents; std::vector filters_wq, filters_tc, filters_pgsz, filters_xfersz, filters_eng; if (evt_ctx.m == NULL || evt_ctx.accel >= ACCEL_MAX || evt_ctx.ctrs.size() == 0 || evt_ctx.ctrs.size() > getMaxNumOfAccelCtrs()) return -1; switch (evt_ctx.accel) { case ACCEL_IAA: case ACCEL_DSA: case ACCEL_QAT: for (auto pctr = evt_ctx.ctrs.begin(); pctr != evt_ctx.ctrs.end(); ++pctr) { rawEvents.push_back(pctr->ccr); filters_wq.push_back(pctr->cfr_wq); filters_tc.push_back(pctr->cfr_tc); filters_pgsz.push_back(pctr->cfr_pgsz); filters_xfersz.push_back(pctr->cfr_xfersz); filters_eng.push_back(pctr->cfr_eng); //std::cout<<"ctr idx=0x" << std::hex << pctr->idx << " hid=0x" << std::hex << pctr->h_id << " vid=0x" << std::hex << pctr->v_id <<" ccr=0x" << std::hex << pctr->ccr << "\n"; //std::cout<<"mul=0x" << std::hex << pctr->multiplier << " div=0x" << std::hex << pctr->divider << "\n" << std::dec; } evt_ctx.m->programIDXAccelCounters(idx_accel_mapping[evt_ctx.accel], rawEvents, filters_wq, filters_eng, filters_tc, filters_pgsz, filters_xfersz); break; default: break; } return 0; } SimpleCounterState AcceleratorCounterState::getAccelCounterState(uint32 dev, uint32 ctr_index) { SimpleCounterState result; if (evt_ctx.m == NULL || evt_ctx.accel >= ACCEL_MAX || dev >= getNumOfAccelDevs() || ctr_index >= getMaxNumOfAccelCtrs()) return result; switch (evt_ctx.accel) { case ACCEL_IAA: case ACCEL_DSA: case ACCEL_QAT: result = evt_ctx.m->getIDXAccelCounterState(evt_ctx.accel, dev, ctr_index); break; case ACCEL_MAX: case ACCEL_NOCONFIG: break; } return result; } bool AcceleratorCounterState::isAccelCounterAvailable() { bool ret = true; if (evt_ctx.m == NULL || evt_ctx.accel >= ACCEL_MAX) ret =false; if (getNumOfAccelDevs() == 0) ret = false; return ret; } std::string AcceleratorCounterState::getAccelCounterName() { std::string ret; switch (evt_ctx.accel) { case ACCEL_IAA: ret = "iaa"; break; case ACCEL_DSA: ret = "dsa"; break; case ACCEL_QAT: ret = "qat"; break; default: ret = "id=" + std::to_string(evt_ctx.accel) + "(unknown)"; } return ret; } bool AcceleratorCounterState::getAccelDevLocation( uint32_t dev, const ACCEL_DEV_LOC_MAPPING loc_map, uint32_t &location) { bool ret = true; switch (loc_map) { case SOCKET_MAP: location = evt_ctx.m->getCPUSocketIdOfIDXAccelDev(evt_ctx.accel, dev); break; case NUMA_MAP: location = evt_ctx.m->getNumaNodeOfIDXAccelDev(evt_ctx.accel, dev); break; default: ret = false; } return ret; } /*! \brief Computes number of accelerator counters present in system \return Number of accel counters in system */ int AcceleratorCounterState::getNumberOfCounters(){ return getCounters().size(); } std::string AcceleratorCounterState::getAccelIndexCounterName(int ctr_index) { accel_counter pctr = getCounters().at(ctr_index); return pctr.v_event_name; } uint64 AcceleratorCounterState::getAccelIndexCounter(uint32 dev, const SystemCounterState & before,const SystemCounterState & after,int ctr_index) { const uint32_t counter_nb = getCounters().size(); accel_counter pctr = getCounters().at(ctr_index); uint64_t raw_result = getNumberOfEvents(before.accel_counters[dev*counter_nb + ctr_index], after.accel_counters[dev*counter_nb + ctr_index]); uint64_t trans_result = uint64_t (raw_result * pctr.multiplier / (double) pctr.divider ); return trans_result; } int idx_evt_parse_handler(evt_cb_type cb_type, void *cb_ctx, counter &base_ctr, std::map &ofm, std::string key, uint64 numValue) { accel_evt_parse_context *context = (accel_evt_parse_context *)cb_ctx; // PCM *m = context->m; AcceleratorCounterState *accs_; accs_ = AcceleratorCounterState::getInstance(); if (cb_type == EVT_LINE_START) //this event will be called per line(start) { context->ctr.cfr_wq = 0xFFFF; context->ctr.cfr_eng = 0xFFFF; context->ctr.cfr_tc = 0xFFFF; context->ctr.cfr_pgsz = 0xFFFF; context->ctr.cfr_xfersz = 0xFFFF; context->ctr.ccr = 0; } else if (cb_type == EVT_LINE_FIELD) //this event will be called per field of line { std::unique_ptr pccr(idx_get_ccr(context->ctr.ccr)); //std::cout << "Key:" << key << " Value:" << value << " opcodeFieldMap[key]:" << ofm[key] << "\n"; switch (ofm[key]) { case PCM::EVENT_SELECT: pccr->set_event_select(numValue); //std::cout << "pccr value:" << std::hex << pccr->get_ccr_value() <<"\n" << std::dec; break; case PCM::ENABLE: pccr->set_enable(numValue); //std::cout << "pccr value:" << std::hex << pccr->get_ccr_value() <<"\n" << std::dec; break; case EVENT_CATEGORY: pccr->set_event_category(numValue); //std::cout << "pccr value:" << std::hex << pccr->get_ccr_value() <<"\n" << std::dec; break; case FILTER_WQ: context->ctr.cfr_wq = (uint32_t)numValue; break; case FILTER_ENG: context->ctr.cfr_eng = (uint32_t)numValue; break; case FILTER_TC: context->ctr.cfr_tc = (uint32_t)numValue; break; case FILTER_PGSZ: context->ctr.cfr_pgsz = (uint32_t)numValue; break; case FILTER_XFERSZ: context->ctr.cfr_xfersz = (uint32_t)numValue; break; case PCM::INVALID: default: std::cerr << "Field in -o file not recognized. The key is: " << key << "\n"; return -1; } } else if(cb_type == EVT_LINE_COMPLETE) //this event will be called every line(end) { if (context->accel == ACCEL_IAA && base_ctr.h_event_name != "IAA") { return 0; //skip non-IAA cfg line } else if(context->accel == ACCEL_DSA && base_ctr.h_event_name != "DSA") { return 0; //skip non-DSA cfg line } else if(context->accel == ACCEL_QAT && base_ctr.h_event_name != "QAT") { return 0; //skip non-QAT cfg line } //Validate the total number of counter exceed the maximum or not. if ((uint32)base_ctr.idx >= accs_->getMaxNumOfAccelCtrs()) { std::cerr << "line parse KO due to invalid value!" << std::dec << "\n"; return 0; //skip the invalid cfg line } context->ctr.h_event_name = base_ctr.h_event_name; context->ctr.v_event_name = base_ctr.v_event_name; context->ctr.idx = base_ctr.idx; context->ctr.multiplier = base_ctr.multiplier; context->ctr.divider = base_ctr.divider; context->ctr.h_id = base_ctr.h_id; context->ctr.v_id = base_ctr.v_id; //std::cout << "line parse OK, ctrcfg=0x" << std::hex << context->ctr.ccr << ", h_event_name=" << base_ctr.h_event_name << ", v_event_name=" << base_ctr.v_event_name; //std::cout << ", h_id=0x" << std::hex << base_ctr.h_id << ", v_id=0x" << std::hex << base_ctr.v_id; //std::cout << ", idx=0x"<< std::hex << base_ctr.idx << ", multiplier=0x" << std::hex << base_ctr.multiplier << ", divider=0x" << std::hex << base_ctr.divider << std::dec << "\n"; context->ctrs.push_back(context->ctr); } return 0; } std::vector& AcceleratorCounterState::getCounters(){ return evt_ctx.ctrs; } uint32_t AcceleratorCounterState::getAccel() { return evt_ctx.accel; } void readAccelCounters(SystemCounterState& sycs_) { AcceleratorCounterState *accs_ = AcceleratorCounterState::getInstance(); PCM *pcm = PCM::getInstance(); // const uint32_t delay_ms = uint32_t(delay * 1000); const uint32_t dev_count = accs_->getNumOfAccelDevs(); const uint32_t counter_nb = accs_->getCounters().size(); pcm->setNumberofAccelCounters(dev_count*counter_nb); uint32_t ctr_index = 0; // accel_content accel_results(ACCEL_MAX, dev_content(ACCEL_IP_DEV_COUNT_MAX, ctr_data())); sycs_.accel_counters.resize(size_t(dev_count) * size_t(counter_nb)); SimpleCounterState *currState = new SimpleCounterState[dev_count*counter_nb]; // programAccelCounters(m, accel, ctrs); switch (accs_->getAccel()) { case ACCEL_IAA: case ACCEL_DSA: for (uint32_t dev = 0; dev != dev_count; ++dev) { ctr_index = 0; for (auto pctr = accs_->getCounters().begin(); pctr != accs_->getCounters().end(); ++pctr) { sycs_.accel_counters[dev*counter_nb + ctr_index] = accs_->getAccelCounterState( dev, ctr_index); ctr_index++; } } break; case ACCEL_QAT: // MySleepMs(delay_ms); for (uint32_t dev = 0; dev != dev_count; ++dev) { pcm->controlQATTelemetry(dev, PCM::QAT_TLM_REFRESH); ctr_index = 0; for (auto pctr = accs_->getCounters().begin();pctr != accs_->getCounters().end(); ++pctr) { sycs_.accel_counters[dev*counter_nb + ctr_index] = accs_->getAccelCounterState(dev, ctr_index); // raw_result = currState[dev*counter_nb + ctr_index].getRawData(); // trans_result = uint64_t (raw_result * pctr->multiplier / (double) pctr->divider ); //accel_result[evt_ctx.accel][dev][std::pair(pctr->h_id,pctr->v_id)] = trans_result; //std::cout << "collect_data: accel=" << accel << " dev=" << dev << " h_id=" << pctr->h_id << " v_id=" << pctr->v_id << " data=" << std::hex << trans_result << "\n" << std::dec; ctr_index++; } } break; } deleteAndNullifyArray(currState); } AcceleratorCounterState* AcceleratorCounterState::instance = NULL; std::mutex instanceCreationMutexForAcceleratorCounterState{}; AcceleratorCounterState * AcceleratorCounterState::getInstance() { // lock-free read // cppcheck-suppress identicalConditionAfterEarlyExit if (instance) return instance; std::unique_lock _(instanceCreationMutexForAcceleratorCounterState); // cppcheck-suppress identicalConditionAfterEarlyExit if (instance) return instance; return instance = new AcceleratorCounterState(); } std::string AcceleratorCounterState::remove_string_inside_use(std::string text) { std::string result = ""; int open_use_count = 0; for (char c : text) { if (c == '(') { open_use_count += 1; } else if (c == ')' ) { open_use_count -= 1; } else if (open_use_count == 0) { result += c; } } return result; } void AcceleratorCounterState::setEvents(PCM *m,ACCEL_IP accel, std::string specify_evtfile,bool evtfile) { evt_ctx.m = m; evt_ctx.accel = accel; if (isAccelCounterAvailable() == true) { if (evtfile==false) //All platform use the spr config file by default. { ev_file_name = "opCode-6-143-accel.txt"; } else { ev_file_name = specify_evtfile; } //std::cout << "load event config file from:" << ev_file_name << "\n"; } else { std::cerr << "Error: " << getAccelCounterName() << " device is NOT available/ready with this platform! Program aborted\n"; exit(EXIT_FAILURE); } switch (accel) { case ACCEL_IAA: case ACCEL_DSA: case ACCEL_QAT: opcodeFieldMap["hname"] = PCM::H_EVENT_NAME; opcodeFieldMap["vname"] = PCM::V_EVENT_NAME; opcodeFieldMap["multiplier"] = PCM::MULTIPLIER; opcodeFieldMap["divider"] = PCM::DIVIDER; opcodeFieldMap["ctr"] = PCM::COUNTER_INDEX; opcodeFieldMap["en"] = PCM::ENABLE; opcodeFieldMap["ev_sel"] = PCM::EVENT_SELECT; opcodeFieldMap["ev_cat"] = EVENT_CATEGORY; opcodeFieldMap["filter_wq"] = FILTER_WQ; opcodeFieldMap["filter_eng"] = FILTER_ENG; opcodeFieldMap["filter_tc"] = FILTER_TC; opcodeFieldMap["filter_pgsz"] = FILTER_PGSZ; opcodeFieldMap["filter_xfersz"] = FILTER_XFERSZ; p_evt_handler = idx_evt_parse_handler; evt_ctx.ctrs.clear();//fill the ctrs by evt_handler callback func. break; default: std::cerr << "Error: Accel type=0x" << std::hex << accel << " is not supported! Program aborted\n" << std::dec; exit(EXIT_FAILURE); } try { load_events(ev_file_name, opcodeFieldMap, p_evt_handler, (void *)&evt_ctx); } catch (std::exception & e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << "Error: event cfg file have the problem, please double check it! Program aborted\n"; exit(EXIT_FAILURE); } if (evt_ctx.ctrs.size() ==0 || evt_ctx.ctrs.size() > getMaxNumOfAccelCtrs()) { std::cout<< evt_ctx.ctrs.size()<< " " << getMaxNumOfAccelCtrs(); std::cerr << "Error: event counter size is 0 or exceed maximum, please check the event cfg file! Program aborted\n"; exit(EXIT_FAILURE); } if (accel == ACCEL_QAT) { const uint32_t dev_count = getNumOfAccelDevs(); for (uint32_t dev = 0; dev != dev_count; ++dev) { m->controlQATTelemetry(dev, PCM::QAT_TLM_START); //start the QAT telemetry service } } }pcm-202502/src/pcm-accel-common.h000066400000000000000000000115341475730356400164610ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2022-2023, Intel Corporation // written by White.Hu, Pavithran P #pragma once #include "cpucounters.h" #ifdef __linux__ #include #endif using namespace pcm; #define PCM_DELAY_DEFAULT 3.0 // in seconds class idx_ccr { public: virtual uint64_t get_event_select() const = 0; virtual void set_event_select(uint64_t value) = 0; virtual uint64_t get_event_category() const = 0; virtual void set_event_category(uint64_t value) = 0; virtual uint64_t get_enable() const = 0; virtual void set_enable(uint64_t value) = 0; virtual uint64_t get_ccr_value() const = 0; virtual void set_ccr_value(uint64_t value) = 0; virtual ~idx_ccr() {}; }; class spr_idx_ccr: public idx_ccr { public: spr_idx_ccr(uint64_t &v){ ccr_value = &v; } virtual uint64_t get_event_select() const { //EVENT bit, bit 32 return ((*ccr_value >> 32) & 0xFFFFFFF); } virtual void set_event_select(uint64_t value) { *ccr_value |= (value << 32); } virtual uint64_t get_event_category() const { //EVENT Categorg, bit 8 return ((*ccr_value >> 8) & 0xF); } virtual void set_event_category(uint64_t value) { *ccr_value |= (value << 8); } virtual uint64_t get_enable() const { //Enable counter, bit 0 return ((*ccr_value >> 0 ) & 0x01); } virtual void set_enable(uint64_t value) { *ccr_value |= (value << 0); } virtual uint64_t get_ccr_value() const { return *ccr_value; } virtual void set_ccr_value(uint64_t value) { *ccr_value = value; } private: uint64_t* ccr_value = NULL; }; idx_ccr* idx_get_ccr(uint64_t& ccr); typedef enum { ACCEL_IAA, ACCEL_DSA, ACCEL_QAT, ACCEL_MAX, ACCEL_NOCONFIG, } ACCEL_IP; enum IDXPerfmonField { DPF_BASE = 0x100, //start from 0x100 to different with PerfmonField in cpucounter.h EVENT_CATEGORY, FILTER_WQ, FILTER_ENG, FILTER_TC, FILTER_PGSZ, FILTER_XFERSZ }; typedef enum { SOCKET_MAP, NUMA_MAP, } ACCEL_DEV_LOC_MAPPING; const std::vector idx_accel_mapping = { PCM::IDX_IAA, PCM::IDX_DSA, PCM::IDX_QAT }; #define ACCEL_IP_DEV_COUNT_MAX (16) typedef uint32_t h_id; typedef uint32_t v_id; typedef std::map,uint64_t> ctr_data; typedef std::vector dev_content; typedef std::vector accel_content; struct accel_counter : public counter { //filter config for IDX Accelerator. uint32_t cfr_wq = 0; uint32_t cfr_eng = 0; uint32_t cfr_tc = 0; uint32_t cfr_pgsz = 0; uint32_t cfr_xfersz = 0; }; typedef struct { PCM *m; ACCEL_IP accel; accel_counter ctr; std::vector ctrs; } accel_evt_parse_context; typedef int (*pfn_evt_handler)(evt_cb_type, void *, counter &, std::map &, std::string, uint64); int idx_evt_parse_handler(evt_cb_type cb_type, void *cb_ctx, counter &base_ctr, std::map &ofm, std::string key, uint64 numValue); void readAccelCounters(SystemCounterState &sycs_); class AcceleratorCounterState { private: AcceleratorCounterState(){}; // forbidden to call directly because it is a singleton AcceleratorCounterState & operator = (const AcceleratorCounterState &) = delete; static AcceleratorCounterState * instance; accel_evt_parse_context evt_ctx = { {}, {}, {}, {} }; public: AcceleratorCounterState(const AcceleratorCounterState& obj) = delete; // std::mutex instanceCreationMutex; static AcceleratorCounterState * getInstance(); std::map opcodeFieldMap; std::string ev_file_name; pfn_evt_handler p_evt_handler = NULL; void setEvents(PCM * m,ACCEL_IP accel,std::string specify_evtfile,bool evtfile); uint32_t getNumOfAccelDevs(); uint32_t getAccel(); uint32_t getMaxNumOfAccelCtrs(); std::vector& getCounters(); int32_t programAccelCounters(); SimpleCounterState getAccelCounterState(uint32 dev, uint32 ctr_index); bool isAccelCounterAvailable(); std::string getAccelCounterName(); void setDSA(); bool getAccelDevLocation( uint32_t dev, const ACCEL_DEV_LOC_MAPPING loc_map, uint32_t &location); // void readAccelCounters(SystemCounterState sycs_); int getNumberOfCounters(); std::string getAccelIndexCounterName(int ctr_index); std::string remove_string_inside_use(std::string text); uint64 getAccelIndexCounter(uint32 dev, const SystemCounterState & before,const SystemCounterState & after,int ctr_index); };pcm-202502/src/pcm-accel.cpp000066400000000000000000000443431475730356400155320ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2022-2024, Intel Corporation // written by White.Hu #include "pcm-accel-common.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include // std::length_error #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include "lspci.h" #include "utils.h" using namespace pcm; accel_content accel_results(ACCEL_MAX, dev_content(ACCEL_IP_DEV_COUNT_MAX, ctr_data())); std::vector build_counter_names(std::string dev_name, std::vector& ctrs, const ACCEL_DEV_LOC_MAPPING loc_map) { std::vector v; std::map> v_sort; v.push_back(dev_name); switch (loc_map) { case SOCKET_MAP: v.push_back("Socket"); break; case NUMA_MAP: v.push_back("NUMA Node"); break; default: break; } //re-organize data collection to be row wise for (std::vector::iterator counter = ctrs.begin(); counter != ctrs.end(); ++counter) { v_sort[counter->h_id][counter->v_id] = &(*counter); } for (std::map>::const_iterator hunit = v_sort.cbegin(); hunit != v_sort.cend(); ++hunit) { std::map v_array = hunit->second; //std::cout << "hunit: hhid=" << hh_id << "\n"; for (std::map::const_iterator vunit = v_array.cbegin(); vunit != v_array.cend(); ++vunit) { std::string v_name = vunit->second->v_event_name; v.push_back(v_name); } } return v; } void print_usage(const std::string& progname) { std::cout << "\n Usage: \n " << progname << " --help | [interval] [options] \n"; std::cout << " => time interval in seconds (floating point number is accepted)\n"; std::cout << " to sample performance counters.\n"; std::cout << " If not specified - 3.0 is used\n"; std::cout << " Supported are: \n"; std::cout << " -h | --help | /h => print this help and exit\n"; std::cout << " -silent => silence information output and print only measurements\n"; std::cout << " -iaa | /iaa => print IAA accel device measurements(default)\n"; std::cout << " -dsa | /dsa => print DSA accel device measurements\n"; #ifdef __linux__ std::cout << " -qat | /qat => print QAT accel device measurements\n"; std::cout << " -numa | /numa => print accel device numa node mapping(for linux only)\n"; #endif std::cout << " -evt[=cfg.txt] | /evt[=cfg.txt] => specify the event cfg file to cfg.txt \n"; std::cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; std::cout << " -csv-delimiter= | /csv-delimiter= => set custom csv delimiter\n"; std::cout << " -human-readable | /human-readable => use human readable format for output (for csv only)\n"; std::cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; std::cout << " Examples:\n"; std::cout << " " << progname << " -iaa 1.0 -i=10 => print IAA counters every second 10 times and exit\n"; std::cout << " " << progname << " -iaa 0.5 -csv=test.log => twice a second save IAA counter values to test.log in CSV format\n"; std::cout << " " << progname << " -iaa -csv -human-readable => every 3 second print IAA counters in human-readable CSV format\n"; std::cout << "\n"; } std::vector build_csv(const ACCEL_IP accel, std::vector& ctrs, const bool human_readable, const std::string& csv_delimiter, accel_content& sample_data, const ACCEL_DEV_LOC_MAPPING loc_map) { AcceleratorCounterState *accs_; accs_ = AcceleratorCounterState::getInstance(); std::vector result; std::vector current_row; auto header = build_counter_names("Accelerator", ctrs, loc_map); result.push_back(build_csv_row(header, csv_delimiter)); std::map> v_sort; uint32_t dev_count = accs_->getNumOfAccelDevs(); for (uint32_t dev = 0; dev != dev_count; ++dev) { //Re-organize data collection to be row wise std::map> v_sort; size_t max_name_width = 0; for (std::vector::iterator counter = ctrs.begin(); counter != ctrs.end(); ++counter) { max_name_width = (std::max)(max_name_width, counter->v_event_name.size()); v_sort[counter->h_id][counter->v_id] = &(*counter); //std::cout << "v_sort: h_id=" << std::hex << counter->h_id << ", v_id=" << std::hex << counter->v_id << "\n" << std::dec; } //Print data for (std::map>::const_iterator hunit = v_sort.cbegin(); hunit != v_sort.cend(); ++hunit) { std::map v_array = hunit->second; uint32_t hh_id = hunit->first; std::vector v_data; std::string h_name = v_array[0]->h_event_name + "#" + std::to_string(dev); uint32 location = 0xff; current_row.clear(); current_row.push_back(h_name); //dev name if (accs_->getAccelDevLocation( dev, loc_map, location) == true) { current_row.push_back(std::to_string(location)); //location info } //std::cout << "location mapping=" << loc_map << ", data=" << location << "\n"; //std::cout << "hunit: hhid=" << hh_id << "\n"; for (std::map::const_iterator vunit = v_array.cbegin(); vunit != v_array.cend(); ++vunit) { uint32_t vv_id = vunit->first; uint64_t raw_data = sample_data[accel][dev][std::pair(hh_id,vv_id)]; //std::cout << "vunit: hhid=" << hh_id << ", vname=" << vunit->second->v_event_name << ", data=" << raw_data << "\n"; current_row.push_back(human_readable ? unit_format(raw_data) : std::to_string(raw_data)); //counter data } result.push_back(build_csv_row(current_row, csv_delimiter)); } } return result; } std::vector build_display(const ACCEL_IP accel, std::vector& ctrs, accel_content& sample_data, const ACCEL_DEV_LOC_MAPPING loc_map) { std::vector buffer; std::vector headers; std::vector data; std::string row; AcceleratorCounterState *accs_; accs_ = AcceleratorCounterState::getInstance(); uint32_t dev_count = accs_->getNumOfAccelDevs(); headers = build_counter_names("Accelerator", ctrs, loc_map); //Print first row row = std::accumulate(headers.begin(), headers.end(), std::string(" "), a_header_footer); buffer.push_back(row); //Print a_title row = std::accumulate(headers.begin(), headers.end(), std::string("|"), a_title); buffer.push_back(row); //Print deliminator row = std::accumulate(headers.begin(), headers.end(), std::string("|"), a_header_footer); buffer.push_back(row); for (uint32_t dev = 0; dev != dev_count; ++dev) { //Print data std::map> v_sort; //re-organize data collection to be row wise for (std::vector::iterator counter = ctrs.begin(); counter != ctrs.end(); ++counter) { v_sort[counter->h_id][counter->v_id] = &(*counter); //std::cout << "v_sort: h_id=" << std::hex << counter->h_id << ", v_id=" << std::hex << counter->v_id << "\n" << std::dec; } for (std::map>::const_iterator hunit = v_sort.cbegin(); hunit != v_sort.cend(); ++hunit) { std::map v_array = hunit->second; uint32_t hh_id = hunit->first; std::vector v_data; std::string h_name = v_array[0]->h_event_name; uint32 location = 0xff; if (accs_->getAccelDevLocation(dev, loc_map, location) == true) { v_data.push_back(location); //location info } //std::cout << "location mapping=" << loc_map << ", data=" << location << "\n"; //std::cout << "hunit: hhid=" << hh_id << "\n"; for (std::map::const_iterator vunit = v_array.cbegin(); vunit != v_array.cend(); ++vunit) { uint32_t vv_id = vunit->first; uint64_t raw_data = sample_data[accel][dev][std::pair(hh_id,vv_id)]; //std::cout << "vunit: hhid=" << hh_id << ", vname=" << vunit->second->v_event_name << ", data=" << raw_data << "\n"; v_data.push_back(raw_data); //counter data } data = prepare_data(v_data, headers); row = "| " + h_name + "#" + std::to_string(dev); //dev name row += std::string(abs(int(headers[0].size() - (row.size() - 1))), ' '); row += std::accumulate(data.begin(), data.end(), std::string("|"), a_data); buffer.push_back(row); } } //Print deliminator row = std::accumulate(headers.begin(), headers.end(), std::string("|"), a_header_footer); buffer.push_back(row); //Print footer row = std::accumulate(headers.begin(), headers.end(), std::string(" "), a_header_footer); buffer.push_back(row); return buffer; } void collect_data(PCM *m, const double delay, const ACCEL_IP accel, std::vector& ctrs) { const uint32_t delay_ms = uint32_t(delay * 1000); SimpleCounterState *before, *after; AcceleratorCounterState *accs_; accs_ = AcceleratorCounterState::getInstance(); const uint32_t dev_count = accs_->getNumOfAccelDevs(); const uint32_t counter_nb = ctrs.size(); uint32_t ctr_index = 0; before = new SimpleCounterState[dev_count*counter_nb]; after = new SimpleCounterState[dev_count*counter_nb]; switch (accel) { case ACCEL_IAA: case ACCEL_DSA: for (uint32_t dev = 0; dev != dev_count; ++dev) { ctr_index = 0; for (auto pctr = ctrs.begin(); pctr != ctrs.end(); ++pctr) { before[dev*counter_nb + ctr_index] = accs_->getAccelCounterState(dev, ctr_index); ctr_index++; } } MySleepMs(delay_ms); for (uint32_t dev = 0; dev != dev_count; ++dev) { ctr_index = 0; for (auto pctr = ctrs.begin();pctr != ctrs.end(); ++pctr) { after[dev*counter_nb + ctr_index] = accs_->getAccelCounterState(dev, ctr_index); uint64_t raw_result = getNumberOfEvents(before[dev*counter_nb + ctr_index], after[dev*counter_nb + ctr_index]); uint64_t trans_result = uint64_t (raw_result * pctr->multiplier / (double) pctr->divider * (1000 / (double) delay_ms)); accel_results[accel][dev][std::pair(pctr->h_id,pctr->v_id)] = trans_result; //std::cout << "collect_data: accel=" << accel << " dev=" << dev << " h_id=" << pctr->h_id << " v_id=" << pctr->v_id << " data=" << std::hex << trans_result << "\n" << std::dec; ctr_index++; } } break; case ACCEL_QAT: MySleepMs(delay_ms); for (uint32_t dev = 0; dev != dev_count; ++dev) { m->controlQATTelemetry(dev, PCM::QAT_TLM_REFRESH); ctr_index = 0; for (auto pctr = ctrs.begin();pctr != ctrs.end(); ++pctr) { after[dev*counter_nb + ctr_index] = accs_->getAccelCounterState(dev, ctr_index); uint64_t raw_result = after[dev*counter_nb + ctr_index].getRawData(); uint64_t trans_result = uint64_t (raw_result * pctr->multiplier / (double) pctr->divider ); accel_results[accel][dev][std::pair(pctr->h_id,pctr->v_id)] = trans_result; //std::cout << "collect_data: accel=" << accel << " dev=" << dev << " h_id=" << pctr->h_id << " v_id=" << pctr->v_id << " data=" << std::hex << trans_result << "\n" << std::dec; ctr_index++; } } break; default: break; } deleteAndNullifyArray(before); deleteAndNullifyArray(after); } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { null_stream nullStream; check_and_set_silent(argc, argv, nullStream); set_signal_handlers(); std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION ; std::cout << "\n This utility measures Sapphire Rapids-SP accelerators information.\n"; std::string program = std::string(argv[0]); bool csv = false; bool human_readable = false; std::string csv_delimiter = ","; std::string output_file; double delay = PCM_DELAY_DEFAULT; ACCEL_IP accel=ACCEL_IAA; //default is IAA bool evtfile = false; std::string specify_evtfile; ACCEL_DEV_LOC_MAPPING loc_map = SOCKET_MAP; //default is socket mapping MainLoop mainLoop; PCM * m; AcceleratorCounterState *accs_; accs_ = AcceleratorCounterState::getInstance(); std::string ev_file_name; while (argc > 1) { argv++; argc--; std::string arg_value; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { //handled in check_and_set_silent continue; } else if (extract_argument_value(*argv, {"-csv-delimiter", "/csv-delimiter"}, arg_value)) { csv_delimiter = std::move(arg_value); } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; output_file = std::move(arg_value); } else if (check_argument_equals(*argv, {"-human-readable", "/human-readable"})) { human_readable = true; } else if (check_argument_equals(*argv, {"-iaa", "/iaa"})) { accel = ACCEL_IAA; } else if (check_argument_equals(*argv, {"-dsa", "/dsa"})) { accel = ACCEL_DSA; } #ifdef __linux__ else if (check_argument_equals(*argv, {"-qat", "/qat"})) { accel = ACCEL_QAT; } else if (check_argument_equals(*argv, {"-numa", "/numa"})) { loc_map = NUMA_MAP; } #endif else if (extract_argument_value(*argv, {"-evt", "/evt"}, arg_value)) { evtfile = true; specify_evtfile = std::move(arg_value); } else if (mainLoop.parseArg(*argv)) { continue; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } print_cpu_details(); #ifdef __linux__ // check kernel version for driver dependency. std::cout << "Info: IDX - Please ensure the required driver(e.g idxd driver for iaa/dsa, qat driver and etc) correct enabled with this system, else the tool may fail to run.\n"; struct utsname sys_info; if (!uname(&sys_info)) { std::string krel_str; uint32 krel_major_ver=0, krel_minor_ver=0; krel_str = sys_info.release; std::vector krel_info = split(krel_str, '.'); std::istringstream iss_krel_major(krel_info[0]); std::istringstream iss_krel_minor(krel_info[1]); iss_krel_major >> std::setbase(0) >> krel_major_ver; iss_krel_minor >> std::setbase(0) >> krel_minor_ver; switch (accel) { case ACCEL_IAA: case ACCEL_DSA: if ((krel_major_ver < 5) || (krel_major_ver == 5 && krel_minor_ver < 11)) { std::cout<< "Warning: IDX - current linux kernel version(" << krel_str << ") is too old, please upgrade it to the latest due to required idxd driver integrated to kernel since 5.11.\n"; } break; default: break; } } #endif try { m = PCM::getInstance(); } catch (std::exception & e) { std::cerr << "Error: " << e.what() << "\n"; exit(EXIT_FAILURE); } if (m->supportIDXAccelDev() == false) { std::cerr << "Error: IDX accelerator is NOT supported with this platform! Program aborted\n"; exit(EXIT_FAILURE); } accs_->setEvents(m,accel,specify_evtfile,evtfile); std::ostream* output = &std::cout; std::fstream file_stream; if (!output_file.empty()) { file_stream.open(output_file.c_str(), std::ios_base::out); output = &file_stream; } accs_->programAccelCounters(); std::vector CTRS= accs_->getCounters(); mainLoop([&]() { collect_data(m, delay, accel, CTRS); std::vector display_buffer = csv ? build_csv( accel, CTRS, human_readable, csv_delimiter, accel_results, loc_map) : build_display( accel, CTRS, accel_results, loc_map); display(display_buffer, *output); return true; }); file_stream.close(); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-bw-histogram.sh000077500000000000000000000044421475730356400167150ustar00rootroot00000000000000#!/bin/sh if [ "$#" -ne 1 ]; then echo echo "Usage: $0 " >&2 echo echo "duration is in the same format as the argument of sleep command:" echo sleep --help exit 1 fi out=bw-tmp rm $out echo echo ========= CHECKING FOR PMM SUPPORT ========= echo ./pcm-memory -pmm -- sleep 1 >tmp 2>&1 dram_only=`cat tmp | grep "PMM traffic metrics are not available" | wc -l` rm tmp if [ $dram_only -gt 0 ] then echo PMM support is not present else echo PMM support is present fi echo echo ========= MEASURING ========= echo if [ $dram_only -gt 0 ] then chrt --rr 1 nice --adjustment=-20 ./pcm-memory 0.005 -nc -csv=$out -- sleep $1 else chrt --rr 1 nice --adjustment=-20 ./pcm-memory 0.005 -pmm -nc -csv=$out -- sleep $1 fi cat $out | sed 's/;/,/g' > $out.csv num_sockets=`lscpu | grep Socket | awk '{print $2}'` echo echo ======== POST-PROCESSING ==== echo for s in `seq 0 $(($num_sockets-1))`; do echo ============ Socket $s ============; if [ $dram_only -gt 0 ] then cat $out.csv | cut -d, -f$((4*s+6)) | awk '{ n=n+1; f[int($1/10000)] = f[int($1/10000)] + 1; } END { print "bandwidth(GB/s),count,time(%),chart"; for (i=0; i<32; i++) { if(i in f){ v=100.*f[i]/n; printf "%d-%d\t,%d\t,%3.2f\t,",i*10,(i+1)*10,f[i],v; for (j=0; j #ifdef _MSC_VER #define strtok_r strtok_s #include #include "windows/windriver.h" #else #include #include #include // for gettimeofday() #endif #include #include #include #include #include #include #include #include #include "cpucounters.h" #include "utils.h" #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs #define MAX_CORES 4096 using namespace std; using namespace pcm; void build_event(const char * argv, EventSelectRegister *reg, int idx); struct CoreEvent { char name[256]; uint64 value; uint64 msr_value; char * description; } events[PERF_MAX_CUSTOM_COUNTERS]; #ifdef PCM_SHARED_LIBRARY extern "C" { static std::shared_ptr globalSysBeforeState, globalSysAfterState; static std::shared_ptr > globalBeforeState, globalAfterState; static std::shared_ptr > globalDummySocketStates; static EventSelectRegister globalRegs[PERF_MAX_COUNTERS]; static PCM::ExtendedCustomCoreEventDescription globalConf; int pcm_c_build_core_event(uint8_t idx, const char * argv) { if(idx > 3) return -1; cout << "building core event " << argv << " " << idx << "\n"; build_event(argv, &globalRegs[idx], idx); return 0; } int pcm_c_init() { PCM * m = PCM::getInstance(); globalSysBeforeState = std::make_shared(); globalSysAfterState = std::make_shared(); globalBeforeState = std::make_shared >(); globalAfterState = std::make_shared >(); globalDummySocketStates = std::make_shared >(); globalConf.fixedCfg = NULL; // default globalConf.nGPCounters = m->getMaxCustomCoreEvents(); globalConf.gpCounterCfg = globalRegs; globalConf.OffcoreResponseMsrValue[0] = events[0].msr_value; globalConf.OffcoreResponseMsrValue[1] = events[1].msr_value; m->resetPMU(); PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &globalConf); if(status == PCM::Success) return 0; else return -1; } void pcm_c_start() { PCM * m = PCM::getInstance(); m->getAllCounterStates(*globalSysBeforeState.get(), *globalDummySocketStates.get(), *globalBeforeState.get()); } void pcm_c_stop() { PCM * m = PCM::getInstance(); m->getAllCounterStates(*globalSysAfterState.get(), *globalDummySocketStates.get(), *globalAfterState.get()); } uint64_t pcm_c_get_cycles(uint32_t core_id) { return getCycles((*globalBeforeState.get())[core_id], (*globalAfterState.get())[core_id]); } uint64_t pcm_c_get_instr(uint32_t core_id) { return getInstructionsRetired((*globalBeforeState.get())[core_id], (*globalAfterState.get())[core_id]); } uint64_t pcm_c_get_core_event(uint32_t core_id, uint32_t event_id) { return getNumberOfCustomEvents(event_id, (*globalBeforeState.get())[core_id], (*globalAfterState.get())[core_id]); } } #endif // PCM_SHARED_LIBRARY void print_usage(const string & progname) { cout << "\n Usage: \n " << progname << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -c | /c => print CPU Model name and exit (used for pmu-query.py)\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " [-e event1] [-e event2] [-e event3] .. => optional list of custom events to monitor\n"; cout << " event description example: cpu/umask=0x01,event=0x05,name=MISALIGN_MEM_REF.LOADS/ \n"; cout << " -yc | --yescores | /yc => enable specific cores to output\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; print_help_force_rtm_abort_mode(41); cout << " Examples:\n"; cout << " " << progname << " 1 => print counters every second without core and socket output\n"; cout << " " << progname << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << progname << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } template void print_custom_stats(const StateType & BeforeState, const StateType & AfterState ,bool csv, uint64 txn_rate) { const uint64 cycles = getCycles(BeforeState, AfterState); const uint64 refCycles = getRefCycles(BeforeState, AfterState); const uint64 instr = getInstructionsRetired(BeforeState, AfterState); if(!csv) { cout << double(instr)/double(cycles); if(txn_rate == 1) { cout << setw(14) << unit_format(instr); cout << setw(11) << unit_format(cycles); cout << setw(12) << unit_format(refCycles); } else { cout << setw(14) << double(instr)/double(txn_rate); cout << setw(11) << double(cycles)/double(txn_rate); cout << setw(12) << double(refCycles) / double(txn_rate); } } else { cout << double(instr)/double(cycles) << ","; cout << double(instr)/double(txn_rate) << ","; cout << double(cycles)/double(txn_rate) << ","; cout << double(refCycles) / double(txn_rate) << ","; } const auto max_ctr = PCM::getInstance()->getMaxCustomCoreEvents(); for (int i = 0; i < max_ctr; ++i) if(!csv) { cout << setw(10); if(txn_rate == 1) cout << unit_format(getNumberOfCustomEvents(i, BeforeState, AfterState)); else cout << double(getNumberOfCustomEvents(i, BeforeState, AfterState))/double(txn_rate); } else cout << double(getNumberOfCustomEvents(i, BeforeState, AfterState))/double(txn_rate) << ","; cout << "\n"; } // emulates scanf %i for hex 0x prefix otherwise assumes dec (no oct support) bool match(const char * subtoken, const char * name, int * result) { std::string sname(name); if (pcm_sscanf(subtoken) >> s_expect(sname + "0x") >> std::hex >> *result) return true; if (pcm_sscanf(subtoken) >> s_expect(sname) >> std::dec >> *result) return true; return false; } void build_event(const char * argv, EventSelectRegister *reg, int idx) { char *token, *subtoken, *saveptr1, *saveptr2; char *str1, *str2; int j, tmp; uint64 tmp2; reg->value = 0; reg->fields.usr = 1; reg->fields.os = 1; reg->fields.enable = 1; /* uint64 apic_int : 1; offcore_rsp=2,period=10000 */ for (j = 1, str1 = (char*) argv; ; j++, str1 = NULL) { token = strtok_r(str1, "/", &saveptr1); if (token == NULL) break; printf("%d: %s\n", j, token); if(strncmp(token,"cpu",3) == 0) continue; for (str2 = token; ; str2 = NULL) { tmp = -1; subtoken = strtok_r(str2, ",", &saveptr2); if (subtoken == NULL) break; if(match(subtoken,"event=",&tmp)) reg->fields.event_select = tmp; else if(match(subtoken,"umask=",&tmp)) reg->fields.umask = tmp; else if(strcmp(subtoken,"edge") == 0) reg->fields.edge = 1; else if(match(subtoken,"any=",&tmp)) reg->fields.any_thread = tmp; else if(match(subtoken,"inv=",&tmp)) reg->fields.invert = tmp; else if(match(subtoken,"cmask=",&tmp)) reg->fields.cmask = tmp; else if(match(subtoken,"in_tx=",&tmp)) reg->fields.in_tx = tmp; else if(match(subtoken,"in_tx_cp=",&tmp)) reg->fields.in_txcp = tmp; else if(match(subtoken,"pc=",&tmp)) reg->fields.pin_control = tmp; else if(pcm_sscanf(subtoken) >> s_expect("offcore_rsp=") >> std::hex >> tmp2) { if(idx >= 2) { cerr << "offcore_rsp must specify in first or second event only. idx=" << idx << "\n"; throw idx; } events[idx].msr_value = tmp2; } else if(pcm_sscanf(subtoken) >> s_expect("name=") >> setw(255) >> events[idx].name) { if (check_for_injections(events[idx].name)) throw events[idx].name; } else { cerr << "Event '" << subtoken << "' is not supported. See the list of supported events\n"; throw subtoken; } } } events[idx].value = reg->value; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; std::cout.rdbuf(&nullStream1); std::cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor: Core Monitoring Utility \n"; cerr << "\n"; double delay = -1.0; char *sysCmd = NULL; char **sysArgv = NULL; uint32 cur_event = 0; bool csv = false; uint64 txn_rate = 1; MainLoop mainLoop; string program = string(argv[0]); EventSelectRegister regs[PERF_MAX_COUNTERS]; PCM::ExtendedCustomCoreEventDescription conf; bool show_partial_core_output = false; std::bitset ycores; PCM * m = PCM::getInstance(); conf.fixedCfg = NULL; // default conf.nGPCounters = m->getMaxCustomCoreEvents(); conf.gpCounterCfg = regs; if(argc > 1) do { argv++; argc--; string arg_value; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"-c", "/c"})) { cout << m->getCPUFamilyModelString() << "\n"; exit(EXIT_SUCCESS); } else if (check_argument_equals(*argv, {"-txn", "/txn"})) { argv++; argc--; txn_rate = strtoull(*argv,NULL,10); cout << "txn_rate set to " << txn_rate << "\n"; continue; } else if (check_argument_equals(*argv, {"--yescores", "-yc", "/yc"})) { argv++; argc--; show_partial_core_output = true; if(*argv == NULL) { cerr << "Error: --yescores requires additional argument.\n"; exit(EXIT_FAILURE); } std::stringstream ss(*argv); while(ss.good()) { string s; int core_id; std::getline(ss, s, ','); if(s.empty()) continue; core_id = atoi(s.c_str()); if(core_id > MAX_CORES) { cerr << "Core ID:" << core_id << " exceed maximum range " << MAX_CORES << ", program abort\n"; exit(EXIT_FAILURE); } ycores.set(atoi(s.c_str()),true); } if(m->getNumCores() > MAX_CORES) { cerr << "Error: --yescores option is enabled, but #define MAX_CORES " << MAX_CORES << " is less than m->getNumCores() = " << m->getNumCores() << "\n"; cerr << "There is a potential to crash the system. Please increase MAX_CORES to at least " << m->getNumCores() << " and re-enable this option.\n"; exit(EXIT_FAILURE); } continue; } else if (check_argument_equals(*argv, {"-e"})) { argv++; argc--; if(cur_event >= conf.nGPCounters) { cerr << "At most " << conf.nGPCounters << " events are allowed\n"; exit(EXIT_FAILURE); } try { build_event(*argv,®s[cur_event],cur_event); cur_event++; } catch (...) { exit(EXIT_FAILURE); } continue; } else if (CheckAndForceRTMAbortMode(*argv, m)) { continue; } else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } while(argc > 1); // end of command line parsing loop if ( cur_event == 0 ) cerr << "WARNING: you did not provide any custom events, is this intentional?\n"; conf.OffcoreResponseMsrValue[0] = events[0].msr_value; conf.OffcoreResponseMsrValue[1] = events[1].msr_value; PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf); m->checkError(status); print_cpu_details(); uint64 BeforeTime = 0, AfterTime = 0; SystemCounterState SysBeforeState, SysAfterState; const uint32 ncores = m->getNumCores(); std::vector BeforeState, AfterState; std::vector DummySocketStates; if ( (sysCmd != NULL) && (delay<=0.0) ) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (csv) { if( delay<=0.0 ) delay = PCM_DELAY_DEFAULT; } else { // for non-CSV mode delay < 1.0 does not make a lot of practical sense: // hard to read from the screen, or // in case delay is not provided in command line => set default if( ((delay<1.0) && (delay>0.0)) || (delay<=0.0) ) delay = PCM_DELAY_DEFAULT; } cerr << "Update every " << delay << " seconds\n"; std::cout.precision(2); std::cout << std::fixed; BeforeTime = m->getTickCount(); m->getAllCounterStates(SysBeforeState, DummySocketStates, BeforeState); if( sysCmd != NULL ) { MySystem(sysCmd, sysArgv); } mainLoop([&]() { if(!csv) cout << std::flush; calibratedSleep(delay, sysCmd, mainLoop, m); AfterTime = m->getTickCount(); m->getAllCounterStates(SysAfterState, DummySocketStates, AfterState); cout << "Time elapsed: " << dec << fixed << AfterTime-BeforeTime << " ms\n"; cout << "txn_rate: " << txn_rate << "\n"; //cout << "Called sleep function for " << dec << fixed << delay_ms << " ms\n"; for(uint32 i=0;iisCoreOnline(i) == false || (show_partial_core_output && ycores.test(i) == false)) continue; if(csv) cout << i << ","; else cout << " " << setw(3) << i << " " << setw(2) ; print_custom_stats(BeforeState[i], AfterState[i], csv, txn_rate); } if(csv) cout << "*,"; else { cout << "---------------------------------------------------------------------------------------------------------------------------------\n"; cout << " * "; } print_custom_stats(SysBeforeState, SysAfterState, csv, txn_rate); std::cout << "\n"; swap(BeforeTime, AfterTime); swap(BeforeState, AfterState); swap(SysBeforeState, SysAfterState); if ( m->isBlocked() ) { // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-iio.cpp000066400000000000000000002573471475730356400152550ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2017-2022, Intel Corporation // written by Patrick Lu, // Aaron Cruz // and others #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include #include // std::length_error #include #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include "lspci.h" #include "utils.h" using namespace std; using namespace pcm; #define PCM_DELAY_DEFAULT 3.0 // in seconds #define QAT_DID 0x18DA #define NIS_DID 0x18D1 #define HQM_DID 0x270B #define GRR_QAT_VRP_DID 0x5789 // Virtual Root Port to integrated QuickAssist (GRR QAT) #define GRR_NIS_VRP_DID 0x5788 // VRP to Network Interface and Scheduler (GRR NIS) #define ROOT_BUSES_OFFSET 0xCC #define ROOT_BUSES_OFFSET_2 0xD0 #define SKX_SOCKETID_UBOX_DID 0x2014 #define SKX_UBOX_DEVICE_NUM 0x08 #define SKX_UBOX_FUNCTION_NUM 0x02 #define SKX_BUS_NUM_STRIDE 8 //the below LNID and GID applies to Skylake Server #define SKX_UNC_SOCKETID_UBOX_LNID_OFFSET 0xC0 #define SKX_UNC_SOCKETID_UBOX_GID_OFFSET 0xD4 static const std::string iio_stack_names[6] = { "IIO Stack 0 - CBDMA/DMI ", "IIO Stack 1 - PCIe0 ", "IIO Stack 2 - PCIe1 ", "IIO Stack 3 - PCIe2 ", "IIO Stack 4 - MCP0 ", "IIO Stack 5 - MCP1 " }; static const std::string skx_iio_stack_names[6] = { "IIO Stack 0 - CBDMA/DMI ", "IIO Stack 1 - PCIe0 ", "IIO Stack 2 - PCIe1 ", "IIO Stack 3 - PCIe2 ", "IIO Stack 4 - MCP0 ", "IIO Stack 5 - MCP1 " }; static const std::string icx_iio_stack_names[6] = { "IIO Stack 0 - PCIe0 ", "IIO Stack 1 - PCIe1 ", "IIO Stack 2 - MCP ", "IIO Stack 3 - PCIe2 ", "IIO Stack 4 - PCIe3 ", "IIO Stack 5 - CBDMA/DMI " }; static const std::string icx_d_iio_stack_names[6] = { "IIO Stack 0 - MCP ", "IIO Stack 1 - PCIe0 ", "IIO Stack 2 - CBDMA/DMI ", "IIO Stack 3 - PCIe2 ", "IIO Stack 4 - PCIe3 ", "IIO Stack 5 - PCIe1 " }; static const std::string snr_iio_stack_names[5] = { "IIO Stack 0 - QAT ", "IIO Stack 1 - CBDMA/DMI ", "IIO Stack 2 - NIS ", "IIO Stack 3 - HQM ", "IIO Stack 4 - PCIe " }; #define ICX_CBDMA_DMI_SAD_ID 0 #define ICX_MCP_SAD_ID 3 #define ICX_PCH_PART_ID 0 #define ICX_CBDMA_PART_ID 3 #define SNR_ICX_SAD_CONTROL_CFG_OFFSET 0x3F4 #define SNR_ICX_MESH2IIO_MMAP_DID 0x09A2 #define ICX_VMD_PCI_DEVNO 0x00 #define ICX_VMD_PCI_FUNCNO 0x05 static const std::map icx_sad_to_pmu_id_mapping = { { ICX_CBDMA_DMI_SAD_ID, 5 }, { 1, 0 }, { 2, 1 }, { ICX_MCP_SAD_ID, 2 }, { 4, 3 }, { 5, 4 } }; static const std::map icx_d_sad_to_pmu_id_mapping = { { ICX_CBDMA_DMI_SAD_ID, 2 }, { 1, 5 }, { 2, 1 }, { ICX_MCP_SAD_ID, 0 }, { 4, 3 }, { 5, 4 } }; #define SNR_ACCELERATOR_PART_ID 4 #define SNR_ROOT_PORT_A_DID 0x334A #define SNR_CBDMA_DMI_SAD_ID 0 #define SNR_PCIE_GEN3_SAD_ID 1 #define SNR_HQM_SAD_ID 2 #define SNR_NIS_SAD_ID 3 #define SNR_QAT_SAD_ID 4 static const std::map snr_sad_to_pmu_id_mapping = { { SNR_CBDMA_DMI_SAD_ID, 1 }, { SNR_PCIE_GEN3_SAD_ID, 4 }, { SNR_HQM_SAD_ID , 3 }, { SNR_NIS_SAD_ID , 2 }, { SNR_QAT_SAD_ID , 0 } }; #define HQMV2_DID 0x2710 // Hardware Queue Manager v2 #define HQMV25_DID 0x2714 // Hardware Queue Manager v2.5 #define DSA_DID 0x0b25 // Data Streaming Accelerator (DSA) #define IAX_DID 0x0cfe // In-Memory Database Analytics Accelerator (IAX) #define QATV2_DID 0x4940 // QuickAssist (CPM) v2 #define SPR_DMI_PART_ID 7 #define SPR_XCC_HQM_PART_ID 5 #define SPR_MCC_HQM_PART_ID 4 #define SPR_XCC_QAT_PART_ID 4 #define SPR_MCC_QAT_PART_ID 5 #define SPR_SAD_CONTROL_CFG_OFFSET SNR_ICX_SAD_CONTROL_CFG_OFFSET #define SPR_PCU_CR3_DID 0x325b #define SPR_PCU_CR3_REG_DEVICE 0x1e #define SPR_PCU_CR3_REG_FUNCTION 0x03 #define SPR_CAPID4_OFFSET 0x94 #define SPR_CAPID4_GET_PHYSICAL_CHOP(capid4) ((capid4 >> 6) & 3) #define SPR_PHYSICAL_CHOP_XCC 0b11 #define SPR_PHYSICAL_CHOP_MCC 0b01 #define SPR_XCC_DMI_PMON_ID 1 #define SPR_XCC_PCIE_GEN5_0_PMON_ID 2 #define SPR_XCC_PCIE_GEN5_1_PMON_ID 4 #define SPR_XCC_PCIE_GEN5_2_PMON_ID 6 #define SPR_XCC_PCIE_GEN5_3_PMON_ID 7 #define SPR_XCC_PCIE_GEN5_4_PMON_ID 9 #define SPR_XCC_IDX0_PMON_ID 0 #define SPR_XCC_IDX1_PMON_ID 3 #define SPR_XCC_IDX2_PMON_ID 5 #define SPR_XCC_IDX3_PMON_ID 8 const std::map spr_xcc_sad_to_pmu_id_mapping = { { 0, SPR_XCC_DMI_PMON_ID }, { 1, SPR_XCC_PCIE_GEN5_0_PMON_ID }, { 2, SPR_XCC_PCIE_GEN5_1_PMON_ID }, { 3, SPR_XCC_PCIE_GEN5_2_PMON_ID }, { 4, SPR_XCC_PCIE_GEN5_3_PMON_ID }, { 5, SPR_XCC_PCIE_GEN5_4_PMON_ID }, { 8, SPR_XCC_IDX0_PMON_ID }, { 9, SPR_XCC_IDX1_PMON_ID }, { 10, SPR_XCC_IDX2_PMON_ID }, { 11, SPR_XCC_IDX3_PMON_ID } }; #define SPR_MCC_DMI_PMON_ID 10 #define SPR_MCC_PCIE_GEN5_0_PMON_ID 0 // assumption #define SPR_MCC_PCIE_GEN5_1_PMON_ID 1 #define SPR_MCC_PCIE_GEN5_2_PMON_ID 2 #define SPR_MCC_PCIE_GEN5_3_PMON_ID 4 // assumption #define SPR_MCC_PCIE_GEN5_4_PMON_ID 5 #define SPR_MCC_IDX0_PMON_ID 3 const std::map spr_mcc_sad_to_pmu_id_mapping = { { 0, SPR_MCC_PCIE_GEN5_0_PMON_ID }, { 1, SPR_MCC_PCIE_GEN5_1_PMON_ID }, { 2, SPR_MCC_PCIE_GEN5_2_PMON_ID }, { 3, SPR_MCC_DMI_PMON_ID }, { 4, SPR_MCC_PCIE_GEN5_3_PMON_ID }, { 5, SPR_MCC_PCIE_GEN5_4_PMON_ID }, { 8, SPR_MCC_IDX0_PMON_ID }, }; static const std::string spr_xcc_iio_stack_names[] = { "IIO Stack 0 - IDX0 ", "IIO Stack 1 - DMI ", "IIO Stack 2 - PCIe0 ", "IIO Stack 3 - IDX1 ", "IIO Stack 4 - PCIe1 ", "IIO Stack 5 - IDX2 ", "IIO Stack 6 - PCIe2 ", "IIO Stack 7 - PCIe3", "IIO Stack 8 - IDX3 ", "IIO Stack 9 - PCIe4", "IIO Stack 10 - NONE ", "IIO Stack 11 - NONE ", }; /* * SPR MCC has 7 I/O stacks but PMON block for DMI has ID number 10. * And just to follow such enumeration keep Stack 10 for DMI. */ static const std::string spr_mcc_iio_stack_names[] = { "IIO Stack 0 - PCIe0 ", "IIO Stack 1 - PCIe1 ", "IIO Stack 2 - PCIe2 ", "IIO Stack 3 - IDX0 ", "IIO Stack 4 - PCIe3 ", "IIO Stack 5 - PCIe4 ", "IIO Stack 6 - NONE ", "IIO Stack 7 - NONE ", "IIO Stack 8 - NONE ", "IIO Stack 9 - NONE ", "IIO Stack 10 - DMI ", }; // MS2IOSF stack IDs in CHA notation #define GRR_PCH_DSA_GEN4_SAD_ID 0 #define GRR_DLB_SAD_ID 1 #define GRR_NIS_QAT_SAD_ID 2 #define GRR_PCH_DSA_GEN4_PMON_ID 2 #define GRR_DLB_PMON_ID 1 #define GRR_NIS_QAT_PMON_ID 0 // Stack 0 contains PCH, DSA and CPU PCIe Gen4 Complex const std::map grr_sad_to_pmu_id_mapping = { { GRR_PCH_DSA_GEN4_SAD_ID, GRR_PCH_DSA_GEN4_PMON_ID }, { GRR_DLB_SAD_ID, GRR_DLB_PMON_ID }, { GRR_NIS_QAT_SAD_ID, GRR_NIS_QAT_PMON_ID }, }; #define GRR_DLB_PART_ID 0 #define GRR_NIS_PART_ID 0 #define GRR_QAT_PART_ID 1 static const std::string grr_iio_stack_names[3] = { "IIO Stack 0 - NIS/QAT ", "IIO Stack 1 - HQM ", "IIO Stack 2 - PCH/DSA/PCIe " }; #define EMR_DMI_PMON_ID 7 #define EMR_PCIE_GEN5_0_PMON_ID 1 #define EMR_PCIE_GEN5_1_PMON_ID 2 #define EMR_PCIE_GEN5_2_PMON_ID 3 #define EMR_PCIE_GEN5_3_PMON_ID 8 #define EMR_PCIE_GEN5_4_PMON_ID 6 #define EMR_IDX0_PMON_ID 0 #define EMR_IDX1_PMON_ID 4 #define EMR_IDX2_PMON_ID 5 #define EMR_IDX3_PMON_ID 9 const std::map emr_sad_to_pmu_id_mapping = { { 0, EMR_DMI_PMON_ID }, { 1, EMR_PCIE_GEN5_0_PMON_ID }, { 2, EMR_PCIE_GEN5_1_PMON_ID }, { 3, EMR_PCIE_GEN5_2_PMON_ID }, { 4, EMR_PCIE_GEN5_3_PMON_ID }, { 5, EMR_PCIE_GEN5_4_PMON_ID }, { 8, EMR_IDX0_PMON_ID }, { 9, EMR_IDX1_PMON_ID }, { 10, EMR_IDX2_PMON_ID }, { 11, EMR_IDX3_PMON_ID } }; static const std::string emr_iio_stack_names[] = { "IIO Stack 0 - IDX0 ", "IIO Stack 1 - PCIe3 ", "IIO Stack 2 - PCIe0 ", "IIO Stack 3 - IDX1 ", "IIO Stack 4 - PCIe1 ", "IIO Stack 5 - IDX2 ", "IIO Stack 6 - PCIe2 ", "IIO Stack 7 - DMI", "IIO Stack 8 - IDX3 ", "IIO Stack 9 - PCIe4", "IIO Stack 10 - NONE ", "IIO Stack 11 - NONE ", }; enum EagleStreamPlatformStacks { esDMI = 0, esPCIe0, esPCIe1, esPCIe2, esPCIe3, esPCIe4, esDINO0, esDINO1, esDINO2, esDINO3, esEndOfList }; const std::vector spr_xcc_stacks_enumeration = { /* esDMI */ SPR_XCC_DMI_PMON_ID, /* esPCIe0 */ SPR_XCC_PCIE_GEN5_0_PMON_ID, /* esPCIe1 */ SPR_XCC_PCIE_GEN5_1_PMON_ID, /* esPCIe2 */ SPR_XCC_PCIE_GEN5_2_PMON_ID, /* esPCIe3 */ SPR_XCC_PCIE_GEN5_3_PMON_ID, /* esPCIe4 */ SPR_XCC_PCIE_GEN5_4_PMON_ID, /* esDINO0 */ SPR_XCC_IDX0_PMON_ID, /* esDINO1 */ SPR_XCC_IDX1_PMON_ID, /* esDINO2 */ SPR_XCC_IDX2_PMON_ID, /* esDINO3 */ SPR_XCC_IDX3_PMON_ID, }; const std::vector spr_mcc_stacks_enumeration = { /* esDMI */ SPR_MCC_DMI_PMON_ID, /* esPCIe0 */ SPR_MCC_PCIE_GEN5_0_PMON_ID, /* esPCIe1 */ SPR_MCC_PCIE_GEN5_1_PMON_ID, /* esPCIe2 */ SPR_MCC_PCIE_GEN5_2_PMON_ID, /* esPCIe3 */ SPR_MCC_PCIE_GEN5_3_PMON_ID, /* esPCIe4 */ SPR_MCC_PCIE_GEN5_4_PMON_ID, /* esDINO0 */ SPR_MCC_IDX0_PMON_ID, }; const std::vector emr_stacks_enumeration = { /* esDMI */ EMR_DMI_PMON_ID, /* esPCIe0 */ EMR_PCIE_GEN5_0_PMON_ID, /* esPCIe1 */ EMR_PCIE_GEN5_1_PMON_ID, /* esPCIe2 */ EMR_PCIE_GEN5_2_PMON_ID, /* esPCIe3 */ EMR_PCIE_GEN5_3_PMON_ID, /* esPCIe4 */ EMR_PCIE_GEN5_4_PMON_ID, /* esDINO0 */ EMR_IDX0_PMON_ID, /* esDINO1 */ EMR_IDX1_PMON_ID, /* esDINO2 */ EMR_IDX2_PMON_ID, /* esDINO3 */ EMR_IDX3_PMON_ID, }; enum class EagleStreamSupportedTypes { esInvalid = -1, esSprXcc, esSprMcc, esEmrXcc }; typedef EagleStreamSupportedTypes estype; const std::map> es_stacks_enumeration = { {estype::esSprXcc, spr_xcc_stacks_enumeration}, {estype::esSprMcc, spr_mcc_stacks_enumeration}, {estype::esEmrXcc, emr_stacks_enumeration }, }; const std::map es_stack_names = { {estype::esSprXcc, spr_xcc_iio_stack_names}, {estype::esSprMcc, spr_mcc_iio_stack_names}, {estype::esEmrXcc, emr_iio_stack_names }, }; const std::map> es_sad_to_pmu_id_mapping = { {estype::esSprXcc, spr_xcc_sad_to_pmu_id_mapping}, {estype::esSprMcc, spr_mcc_sad_to_pmu_id_mapping}, {estype::esEmrXcc, emr_sad_to_pmu_id_mapping }, }; #define SRF_PE0_PMON_ID 3 #define SRF_PE1_PMON_ID 4 #define SRF_PE2_PMON_ID 2 #define SRF_PE3_PMON_ID 5 /* * There are platform configuration when FlexUPI stacks (stacks 5 and 6) are enabled as * PCIe stack and PCIe ports are disabled (ports 2 and 3) and vice sersa. See details here: * In these cases the PMON IDs are different. * So, defines with _FLEX_ are applicable for cases when FlexUPI stacks * are working as PCIe ports. */ #define SRF_PE4_PMON_ID 11 #define SRF_FLEX_PE4_PMON_ID 13 #define SRF_PE5_PMON_ID 12 #define SRF_FLEX_PE5_PMON_ID 10 #define SRF_PE6_PMON_ID 0 #define SRF_PE7_PMON_ID 7 #define SRF_PE8_PMON_ID 8 #define SRF_HC0_PMON_ID 1 #define SRF_HC1_PMON_ID 6 #define SRF_HC2_PMON_ID 9 #define SRF_HC3_PMON_ID 14 #define SRF_PE0_SAD_BUS_ID 2 #define SRF_PE1_SAD_BUS_ID 3 #define SRF_PE2_SAD_BUS_ID 1 #define SRF_PE3_SAD_BUS_ID 4 #define SRF_PE4_SAD_BUS_ID 29 #define SRF_FLEX_PE4_SAD_BUS_ID SRF_PE4_SAD_BUS_ID #define SRF_PE5_SAD_BUS_ID 26 #define SRF_FLEX_PE5_SAD_BUS_ID SRF_PE5_SAD_BUS_ID #define SRF_PE6_SAD_BUS_ID 0 // UPI0 #define SRF_PE7_SAD_BUS_ID 5 // UPI1 #define SRF_PE8_SAD_BUS_ID 28 // UPI2 #define SRF_UBOXA_SAD_BUS_ID 30 #define SRF_UBOXB_SAD_BUS_ID 31 const std::set srf_pcie_stacks({ SRF_PE0_SAD_BUS_ID, SRF_PE1_SAD_BUS_ID, SRF_PE2_SAD_BUS_ID, SRF_PE3_SAD_BUS_ID, SRF_PE4_SAD_BUS_ID, SRF_FLEX_PE4_SAD_BUS_ID, SRF_PE5_SAD_BUS_ID, SRF_FLEX_PE5_SAD_BUS_ID, SRF_PE6_SAD_BUS_ID, SRF_PE7_SAD_BUS_ID, SRF_PE8_SAD_BUS_ID, }); #define SRF_HC0_SAD_BUS_ID 8 #define SRF_HC1_SAD_BUS_ID 12 #define SRF_HC2_SAD_BUS_ID 20 #define SRF_HC3_SAD_BUS_ID 16 const std::map srf_sad_to_pmu_id_mapping = { { SRF_PE0_SAD_BUS_ID, SRF_PE0_PMON_ID }, { SRF_PE1_SAD_BUS_ID, SRF_PE1_PMON_ID }, { SRF_PE2_SAD_BUS_ID, SRF_PE2_PMON_ID }, { SRF_PE3_SAD_BUS_ID, SRF_PE3_PMON_ID }, { SRF_PE4_SAD_BUS_ID, SRF_PE4_PMON_ID }, { SRF_FLEX_PE4_SAD_BUS_ID, SRF_FLEX_PE4_PMON_ID }, { SRF_PE5_SAD_BUS_ID, SRF_PE5_PMON_ID }, { SRF_FLEX_PE5_SAD_BUS_ID, SRF_FLEX_PE5_PMON_ID }, { SRF_PE6_SAD_BUS_ID, SRF_PE6_PMON_ID }, { SRF_PE7_SAD_BUS_ID, SRF_PE7_PMON_ID }, { SRF_PE8_SAD_BUS_ID, SRF_PE8_PMON_ID }, { SRF_HC0_SAD_BUS_ID, SRF_HC0_PMON_ID }, { SRF_HC1_SAD_BUS_ID, SRF_HC1_PMON_ID }, { SRF_HC2_SAD_BUS_ID, SRF_HC2_PMON_ID }, { SRF_HC3_SAD_BUS_ID, SRF_HC3_PMON_ID }, }; #define SRF_DSA_IAX_PART_NUMBER 0 #define SRF_HQM_PART_NUMBER 5 #define SRF_QAT_PART_NUMBER 4 static const std::string srf_iio_stack_names[] = { "IIO Stack 0 - PCIe6 ", // SRF_PE6_PMON_ID 0 "IIO Stack 1 - HCx0 ", // SRF_HC0_PMON_ID 1 "IIO Stack 2 - PCIe2 ", // SRF_PE2_PMON_ID 2 "IIO Stack 3 - PCIe0 ", // SRF_PE0_PMON_ID 3 "IIO Stack 4 - PCIe1 ", // SRF_PE1_PMON_ID 4 "IIO Stack 5 - PCIe3 ", // SRF_PE3_PMON_ID 5 "IIO Stack 6 - HCx1 ", // SRF_HC1_PMON_ID 6 "IIO Stack 7 - PCIe7 ", // SRF_PE7_PMON_ID 7 "IIO Stack 8 - PCIe8 ", // SRF_PE8_PMON_ID 8 "IIO Stack 9 - HCx3 ", // SRF_HC3_PMON_ID 9 "IIO Stack 10 - Flex PCIe5", // SRF_FLEX_PE5_PMON_ID 10 "IIO Stack 11 - PCIe4 ", // SRF_PE4_PMON_ID 11 "IIO Stack 12 - PCIe5 ", // SRF_PE5_PMON_ID 12 "IIO Stack 13 - Flex PCIe4", // SRF_FLEX_PE4_PMON_ID 13 "IIO Stack 14 - HCx2 ", // SRF_HC2_PMON_ID 14 }; const std::string generate_stack_str(const int unit) { static const std::string stack_str = "Stack "; std::stringstream ss; ss << stack_str << std::setw(2) << unit; return ss.str(); } struct iio_counter : public counter { std::vector data; }; result_content results; typedef struct { PCM *m; iio_counter ctr; vector ctrs; } iio_evt_parse_context; vector combine_stack_name_and_counter_names(string stack_name, const map>> &nameMap) { vector v; vector tmp(nameMap.size()); v.push_back(stack_name); for (std::map>>::const_iterator iunit = nameMap.begin(); iunit != nameMap.end(); ++iunit) { string h_name = iunit->first; int h_id = (iunit->second).first; tmp[h_id] = h_name; //cout << "h_id:" << h_id << " name:" << h_name << "\n"; } //XXX: How to simplify and just combine tmp & v? for (uint32_t i = 0; i < nameMap.size(); i++) { v.push_back(tmp[i]); } return v; } string build_pci_header(const PCIDB & pciDB, uint32_t column_width, const struct pci &p, int part = -1, uint32_t level = 0) { string s = "|"; char bdf_buf[32]; char speed_buf[10]; char vid_did_buf[10]; char device_name_buf[128]; snprintf(bdf_buf, sizeof(bdf_buf), "%04X:%02X:%02X.%1d", p.bdf.domainno, p.bdf.busno, p.bdf.devno, p.bdf.funcno); snprintf(speed_buf, sizeof(speed_buf), "Gen%1d x%-2d", p.link_speed, p.link_width); snprintf(vid_did_buf, sizeof(vid_did_buf), "%04X:%04X", p.vendor_id, p.device_id); snprintf(device_name_buf, sizeof(device_name_buf), "%s %s", (pciDB.first.count(p.vendor_id) > 0)?pciDB.first.at(p.vendor_id).c_str():"unknown vendor", (pciDB.second.count(p.vendor_id) > 0 && pciDB.second.at(p.vendor_id).count(p.device_id) > 0)?pciDB.second.at(p.vendor_id).at(p.device_id).c_str():"unknown device" ); s += bdf_buf; s += '|'; s += speed_buf; s += '|'; s += vid_did_buf; s += " "; s += device_name_buf; if (!p.parts_no.empty()) { s += "; Part: "; for (auto& part : p.parts_no) { s += std::to_string(part) + ", "; } s += "\b\b "; } /* row with data */ if (part >= 0) { s.insert(1,"P" + std::to_string(part) + " "); s += std::string(column_width - (s.size()-1), ' '); } else { /* row without data, just child pci device */ s.insert(0, std::string(4*level, ' ')); } return s; } void build_pci_tree(vector &buffer, const PCIDB & pciDB, uint32_t column_width, const struct pci &p, int part, uint32_t level = 0) { string row; for (const auto& child : p.child_pci_devs) { row = build_pci_header(pciDB, column_width, child, part, level); buffer.push_back(row); if (child.hasChildDevices()) build_pci_tree(buffer, pciDB, column_width, child, part, level + 1); } } vector build_display(vector& iios, vector& ctrs, const PCIDB& pciDB, const map>> &nameMap) { vector buffer; vector headers; vector data; uint64_t header_width; string row; for (auto socket = iios.cbegin(); socket != iios.cend(); ++socket) { buffer.push_back("Socket" + std::to_string(socket->socket_id)); for (auto stack = socket->stacks.cbegin(); stack != socket->stacks.cend(); ++stack) { auto stack_id = stack->iio_unit_id; headers = combine_stack_name_and_counter_names(stack->stack_name, nameMap); //Print first row row = std::accumulate(headers.begin(), headers.end(), string(" "), a_header_footer); header_width = row.size(); buffer.push_back(row); //Print a_title row = std::accumulate(headers.begin(), headers.end(), string("|"), a_title); buffer.push_back(row); //Print deliminator row = std::accumulate(headers.begin(), headers.end(), string("|"), a_header_footer); buffer.push_back(row); //Print data std::map> v_sort; //re-organize data collection to be row wise for (std::vector::iterator counter = ctrs.begin(); counter != ctrs.end(); ++counter) { v_sort[counter->v_id][counter->h_id] = &(*counter); } for (std::map>::const_iterator vunit = v_sort.cbegin(); vunit != v_sort.cend(); ++vunit) { map h_array = vunit->second; uint32_t vv_id = vunit->first; vector h_data; string v_name = h_array[0]->v_event_name; for (map::const_iterator hunit = h_array.cbegin(); hunit != h_array.cend(); ++hunit) { uint32_t hh_id = hunit->first; uint64_t raw_data = hunit->second->data[0][socket->socket_id][stack_id][std::pair(hh_id,vv_id)]; h_data.push_back(raw_data); } data = prepare_data(h_data, headers); row = "| " + v_name; row += string(abs(int(headers[0].size() - (row.size() - 1))), ' '); row += std::accumulate(data.begin(), data.end(), string("|"), a_data); buffer.push_back(row); } //Print deliminator row = std::accumulate(headers.begin(), headers.end(), string("|"), a_header_footer); buffer.push_back(row); //Print pcie devices for (const auto& part : stack->parts) { uint8_t level = 1; for (const auto& pci_device : part.child_pci_devs) { row = build_pci_header(pciDB, (uint32_t)header_width, pci_device, -1, level); buffer.push_back(row); if (pci_device.hasChildDevices()) { build_pci_tree(buffer, pciDB, (uint32_t)header_width, pci_device, -1, level + 1); } else if (pci_device.header_type == 1) { level++; } } } //Print footer row = std::accumulate(headers.begin(), headers.end(), string(" "), a_header_footer); buffer.push_back(row); } } return buffer; } std::string get_root_port_dev(const bool show_root_port, int part_id, const pcm::iio_stack *stack) { char tmp[9] = " "; std::string rp_pci; if (!show_root_port) return rp_pci; for (auto part = stack->parts.begin(); part != stack->parts.end(); part = std::next(part)) { if (part->part_id == part_id) { std::snprintf(tmp, sizeof(tmp), "%02x:%02x.%x", part->root_pci_dev.bdf.busno, part->root_pci_dev.bdf.devno, part->root_pci_dev.bdf.funcno); break; } } rp_pci.append(tmp); return rp_pci; } vector build_csv(vector& iios, vector& ctrs, const bool human_readable, const bool show_root_port, const std::string& csv_delimiter, const map>> &nameMap) { vector result; vector current_row; auto header = combine_stack_name_and_counter_names("Part", nameMap); header.insert(header.begin(), "Name"); if (show_root_port) header.insert(header.begin(), "Root Port"); header.insert(header.begin(), "Socket"); result.push_back(build_csv_row(header, csv_delimiter)); std::map> v_sort; //re-organize data collection to be row wise size_t max_name_width = 0; for (std::vector::iterator counter = ctrs.begin(); counter != ctrs.end(); ++counter) { v_sort[counter->v_id][counter->h_id] = &(*counter); max_name_width = (std::max)(max_name_width, counter->v_event_name.size()); } for (auto socket = iios.cbegin(); socket != iios.cend(); ++socket) { for (auto stack = socket->stacks.cbegin(); stack != socket->stacks.cend(); ++stack) { const std::string socket_name = "Socket" + std::to_string(socket->socket_id); std::string stack_name = stack->stack_name; if (!human_readable) { stack_name.erase(stack_name.find_last_not_of(' ') + 1); } const uint32_t stack_id = stack->iio_unit_id; //Print data int part_id; std::map>::const_iterator vunit; for (vunit = v_sort.cbegin(), part_id = 0; vunit != v_sort.cend(); ++vunit, ++part_id) { map h_array = vunit->second; uint32_t vv_id = vunit->first; vector h_data; string v_name = h_array[0]->v_event_name; if (human_readable) { v_name += string(max_name_width - (v_name.size()), ' '); } current_row.clear(); current_row.push_back(socket_name); if (show_root_port) { auto pci_dev = get_root_port_dev(show_root_port, part_id, &(*stack)); current_row.push_back(pci_dev); } current_row.push_back(stack_name); current_row.push_back(v_name); for (map::const_iterator hunit = h_array.cbegin(); hunit != h_array.cend(); ++hunit) { uint32_t hh_id = hunit->first; uint64_t raw_data = hunit->second->data[0][socket->socket_id][stack_id][std::pair(hh_id,vv_id)]; current_row.push_back(human_readable ? unit_format(raw_data) : std::to_string(raw_data)); } result.push_back(build_csv_row(current_row, csv_delimiter)); } } } return result; } class IPlatformMapping { private: uint32_t m_sockets; uint32_t m_model; protected: void probeDeviceRange(std::vector &child_pci_devs, int domain, int secondary, int subordinate); public: IPlatformMapping(int cpu_model, uint32_t sockets_count) : m_sockets(sockets_count), m_model(cpu_model) {} virtual ~IPlatformMapping() {}; static std::unique_ptr getPlatformMapping(int cpu_model, uint32_t sockets_count); virtual bool pciTreeDiscover(std::vector& iios) = 0; uint32_t socketsCount() const { return m_sockets; } uint32_t cpuId() const { return m_model; } }; void IPlatformMapping::probeDeviceRange(std::vector &pci_devs, int domain, int secondary, int subordinate) { for (uint8_t bus = secondary; int(bus) <= subordinate; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_dev; child_dev.bdf.domainno = domain; child_dev.bdf.busno = bus; child_dev.bdf.devno = device; child_dev.bdf.funcno = function; if (probe_pci(&child_dev)) { if (secondary < child_dev.secondary_bus_number && subordinate < child_dev.subordinate_bus_number) { probeDeviceRange(child_dev.child_pci_devs, domain, child_dev.secondary_bus_number, child_dev.subordinate_bus_number); } pci_devs.push_back(child_dev); } } } } } // Mapping for SkyLake Server. class PurleyPlatformMapping: public IPlatformMapping { private: void getUboxBusNumbers(std::vector& ubox); public: PurleyPlatformMapping(int cpu_model, uint32_t sockets_count) : IPlatformMapping(cpu_model, sockets_count) {} ~PurleyPlatformMapping() = default; bool pciTreeDiscover(std::vector& iios) override; }; void PurleyPlatformMapping::getUboxBusNumbers(std::vector& ubox) { for (uint16_t bus = 0; bus < 256; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci pci_dev; pci_dev.bdf.busno = (uint8_t)bus; pci_dev.bdf.devno = device; pci_dev.bdf.funcno = function; if (probe_pci(&pci_dev)) { if (pci_dev.isIntelDevice() && (pci_dev.device_id == SKX_SOCKETID_UBOX_DID)) { ubox.push_back(bus); } } } } } } bool PurleyPlatformMapping::pciTreeDiscover(std::vector& iios) { std::vector ubox; getUboxBusNumbers(ubox); if (ubox.empty()) { cerr << "UBOXs were not found! Program aborted" << endl; return false; } for (uint32_t socket_id = 0; socket_id < socketsCount(); socket_id++) { if (!PciHandleType::exists(0, ubox[socket_id], SKX_UBOX_DEVICE_NUM, SKX_UBOX_FUNCTION_NUM)) { cerr << "No access to PCICFG\n" << endl; return false; } uint64 cpubusno = 0; struct iio_stacks_on_socket iio_on_socket; iio_on_socket.socket_id = socket_id; PciHandleType h(0, ubox[socket_id], SKX_UBOX_DEVICE_NUM, SKX_UBOX_FUNCTION_NUM); h.read64(ROOT_BUSES_OFFSET, &cpubusno); iio_on_socket.stacks.reserve(6); for (int stack_id = 0; stack_id < 6; stack_id++) { struct iio_stack stack; stack.iio_unit_id = stack_id; stack.busno = (uint8_t)(cpubusno >> (stack_id * SKX_BUS_NUM_STRIDE)); stack.stack_name = skx_iio_stack_names[stack_id]; for (uint8_t part_id = 0; part_id < 4; part_id++) { struct iio_bifurcated_part part; part.part_id = part_id; struct pci *pci = &part.root_pci_dev; struct bdf *bdf = &pci->bdf; bdf->busno = stack.busno; bdf->devno = part_id; bdf->funcno = 0; /* This is a workaround to catch some IIO stack does not exist */ if (stack_id != 0 && stack.busno == 0) { pci->exist = false; } else if (probe_pci(pci)) { /* FIXME: for 0:0.0, we may need to scan from secondary switch down; lgtm [cpp/fixme-comment] */ for (uint8_t bus = pci->secondary_bus_number; bus <= pci->subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev; child_pci_dev.bdf.busno = bus; child_pci_dev.bdf.devno = device; child_pci_dev.bdf.funcno = function; if (probe_pci(&child_pci_dev)) { part.child_pci_devs.push_back(child_pci_dev); } } } } } stack.parts.push_back(part); } iio_on_socket.stacks.push_back(stack); } iios.push_back(iio_on_socket); } return true; } class IPlatformMapping10Nm: public IPlatformMapping { private: public: IPlatformMapping10Nm(int cpu_model, uint32_t sockets_count) : IPlatformMapping(cpu_model, sockets_count) {} ~IPlatformMapping10Nm() = default; bool getSadIdRootBusMap(uint32_t socket_id, std::map& sad_id_bus_map); }; bool IPlatformMapping10Nm::getSadIdRootBusMap(uint32_t socket_id, std::map& sad_id_bus_map) { for (uint16_t bus = 0; bus < 256; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci pci_dev; pci_dev.bdf.busno = (uint8_t)bus; pci_dev.bdf.devno = device; pci_dev.bdf.funcno = function; if (probe_pci(&pci_dev) && pci_dev.isIntelDevice() && (pci_dev.device_id == SNR_ICX_MESH2IIO_MMAP_DID)) { PciHandleType h(0, bus, device, function); std::uint32_t sad_ctrl_cfg; h.read32(SNR_ICX_SAD_CONTROL_CFG_OFFSET, &sad_ctrl_cfg); if (sad_ctrl_cfg == (std::numeric_limits::max)()) { cerr << "Could not read SAD_CONTROL_CFG" << endl; return false; } if ((sad_ctrl_cfg & 0xf) == socket_id) { uint8_t sid = (sad_ctrl_cfg >> 4) & 0x7; sad_id_bus_map.insert(std::pair(sid, (uint8_t)bus)); } } } } } if (sad_id_bus_map.empty()) { cerr << "Could not find Root Port bus numbers" << endl; return false; } return true; } // Mapping for IceLake Server. class WhitleyPlatformMapping: public IPlatformMapping10Nm { private: const bool icx_d; const std::map& sad_to_pmu_id_mapping; const std::string * iio_stack_names; public: WhitleyPlatformMapping(int cpu_model, uint32_t sockets_count) : IPlatformMapping10Nm(cpu_model, sockets_count), icx_d(PCM::getInstance()->getCPUFamilyModelFromCPUID() == PCM::ICX_D), sad_to_pmu_id_mapping(icx_d ? icx_d_sad_to_pmu_id_mapping : icx_sad_to_pmu_id_mapping), iio_stack_names(icx_d ? icx_d_iio_stack_names : icx_iio_stack_names) { } ~WhitleyPlatformMapping() = default; bool pciTreeDiscover(std::vector& iios) override; }; bool WhitleyPlatformMapping::pciTreeDiscover(std::vector& iios) { for (uint32_t socket = 0; socket < socketsCount(); socket++) { struct iio_stacks_on_socket iio_on_socket; iio_on_socket.socket_id = socket; std::map sad_id_bus_map; if (!getSadIdRootBusMap(socket, sad_id_bus_map)) { return false; } { struct iio_stack stack; stack.iio_unit_id = sad_to_pmu_id_mapping.at(ICX_MCP_SAD_ID); stack.stack_name = iio_stack_names[stack.iio_unit_id]; iio_on_socket.stacks.push_back(stack); } for (auto sad_id_bus_pair = sad_id_bus_map.cbegin(); sad_id_bus_pair != sad_id_bus_map.cend(); ++sad_id_bus_pair) { int sad_id = sad_id_bus_pair->first; if (sad_to_pmu_id_mapping.find(sad_id) == sad_to_pmu_id_mapping.end()) { cerr << "Unknown SAD ID: " << sad_id << endl; return false; } if (sad_id == ICX_MCP_SAD_ID) { continue; } struct iio_stack stack; int root_bus = sad_id_bus_pair->second; if (sad_id == ICX_CBDMA_DMI_SAD_ID) { // There is one DMA Controller on each socket stack.iio_unit_id = sad_to_pmu_id_mapping.at(sad_id); stack.busno = root_bus; stack.stack_name = iio_stack_names[stack.iio_unit_id]; // PCH is on socket 0 only if (socket == 0) { struct iio_bifurcated_part pch_part; struct pci *pci = &pch_part.root_pci_dev; struct bdf *bdf = &pci->bdf; pch_part.part_id = ICX_PCH_PART_ID; bdf->busno = root_bus; bdf->devno = 0x00; bdf->funcno = 0x00; if (probe_pci(pci)) { // Probe child devices only under PCH part. for (uint8_t bus = pci->secondary_bus_number; bus <= pci->subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev; child_pci_dev.bdf.busno = bus; child_pci_dev.bdf.devno = device; child_pci_dev.bdf.funcno = function; if (probe_pci(&child_pci_dev)) { pch_part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(pch_part); } } struct iio_bifurcated_part part; part.part_id = ICX_CBDMA_PART_ID; struct pci *pci = &part.root_pci_dev; struct bdf *bdf = &pci->bdf; bdf->busno = root_bus; bdf->devno = 0x01; bdf->funcno = 0x00; if (probe_pci(pci)) stack.parts.push_back(part); iio_on_socket.stacks.push_back(stack); continue; } stack.busno = root_bus; stack.iio_unit_id = sad_to_pmu_id_mapping.at(sad_id); stack.stack_name = iio_stack_names[stack.iio_unit_id]; for (int slot = 2; slot < 6; slot++) { struct pci pci; pci.bdf.busno = root_bus; pci.bdf.devno = slot; pci.bdf.funcno = 0x00; if (!probe_pci(&pci)) { continue; } struct iio_bifurcated_part part; part.part_id = slot - 2; part.root_pci_dev = pci; for (uint8_t bus = pci.secondary_bus_number; bus <= pci.subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev; child_pci_dev.bdf.busno = bus; child_pci_dev.bdf.devno = device; child_pci_dev.bdf.funcno = function; if (probe_pci(&child_pci_dev)) { part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(part); } iio_on_socket.stacks.push_back(stack); } std::sort(iio_on_socket.stacks.begin(), iio_on_socket.stacks.end()); iios.push_back(iio_on_socket); } return true; } // Mapping for Snowridge. class JacobsvillePlatformMapping: public IPlatformMapping10Nm { private: public: JacobsvillePlatformMapping(int cpu_model, uint32_t sockets_count) : IPlatformMapping10Nm(cpu_model, sockets_count) {} ~JacobsvillePlatformMapping() = default; bool pciTreeDiscover(std::vector& iios) override; bool JacobsvilleAccelerators(const std::pair& sad_id_bus_pair, struct iio_stack& stack); }; bool JacobsvillePlatformMapping::JacobsvilleAccelerators(const std::pair& sad_id_bus_pair, struct iio_stack& stack) { uint16_t expected_dev_id; auto sad_id = sad_id_bus_pair.first; switch (sad_id) { case SNR_HQM_SAD_ID: expected_dev_id = HQM_DID; break; case SNR_NIS_SAD_ID: expected_dev_id = NIS_DID; break; case SNR_QAT_SAD_ID: expected_dev_id = QAT_DID; break; default: return false; } stack.iio_unit_id = snr_sad_to_pmu_id_mapping.at(sad_id); stack.stack_name = snr_iio_stack_names[stack.iio_unit_id]; for (uint16_t bus = sad_id_bus_pair.second; bus < 256; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci pci_dev; pci_dev.bdf.busno = (uint8_t)bus; pci_dev.bdf.devno = device; pci_dev.bdf.funcno = function; if (probe_pci(&pci_dev)) { if (expected_dev_id == pci_dev.device_id) { struct iio_bifurcated_part part; part.part_id = SNR_ACCELERATOR_PART_ID; part.root_pci_dev = pci_dev; stack.busno = (uint8_t)bus; stack.parts.push_back(part); return true; } } } } } return false; } bool JacobsvillePlatformMapping::pciTreeDiscover(std::vector& iios) { std::map sad_id_bus_map; if (!getSadIdRootBusMap(0, sad_id_bus_map)) { return false; } struct iio_stacks_on_socket iio_on_socket; iio_on_socket.socket_id = 0; if (sad_id_bus_map.size() != snr_sad_to_pmu_id_mapping.size()) { cerr << "Found unexpected number of stacks: " << sad_id_bus_map.size() << ", expected: " << snr_sad_to_pmu_id_mapping.size() << endl; return false; } for (auto sad_id_bus_pair = sad_id_bus_map.cbegin(); sad_id_bus_pair != sad_id_bus_map.cend(); ++sad_id_bus_pair) { int sad_id = sad_id_bus_pair->first; struct iio_stack stack; switch (sad_id) { case SNR_CBDMA_DMI_SAD_ID: { int root_bus = sad_id_bus_pair->second; stack.iio_unit_id = snr_sad_to_pmu_id_mapping.at(sad_id); stack.stack_name = snr_iio_stack_names[stack.iio_unit_id]; stack.busno = root_bus; // DMA Controller struct iio_bifurcated_part part; part.part_id = 0; struct pci pci_dev; pci_dev.bdf.busno = root_bus; pci_dev.bdf.devno = 0x01; pci_dev.bdf.funcno = 0x00; if (probe_pci(&pci_dev)) { part.root_pci_dev = pci_dev; stack.parts.push_back(part); } part.part_id = 4; pci_dev.bdf.busno = root_bus; pci_dev.bdf.devno = 0x00; pci_dev.bdf.funcno = 0x00; if (probe_pci(&pci_dev)) { for (uint8_t bus = pci_dev.secondary_bus_number; bus <= pci_dev.subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev; child_pci_dev.bdf.busno = bus; child_pci_dev.bdf.devno = device; child_pci_dev.bdf.funcno = function; if (probe_pci(&child_pci_dev)) { part.child_pci_devs.push_back(child_pci_dev); } } } } part.root_pci_dev = pci_dev; stack.parts.push_back(part); } } break; case SNR_PCIE_GEN3_SAD_ID: { int root_bus = sad_id_bus_pair->second; stack.busno = root_bus; stack.iio_unit_id = snr_sad_to_pmu_id_mapping.at(sad_id); stack.stack_name = snr_iio_stack_names[stack.iio_unit_id]; for (int slot = 4; slot < 8; slot++) { struct pci pci_dev; pci_dev.bdf.busno = root_bus; pci_dev.bdf.devno = slot; pci_dev.bdf.funcno = 0x00; if (!probe_pci(&pci_dev)) { continue; } int part_id = 4 + pci_dev.device_id - SNR_ROOT_PORT_A_DID; if ((part_id < 0) || (part_id > 4)) { cerr << "Invalid part ID " << part_id << endl; return false; } struct iio_bifurcated_part part; part.part_id = part_id; part.root_pci_dev = pci_dev; for (uint8_t bus = pci_dev.secondary_bus_number; bus <= pci_dev.subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 32; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev; child_pci_dev.bdf.busno = bus; child_pci_dev.bdf.devno = device; child_pci_dev.bdf.funcno = function; if (probe_pci(&child_pci_dev)) { part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(part); } } break; case SNR_HQM_SAD_ID: case SNR_NIS_SAD_ID: case SNR_QAT_SAD_ID: JacobsvilleAccelerators(*sad_id_bus_pair, stack); break; default: cerr << "Unknown SAD ID: " << sad_id << endl; return false; } iio_on_socket.stacks.push_back(stack); } std::sort(iio_on_socket.stacks.begin(), iio_on_socket.stacks.end()); iios.push_back(iio_on_socket); return true; } class EagleStreamPlatformMapping: public IPlatformMapping { private: bool getRootBuses(std::map> &root_buses); bool stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); bool eagleStreamDmiStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); bool eagleStreamPciStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); bool eagleStreamAcceleratorStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); bool isDmiStack(int unit); bool isPcieStack(int unit); bool isDinoStack(int unit); std::uint32_t m_chop; EagleStreamSupportedTypes m_es_type; public: EagleStreamPlatformMapping(int cpu_model, uint32_t sockets_count) : IPlatformMapping(cpu_model, sockets_count), m_chop(0), m_es_type(estype::esInvalid) {} ~EagleStreamPlatformMapping() = default; bool setChopValue(); bool isXccPlatform() const { return m_chop == kXccChop; } const std::uint32_t kXccChop = 0b11; const std::uint32_t kMccChop = 0b01; bool pciTreeDiscover(std::vector& iios) override; }; bool EagleStreamPlatformMapping::setChopValue() { for (uint16_t b = 0; b < 256; b++) { struct pci pci_dev(0, b, SPR_PCU_CR3_REG_DEVICE, SPR_PCU_CR3_REG_FUNCTION); if (!probe_pci(&pci_dev)) { continue; } if (!(pci_dev.isIntelDevice() && (pci_dev.device_id == SPR_PCU_CR3_DID))) { continue; } std::uint32_t capid4; PciHandleType h(0, b, SPR_PCU_CR3_REG_DEVICE, SPR_PCU_CR3_REG_FUNCTION); h.read32(SPR_CAPID4_OFFSET, &capid4); if (capid4 == (std::numeric_limits::max)()) { std::cerr << "Cannot read PCU RC3 register" << std::endl; return false; } capid4 = SPR_CAPID4_GET_PHYSICAL_CHOP(capid4); if (capid4 == kXccChop || capid4 == kMccChop) { m_chop = capid4; m_es_type = cpuId() == PCM::SPR ? (m_chop == kXccChop ? estype::esSprXcc : estype::esSprMcc) : estype::esEmrXcc; } else { std::cerr << "Unknown chop value " << capid4 << std::endl; return false; } return true; } std::cerr << "Cannot find PCU RC3 registers on the system. Device ID is " << std::hex << SPR_PCU_CR3_DID << std::dec << std::endl; return false; } bool EagleStreamPlatformMapping::getRootBuses(std::map> &root_buses) { bool mapped = true; for (uint32_t domain = 0; mapped; domain++) { mapped = false; for (uint16_t b = 0; b < 256; b++) { for (uint8_t d = 0; d < 32; d++) { for (uint8_t f = 0; f < 8; f++) { struct pci pci_dev(domain, b, d, f); if (!probe_pci(&pci_dev)) { break; } if (!(pci_dev.isIntelDevice() && (pci_dev.device_id == SPR_MSM_DEV_ID))) { continue; } std::uint32_t cpuBusValid; std::vector cpuBusNo; int package_id; if (get_cpu_bus(domain, b, d, f, cpuBusValid, cpuBusNo, package_id) == false) { return false; } const auto& sad_to_pmu_id_mapping = es_sad_to_pmu_id_mapping.at(m_es_type); for (int cpuBusId = 0; cpuBusId < SPR_MSM_CPUBUSNO_MAX; ++cpuBusId) { if (!((cpuBusValid >> cpuBusId) & 0x1)) { cout << "CPU bus " << cpuBusId << " is disabled on package " << package_id << endl; continue; } if (sad_to_pmu_id_mapping.find(cpuBusId) == sad_to_pmu_id_mapping.end()) { cerr << "Cannot map CPU bus " << cpuBusId << " to IO PMU ID" << endl; continue; } int pmuId = sad_to_pmu_id_mapping.at(cpuBusId); int rootBus = (cpuBusNo[(int)(cpuBusId / 4)] >> ((cpuBusId % 4) * 8)) & 0xff; root_buses[package_id][pmuId] = bdf(domain, rootBus, 0, 0); cout << "Mapped CPU bus #" << cpuBusId << " (domain " << domain << " bus " << std::hex << rootBus << std::dec << ") to IO PMU #" << pmuId << " package " << package_id << endl; mapped = true; } } } } } return !root_buses.empty(); } bool EagleStreamPlatformMapping::eagleStreamDmiStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { struct iio_stack stack; stack.iio_unit_id = unit; stack.stack_name = es_stack_names.at(m_es_type)[unit]; stack.busno = address.busno; stack.domain = address.domainno; struct iio_bifurcated_part pch_part; struct pci *pci = &pch_part.root_pci_dev; auto dmi_part_id = SPR_DMI_PART_ID; pch_part.part_id = dmi_part_id; pci->bdf = address; if (!probe_pci(pci)) { cerr << "Failed to probe DMI Stack: address: " << std::setw(4) << std::setfill('0') << std::hex << address.domainno << std::setw(2) << std::setfill('0') << ":" << address.busno << ":" << address.devno << "." << address.funcno << std::dec << endl; return false; } /* Scan devices behind PCH port only */ if (!iio_on_socket.socket_id) probeDeviceRange(pch_part.child_pci_devs, pci->bdf.domainno, pci->secondary_bus_number, pci->subordinate_bus_number); pci->parts_no.push_back(dmi_part_id); stack.parts.push_back(pch_part); iio_on_socket.stacks.push_back(stack); return true; } bool EagleStreamPlatformMapping::eagleStreamPciStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { /* * Stacks that manage PCIe 4.0 (device 2,4,6,8) and 5.0 (device 1,3,5,7) Root Ports. */ struct iio_stack stack; stack.domain = address.domainno; stack.busno = address.busno; stack.iio_unit_id = unit; stack.stack_name = es_stack_names.at(m_es_type)[unit]; for (int slot = 1; slot < 9; ++slot) { // Check if port is enabled struct pci root_pci_dev; root_pci_dev.bdf = bdf(address.domainno, address.busno, slot, 0x0); if (probe_pci(&root_pci_dev)) { struct iio_bifurcated_part part; // Bifurcated Root Ports to channel mapping on SPR part.part_id = slot - 1; part.root_pci_dev = root_pci_dev; for (uint8_t b = root_pci_dev.secondary_bus_number; b <= root_pci_dev.subordinate_bus_number; ++b) { for (uint8_t d = 0; d < 32; ++d) { for (uint8_t f = 0; f < 8; ++f) { struct pci child_pci_dev(address.domainno, b, d, f); if (probe_pci(&child_pci_dev)) { child_pci_dev.parts_no.push_back(part.part_id); part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(part); } } iio_on_socket.stacks.push_back(stack); return true; } bool EagleStreamPlatformMapping::eagleStreamAcceleratorStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { struct iio_stack stack; stack.iio_unit_id = unit; stack.domain = address.domainno; stack.busno = address.busno; // Channel mappings are checked on B0 stepping auto rb = address.busno; const std::vector acceleratorBuses{ rb, rb + 1, rb + 2, rb + 3 }; stack.stack_name = es_stack_names.at(m_es_type)[unit]; for (auto& b : acceleratorBuses) { for (auto d = 0; d < 32; ++d) { for (auto f = 0; f < 8; ++f) { struct iio_bifurcated_part part; struct pci pci_dev(address.domainno, b, d, f); if (probe_pci(&pci_dev)) { if (pci_dev.isIntelDevice()) { switch (pci_dev.device_id) { case DSA_DID: pci_dev.parts_no.push_back(0); pci_dev.parts_no.push_back(1); pci_dev.parts_no.push_back(2); break; case IAX_DID: pci_dev.parts_no.push_back(0); pci_dev.parts_no.push_back(1); pci_dev.parts_no.push_back(2); break; case HQMV2_DID: pci_dev.parts_no.push_back(isXccPlatform() ? SPR_XCC_HQM_PART_ID : SPR_MCC_HQM_PART_ID); break; case QATV2_DID: pci_dev.parts_no.push_back(isXccPlatform() ? SPR_XCC_QAT_PART_ID : SPR_MCC_QAT_PART_ID); break; default: continue; } part.child_pci_devs.push_back(pci_dev); } stack.parts.push_back(part); } } } } iio_on_socket.stacks.push_back(stack); return true; } bool EagleStreamPlatformMapping::isDmiStack(int unit) { const auto& stacks_enumeration = es_stacks_enumeration.at(m_es_type); return stacks_enumeration[esDMI] == unit; } bool EagleStreamPlatformMapping::isPcieStack(int unit) { const auto& stacks_enumeration = es_stacks_enumeration.at(m_es_type); return stacks_enumeration[esPCIe0] == unit || stacks_enumeration[esPCIe1] == unit || stacks_enumeration[esPCIe2] == unit || stacks_enumeration[esPCIe3] == unit || stacks_enumeration[esPCIe4] == unit; } bool EagleStreamPlatformMapping::isDinoStack(int unit) { const auto& stacks_enumeration = es_stacks_enumeration.at(m_es_type); return stacks_enumeration[esDINO0] == unit || stacks_enumeration[esDINO1] == unit || stacks_enumeration[esDINO2] == unit || stacks_enumeration[esDINO3] == unit; } bool EagleStreamPlatformMapping::stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { if (isDmiStack(unit)) { return eagleStreamDmiStackProbe(unit, address, iio_on_socket); } else if (isPcieStack(unit)) { return eagleStreamPciStackProbe(unit, address, iio_on_socket); } else if (isDinoStack(unit)) { return eagleStreamAcceleratorStackProbe(unit, address, iio_on_socket); } return false; } bool EagleStreamPlatformMapping::pciTreeDiscover(std::vector& iios) { if (!setChopValue()) return false; std::map> root_buses; if (!getRootBuses(root_buses)) { return false; } for (auto iter = root_buses.cbegin(); iter != root_buses.cend(); ++iter) { auto rbs_on_socket = iter->second; struct iio_stacks_on_socket iio_on_socket; iio_on_socket.socket_id = iter->first; for (auto rb = rbs_on_socket.cbegin(); rb != rbs_on_socket.cend(); ++rb) { if (!stackProbe(rb->first, rb->second, iio_on_socket)) { return false; } } std::sort(iio_on_socket.stacks.begin(), iio_on_socket.stacks.end()); iios.push_back(iio_on_socket); } return true; } class LoganvillePlatform: public IPlatformMapping10Nm { private: bool loganvillePchDsaPciStackProbe(struct iio_stacks_on_socket& iio_on_socket, int root_bus, int stack_pmon_id); bool loganvilleDlbStackProbe(struct iio_stacks_on_socket& iio_on_socket, int root_bus, int stack_pmon_id); bool loganvilleNacStackProbe(struct iio_stacks_on_socket& iio_on_socket, int root_bus, int stack_pmon_id); public: LoganvillePlatform(int cpu_model, uint32_t sockets_count) : IPlatformMapping10Nm(cpu_model, sockets_count) {} ~LoganvillePlatform() = default; bool pciTreeDiscover(std::vector& iios) override; }; bool LoganvillePlatform::loganvillePchDsaPciStackProbe(struct iio_stacks_on_socket& iio_on_socket, int root_bus, int stack_pmon_id) { struct iio_stack stack; stack.busno = root_bus; stack.iio_unit_id = stack_pmon_id; stack.stack_name = grr_iio_stack_names[stack_pmon_id]; struct iio_bifurcated_part pch_part; pch_part.part_id = 7; struct pci* pci_dev = &pch_part.root_pci_dev; pci_dev->bdf.busno = root_bus; if (probe_pci(pci_dev)) { probeDeviceRange(pch_part.child_pci_devs, pci_dev->bdf.domainno, pci_dev->secondary_bus_number, pci_dev->subordinate_bus_number); stack.parts.push_back(pch_part); iio_on_socket.stacks.push_back(stack); return true; } return false; } bool LoganvillePlatform::loganvilleDlbStackProbe(struct iio_stacks_on_socket& iio_on_socket, int root_bus, int stack_pmon_id) { struct iio_stack stack; stack.busno = root_bus; stack.iio_unit_id = stack_pmon_id; stack.stack_name = grr_iio_stack_names[stack_pmon_id]; struct iio_bifurcated_part dlb_part; dlb_part.part_id = GRR_DLB_PART_ID; for (uint8_t bus = root_bus; bus < 255; bus++) { struct pci pci_dev(bus, 0x00, 0x00); if (probe_pci(&pci_dev)) { if ((pci_dev.vendor_id == PCM_INTEL_PCI_VENDOR_ID) && (pci_dev.device_id == HQMV25_DID)) { dlb_part.root_pci_dev = pci_dev; // Check Virtual RPs for DLB for (uint8_t device = 0; device < 2; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev(bus, device, function); if (probe_pci(&child_pci_dev)) { dlb_part.child_pci_devs.push_back(child_pci_dev); } } } stack.parts.push_back(dlb_part); iio_on_socket.stacks.push_back(stack); return true; } } } return false; } bool LoganvillePlatform::loganvilleNacStackProbe(struct iio_stacks_on_socket& iio_on_socket, int root_bus, int stack_pmon_id) { struct iio_stack stack; stack.busno = root_bus; stack.iio_unit_id = stack_pmon_id; stack.stack_name = grr_iio_stack_names[stack_pmon_id]; // Probe NIS { struct iio_bifurcated_part nis_part; nis_part.part_id = GRR_NIS_PART_ID; struct pci pci_dev(root_bus, 0x04, 0x00); if (probe_pci(&pci_dev)) { nis_part.root_pci_dev = pci_dev; for (uint8_t bus = pci_dev.secondary_bus_number; bus <= pci_dev.subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 2; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev(bus, device, function); if (probe_pci(&child_pci_dev)) { nis_part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(nis_part); } } // Probe QAT { struct iio_bifurcated_part qat_part; qat_part.part_id = GRR_QAT_PART_ID; struct pci pci_dev(root_bus, 0x05, 0x00); if (probe_pci(&pci_dev)) { qat_part.root_pci_dev = pci_dev; for (uint8_t bus = pci_dev.secondary_bus_number; bus <= pci_dev.subordinate_bus_number; bus++) { for (uint8_t device = 0; device < 17; device++) { for (uint8_t function = 0; function < 8; function++) { struct pci child_pci_dev(bus, device, function); if (probe_pci(&child_pci_dev)) { qat_part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(qat_part); } } iio_on_socket.stacks.push_back(stack); return true; } bool LoganvillePlatform::pciTreeDiscover(std::vector& iios) { std::map sad_id_bus_map; if (!getSadIdRootBusMap(0, sad_id_bus_map)) { return false; } if (sad_id_bus_map.size() != grr_sad_to_pmu_id_mapping.size()) { cerr << "Found unexpected number of stacks: " << sad_id_bus_map.size() << ", expected: " << grr_sad_to_pmu_id_mapping.size() << endl; return false; } struct iio_stacks_on_socket iio_on_socket; iio_on_socket.socket_id = 0; for (auto sad_id_bus_pair = sad_id_bus_map.cbegin(); sad_id_bus_pair != sad_id_bus_map.cend(); ++sad_id_bus_pair) { if (grr_sad_to_pmu_id_mapping.find(sad_id_bus_pair->first) == grr_sad_to_pmu_id_mapping.end()) { cerr << "Cannot map SAD ID to PMON ID. Unknown ID: " << sad_id_bus_pair->first << endl; return false; } int stack_pmon_id = grr_sad_to_pmu_id_mapping.at(sad_id_bus_pair->first); int root_bus = sad_id_bus_pair->second; switch (stack_pmon_id) { case GRR_PCH_DSA_GEN4_PMON_ID: if (!loganvillePchDsaPciStackProbe(iio_on_socket, root_bus, stack_pmon_id)) { return false; } break; case GRR_DLB_PMON_ID: if (!loganvilleDlbStackProbe(iio_on_socket, root_bus, stack_pmon_id)) { return false; } break; case GRR_NIS_QAT_PMON_ID: if (!loganvilleNacStackProbe(iio_on_socket, root_bus, stack_pmon_id)) { return false; } break; default: return false; } } std::sort(iio_on_socket.stacks.begin(), iio_on_socket.stacks.end()); iios.push_back(iio_on_socket); return true; } class Xeon6thNextGenPlatform: public IPlatformMapping { private: bool getRootBuses(std::map> &root_buses); protected: virtual bool stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) = 0; public: Xeon6thNextGenPlatform(int cpu_model, uint32_t sockets_count) : IPlatformMapping(cpu_model, sockets_count) {} virtual ~Xeon6thNextGenPlatform() = default; bool pciTreeDiscover(std::vector& iios) override; }; bool Xeon6thNextGenPlatform::getRootBuses(std::map> &root_buses) { bool mapped = true; for (uint32_t domain = 0; mapped; domain++) { mapped = false; for (uint16_t b = 0; b < 256; b++) { for (uint8_t d = 0; d < 32; d++) { for (uint8_t f = 0; f < 8; f++) { struct pci pci_dev(domain, b, d, f); if (!probe_pci(&pci_dev)) { break; } if (!(pci_dev.isIntelDevice() && (pci_dev.device_id == SPR_MSM_DEV_ID))) { continue; } std::uint32_t cpuBusValid; std::vector cpuBusNo; int package_id; if (!get_cpu_bus(domain, b, d, f, cpuBusValid, cpuBusNo, package_id)) { return false; } for (int cpuBusId = 0; cpuBusId < SPR_MSM_CPUBUSNO_MAX; ++cpuBusId) { if (!((cpuBusValid >> cpuBusId) & 0x1)) { cout << "CPU bus " << cpuBusId << " is disabled on package " << package_id << endl; continue; } int rootBus = (cpuBusNo[(int)(cpuBusId / 4)] >> ((cpuBusId % 4) * 8)) & 0xff; root_buses[package_id][cpuBusId] = bdf(domain, rootBus, 0, 0); cout << "Mapped CPU bus #" << cpuBusId << " (domain " << domain << " bus " << std::hex << rootBus << std::dec << ")" << " package " << package_id << endl; mapped = true; } } } } } return !root_buses.empty(); } bool Xeon6thNextGenPlatform::pciTreeDiscover(std::vector& iios) { std::map> root_buses; if (!getRootBuses(root_buses)) { return false; } for (auto iter = root_buses.cbegin(); iter != root_buses.cend(); ++iter) { auto rbs_on_socket = iter->second; struct iio_stacks_on_socket iio_on_socket; iio_on_socket.socket_id = iter->first; for (auto rb = rbs_on_socket.cbegin(); rb != rbs_on_socket.cend(); ++rb) { if (!stackProbe(rb->first, rb->second, iio_on_socket)) { return false; } } std::sort(iio_on_socket.stacks.begin(), iio_on_socket.stacks.end()); iios.push_back(iio_on_socket); } return true; } class BirchStreamPlatform: public Xeon6thNextGenPlatform { private: bool isPcieStack(int unit); bool isRootHcStack(int unit); bool isPartHcStack(int unit); bool isUboxStack(int unit); bool birchStreamPciStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); bool birchStreamAcceleratorStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); protected: bool stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) override; public: BirchStreamPlatform(int cpu_model, uint32_t sockets_count) : Xeon6thNextGenPlatform(cpu_model, sockets_count) {} ~BirchStreamPlatform() = default; }; bool BirchStreamPlatform::birchStreamPciStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { /* * All stacks manage PCIe 5.0 Root Ports. Bifurcated Root Ports A-H appear as devices 2-9. */ struct iio_stack stack; stack.domain = address.domainno; stack.busno = address.busno; stack.iio_unit_id = srf_sad_to_pmu_id_mapping.at(unit); stack.stack_name = srf_iio_stack_names[stack.iio_unit_id]; for (int slot = 2; slot < 9; ++slot) { struct pci root_pci_dev; root_pci_dev.bdf = bdf(address.domainno, address.busno, slot, 0x0); if (probe_pci(&root_pci_dev)) { struct iio_bifurcated_part part; part.part_id = slot - 2; part.root_pci_dev = root_pci_dev; for (uint8_t b = root_pci_dev.secondary_bus_number; b <= root_pci_dev.subordinate_bus_number; ++b) { for (uint8_t d = 0; d < 32; ++d) { for (uint8_t f = 0; f < 8; ++f) { struct pci child_pci_dev(address.domainno, b, d, f); if (probe_pci(&child_pci_dev)) { child_pci_dev.parts_no.push_back(part.part_id); part.child_pci_devs.push_back(child_pci_dev); } } } } stack.parts.push_back(part); } } iio_on_socket.stacks.push_back(stack); return true; } bool BirchStreamPlatform::birchStreamAcceleratorStackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { struct iio_stack stack; stack.iio_unit_id = srf_sad_to_pmu_id_mapping.at(unit); stack.domain = address.domainno; stack.busno = address.busno; stack.stack_name = srf_iio_stack_names[stack.iio_unit_id]; /* * Instance of DSA(0, 1, 2, 3) appears as PCIe device with SAD Bus ID (8, 12, 20, 16), device 1, function 0 * Instance of IAX(0, 1, 2, 3) appears as PCIe device with SAD Bus ID (8, 12, 20, 16), device 2, function 0 * Instance of QAT(0, 1, 2, 3) appears as PCIe device with SAD Bus ID (9, 13, 21, 17), device 0, function 0 * Instance of HQM(0, 1, 2, 3) appears as PCIe device with SAD Bus ID (10, 14, 22, 18), device 0, function 0 */ auto process_pci_dev = [](int domainno, int busno, int devno, int part_number, iio_bifurcated_part& part) { struct pci pci_dev(domainno, busno, devno, 0); if (probe_pci(&pci_dev) && pci_dev.isIntelDevice()) { part.part_id = part_number; pci_dev.parts_no.push_back(part_number); part.child_pci_devs.push_back(pci_dev); return true; } return false; }; { struct iio_bifurcated_part part; if (process_pci_dev(address.domainno, address.busno, 1, SRF_DSA_IAX_PART_NUMBER, part) || process_pci_dev(address.domainno, address.busno, 2, SRF_DSA_IAX_PART_NUMBER, part)) { stack.parts.push_back(part); } } { struct iio_bifurcated_part part; if (process_pci_dev(address.domainno, address.busno + 1, 0, SRF_QAT_PART_NUMBER, part)) { stack.parts.push_back(part); } } { /* Bus number for HQM is higher on 3 than DSA bus number */ struct iio_bifurcated_part part; if (process_pci_dev(address.domainno, address.busno + 3, 0, SRF_HQM_PART_NUMBER, part)) { stack.parts.push_back(part); } } if (!stack.parts.empty()) { iio_on_socket.stacks.push_back(stack); } return true; } bool BirchStreamPlatform::isPcieStack(int unit) { return srf_pcie_stacks.find(unit) != srf_pcie_stacks.end(); } /* * HC is the name of DINO stacks as we had on SPR */ bool BirchStreamPlatform::isRootHcStack(int unit) { return SRF_HC0_SAD_BUS_ID == unit || SRF_HC1_SAD_BUS_ID == unit || SRF_HC2_SAD_BUS_ID == unit || SRF_HC3_SAD_BUS_ID == unit; } bool BirchStreamPlatform::isPartHcStack(int unit) { return isRootHcStack(unit - 1) || isRootHcStack(unit - 2); } bool BirchStreamPlatform::isUboxStack(int unit) { return SRF_UBOXA_SAD_BUS_ID == unit || SRF_UBOXB_SAD_BUS_ID == unit; } bool BirchStreamPlatform::stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { if (isPcieStack(unit)) { return birchStreamPciStackProbe(unit, address, iio_on_socket); } else if (isRootHcStack(unit)) { return birchStreamAcceleratorStackProbe(unit, address, iio_on_socket); } else if (isPartHcStack(unit)) { cout << "Found a part of HC stack. Stack ID - " << unit << " domain " << address.domainno << " bus " << std::hex << std::setfill('0') << std::setw(2) << (int)address.busno << std::dec << ". Don't probe it again." << endl; return true; } else if (isUboxStack(unit)) { cout << "Found UBOX stack. Stack ID - " << unit << " domain " << address.domainno << " bus " << std::hex << std::setfill('0') << std::setw(2) << (int)address.busno << std::dec << endl; return true; } cout << "Unknown stack ID " << unit << " domain " << address.domainno << " bus " << std::hex << std::setfill('0') << std::setw(2) << (int)address.busno << std::dec << endl; return false; } class KasseyvillePlatform: public Xeon6thNextGenPlatform { private: bool stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket); bool isUboxStack(int unit) { return SRF_UBOXA_SAD_BUS_ID == unit || SRF_UBOXB_SAD_BUS_ID == unit; } public: KasseyvillePlatform(int cpu_model, uint32_t sockets_count) : Xeon6thNextGenPlatform(cpu_model, sockets_count) {} ~KasseyvillePlatform() = default; }; bool KasseyvillePlatform::stackProbe(int unit, const struct bdf &address, struct iio_stacks_on_socket &iio_on_socket) { // Skip UBOX buses if (isUboxStack(unit)) return true; // To suppress compilation warning (void)address; struct iio_stack stack; stack.iio_unit_id = unit; stack.stack_name = generate_stack_str(unit); iio_on_socket.stacks.push_back(stack); return true; } std::unique_ptr IPlatformMapping::getPlatformMapping(int cpu_family_model, uint32_t sockets_count) { switch (cpu_family_model) { case PCM::SKX: return std::unique_ptr{new PurleyPlatformMapping(cpu_family_model, sockets_count)}; case PCM::ICX: return std::unique_ptr{new WhitleyPlatformMapping(cpu_family_model, sockets_count)}; case PCM::SNOWRIDGE: return std::unique_ptr{new JacobsvillePlatformMapping(cpu_family_model, sockets_count)}; case PCM::SPR: case PCM::EMR: return std::unique_ptr{new EagleStreamPlatformMapping(cpu_family_model, sockets_count)}; case PCM::GRR: return std::unique_ptr{new LoganvillePlatform(cpu_family_model, sockets_count)}; case PCM::SRF: case PCM::GNR: return std::unique_ptr{new BirchStreamPlatform(cpu_family_model, sockets_count)}; case PCM::GNR_D: std::cerr << "Warning: Only initial support (without attribution to PCIe devices) for Graniterapids-D is provided" << std::endl; return std::unique_ptr{new KasseyvillePlatform(cpu_family_model, sockets_count)}; default: return nullptr; } } ccr* get_ccr(PCM* m, uint64_t& ccr) { switch (m->getCPUFamilyModel()) { case PCM::SKX: return new pcm::ccr(ccr, ccr::ccr_type::skx); case PCM::ICX: case PCM::SNOWRIDGE: case PCM::SPR: case PCM::EMR: case PCM::GRR: case PCM::SRF: case PCM::GNR: case PCM::GNR_D: return new pcm::ccr(ccr, ccr::ccr_type::icx); default: cerr << m->getCPUFamilyModelString() << " is not supported! Program aborted" << endl; exit(EXIT_FAILURE); } } int iio_evt_parse_handler(evt_cb_type cb_type, void *cb_ctx, counter &base_ctr, std::map &ofm, std::string key, uint64 numValue) { iio_evt_parse_context *context = (iio_evt_parse_context *)cb_ctx; PCM *m = context->m; if (cb_type == EVT_LINE_START) //this event will be called per line(start) { context->ctr.ccr = 0; } else if (cb_type == EVT_LINE_FIELD) //this event will be called per field of line { std::unique_ptr pccr(get_ccr(m, context->ctr.ccr)); switch (ofm[key]) { case PCM::OPCODE: break; case PCM::EVENT_SELECT: pccr->set_event_select(numValue); break; case PCM::UMASK: pccr->set_umask(numValue); break; case PCM::RESET: pccr->set_reset(numValue); break; case PCM::EDGE_DET: pccr->set_edge(numValue); break; case PCM::IGNORED: break; case PCM::OVERFLOW_ENABLE: pccr->set_ov_en(numValue); break; case PCM::ENABLE: pccr->set_enable(numValue); break; case PCM::INVERT: pccr->set_invert(numValue); break; case PCM::THRESH: pccr->set_thresh(numValue); break; case PCM::CH_MASK: pccr->set_ch_mask(numValue); break; case PCM::FC_MASK: pccr->set_fc_mask(numValue); break; case PCM::INVALID: default: std::cerr << "Field in -o file not recognized. The key is: " << key << "\n"; return -1; } } else if(cb_type == EVT_LINE_COMPLETE) //this event will be called every line(end) { context->ctr.h_event_name = base_ctr.h_event_name; context->ctr.v_event_name = base_ctr.v_event_name; context->ctr.idx = base_ctr.idx; context->ctr.multiplier = base_ctr.multiplier; context->ctr.divider = base_ctr.divider; context->ctr.h_id = base_ctr.h_id; context->ctr.v_id = base_ctr.v_id; //std::cout << "line parse OK, ctrcfg=0x" << std::hex << context->ctr.ccr << ", h_event_name=" << base_ctr.h_event_name << ", v_event_name=" << base_ctr.v_event_name; //std::cout << ", h_id=0x" << std::hex << base_ctr.h_id << ", v_id=0x" << std::hex << base_ctr.v_id; //std::cout << ", idx=0x"<< std::hex << base_ctr.idx << ", multiplier=0x" << std::hex << base_ctr.multiplier << ", divider=0x" << std::hex << base_ctr.divider << std::dec << "\n"; context->ctrs.push_back(context->ctr); } return 0; } result_content get_IIO_Samples(PCM *m, const std::vector& iios, const struct iio_counter & ctr, uint32_t delay_ms) { IIOCounterState *before, *after; uint64 rawEvents[4] = {0}; auto ccrCopy = ctr.ccr; std::unique_ptr pccr(get_ccr(m, ccrCopy)); rawEvents[ctr.idx] = pccr->get_ccr_value(); const int stacks_count = (int)m->getMaxNumOfIIOStacks(); before = new IIOCounterState[iios.size() * stacks_count]; after = new IIOCounterState[iios.size() * stacks_count]; m->programIIOCounters(rawEvents); for (auto socket = iios.cbegin(); socket != iios.cend(); ++socket) { for (auto stack = socket->stacks.cbegin(); stack != socket->stacks.cend(); ++stack) { auto iio_unit_id = stack->iio_unit_id; uint32_t idx = (uint32_t)stacks_count * socket->socket_id + iio_unit_id; before[idx] = m->getIIOCounterState(socket->socket_id, iio_unit_id, ctr.idx); } } MySleepMs(delay_ms); for (auto socket = iios.cbegin(); socket != iios.cend(); ++socket) { for (auto stack = socket->stacks.cbegin(); stack != socket->stacks.cend(); ++stack) { auto iio_unit_id = stack->iio_unit_id; uint32_t idx = (uint32_t)stacks_count * socket->socket_id + iio_unit_id; after[idx] = m->getIIOCounterState(socket->socket_id, iio_unit_id, ctr.idx); uint64_t raw_result = getNumberOfEvents(before[idx], after[idx]); uint64_t trans_result = uint64_t (raw_result * ctr.multiplier / (double) ctr.divider * (1000 / (double) delay_ms)); results[socket->socket_id][iio_unit_id][std::pair(ctr.h_id,ctr.v_id)] = trans_result; } } deleteAndNullifyArray(before); deleteAndNullifyArray(after); return results; } void collect_data(PCM *m, const double delay, vector& iios, vector& ctrs) { const uint32_t delay_ms = uint32_t(delay * 1000 / ctrs.size()); for (auto counter = ctrs.begin(); counter != ctrs.end(); ++counter) { counter->data.clear(); result_content sample = get_IIO_Samples(m, iios, *counter, delay_ms); counter->data.push_back(sample); } } void print_PCIeMapping(const std::vector& iios, const PCIDB & pciDB, std::ostream& stream) { uint32_t header_width = 100; string row; vector buffer; for (auto it = iios.begin(); it != iios.end(); ++it) { row = "Socket " + std::to_string((*it).socket_id); buffer.push_back(row); for (auto & stack : it->stacks) { std::stringstream ss; ss << "\t" << stack.stack_name << " domain 0x" << std::hex << std::setfill('0') << std::setw(4) << stack.domain << "; root bus: 0x" << std::setfill('0') << std::setw(2) << (int)stack.busno << "\tflipped: " << (stack.flipped ? "true" : "false"); buffer.push_back(ss.str()); for (auto& part : stack.parts) { vector pp = part.child_pci_devs; uint8_t level = 5; for (std::vector::const_iterator iunit = pp.begin(); iunit != pp.end(); ++iunit) { row = build_pci_header(pciDB, header_width, *iunit, -1, level); buffer.push_back(row); if (iunit->hasChildDevices()) { build_pci_tree(buffer, pciDB, header_width, *iunit, -1, level + 1); } else if (iunit->header_type == 1) { level++; } } } } } display(buffer, stream); } void print_usage(const string& progname) { cout << "\n Usage: \n " << progname << " --help | [interval] [options] \n"; cout << " => time interval in seconds (floating point number is accepted)\n"; cout << " to sample performance counters.\n"; cout << " If not specified - 3.0 is used\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -csv-delimiter= | /csv-delimiter= => set custom csv delimiter\n"; cout << " -human-readable | /human-readable => use human readable format for output (for csv only)\n"; cout << " -root-port | /root-port => add root port devices to output (for csv only)\n"; cout << " -list | --list => provide platform topology info\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " Examples:\n"; cout << " " << progname << " 1.0 -i=10 => print counters every second 10 times and exit\n"; cout << " " << progname << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << progname << " -csv -human-readable => every 3 second print counters in human-readable CSV format\n"; cout << "\n"; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if (print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream; check_and_set_silent(argc, argv, nullStream); std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; std::cout << "\n This utility measures IIO information\n\n"; string program = string(argv[0]); vector counters; bool csv = false; bool human_readable = false; bool show_root_port = false; std::string csv_delimiter = ","; std::string output_file; double delay = PCM_DELAY_DEFAULT; bool list = false; MainLoop mainLoop; iio_evt_parse_context evt_ctx; // Map with metrics names. map>> nameMap; while (argc > 1) { argv++; argc--; std::string arg_value; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (extract_argument_value(*argv, {"-csv-delimiter", "/csv-delimiter"}, arg_value)) { csv_delimiter = std::move(arg_value); } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; output_file = std::move(arg_value); } else if (check_argument_equals(*argv, {"-human-readable", "/human-readable"})) { human_readable = true; } else if (check_argument_equals(*argv, {"-list", "--list"})) { list = true; } else if (check_argument_equals(*argv, {"-root-port", "/root-port"})) { show_root_port = true; } else if (mainLoop.parseArg(*argv)) { continue; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } set_signal_handlers(); print_cpu_details(); PCM * m = PCM::getInstance(); PCIDB pciDB; load_PCIDB(pciDB); auto mapping = IPlatformMapping::getPlatformMapping(m->getCPUFamilyModel(), m->getNumSockets()); if (!mapping) { cerr << "Failed to discover pci tree: unknown platform" << endl; exit(EXIT_FAILURE); } std::vector iios; if (!mapping->pciTreeDiscover(iios)) { exit(EXIT_FAILURE); } std::ostream* output = &std::cout; std::fstream file_stream; if (!output_file.empty()) { file_stream.open(output_file.c_str(), std::ios_base::out); output = &file_stream; } if (list) { print_PCIeMapping(iios, pciDB, *output); return 0; } string ev_file_name; if (m->IIOEventsAvailable()) { ev_file_name = "opCode-" + std::to_string(m->getCPUFamily()) + "-" + std::to_string(m->getInternalCPUModel()) + ".txt"; } else { cerr << "This CPU is not supported by PCM IIO tool! Program aborted\n"; exit(EXIT_FAILURE); } map opcodeFieldMap; opcodeFieldMap["opcode"] = PCM::OPCODE; opcodeFieldMap["ev_sel"] = PCM::EVENT_SELECT; opcodeFieldMap["umask"] = PCM::UMASK; opcodeFieldMap["reset"] = PCM::RESET; opcodeFieldMap["edge_det"] = PCM::EDGE_DET; opcodeFieldMap["ignored"] = PCM::IGNORED; opcodeFieldMap["overflow_enable"] = PCM::OVERFLOW_ENABLE; opcodeFieldMap["en"] = PCM::ENABLE; opcodeFieldMap["invert"] = PCM::INVERT; opcodeFieldMap["thresh"] = PCM::THRESH; opcodeFieldMap["ch_mask"] = PCM::CH_MASK; opcodeFieldMap["fc_mask"] = PCM::FC_MASK; opcodeFieldMap["hname"] =PCM::H_EVENT_NAME; opcodeFieldMap["vname"] =PCM::V_EVENT_NAME; opcodeFieldMap["multiplier"] = PCM::MULTIPLIER; opcodeFieldMap["divider"] = PCM::DIVIDER; opcodeFieldMap["ctr"] = PCM::COUNTER_INDEX; evt_ctx.m = m; evt_ctx.ctrs.clear();//fill the ctrs by evt_handler call back func. try { load_events(ev_file_name, opcodeFieldMap, iio_evt_parse_handler, (void *)&evt_ctx, nameMap); } catch (std::exception & e) { std::cerr << "Error info:" << e.what() << "\n"; std::cerr << "The event configuration file (" << ev_file_name << ") cannot be loaded. Please verify the file. Exiting.\n"; exit(EXIT_FAILURE); } #ifdef PCM_DEBUG print_nameMap(nameMap); #endif results.resize(m->getNumSockets(), stack_content(m->getMaxNumOfIIOStacks(), ctr_data())); mainLoop([&]() { collect_data(m, delay, iios, evt_ctx.ctrs); vector display_buffer = csv ? build_csv(iios, evt_ctx.ctrs, human_readable, show_root_port, csv_delimiter, nameMap) : build_display(iios, evt_ctx.ctrs, pciDB, nameMap); display(display_buffer, *output); return true; }); file_stream.close(); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-latency.cpp000066400000000000000000000411631475730356400161170ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2018-2022, Intel Corporation // // written by Subhiksha Ravisundar #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include "lspci.h" #include "utils.h" using namespace std; using namespace pcm; #define DDR 0 #define PMM 1 #define L1 0 #define L2 1 #define RPQ_OCC 0 #define RPQ_INS 1 #define WPQ_OCC 2 #define WPQ_INS 3 #define FB_OCC_RD 0 #define FB_INS_RD 1 #define PCM_DELAY_DEFAULT 3.0 // in seconds #define MAX_CORES 4096 EventSelectRegister regs[2]; const uint8_t max_sockets = 64; struct socket_info_uncore { int socket_id; double rlatency; double wlatency; double rinsert; double winsert; double roccupancy; double woccupancy; }; struct core_info { int core_id; int socket; int thread; double latency; double occ_rd; double insert_rd; core_info() : core_id(0), socket(0), thread(0), latency(0.), occ_rd(0.), insert_rd(0.) {} }; struct socket_info_pci { int socket_id; uint64_t latency; }; struct res_uncore { string name; vector skt; } uncore_event[10]; struct res_core { string name; vector core; vector socket; } core_latency[10]; double DRAMSpeed; ServerUncoreCounterState * BeforeState; ServerUncoreCounterState * AfterState; std::shared_ptr SysBeforeState, SysAfterState; std::shared_ptr > BeforeState_core, AfterState_core; std::shared_ptr > DummySocketStates; void collect_beforestate_uncore(PCM *m) { for (unsigned int i=0; igetNumSockets(); i++) { BeforeState[i] = m->getServerUncoreCounterState(i); } } void collect_afterstate_uncore(PCM *m) { for (unsigned int i=0; igetNumSockets(); i++) { AfterState[i] = m->getServerUncoreCounterState(i); } } void store_latency_uncore(PCM *m, bool ddr, int delay_ms) { for (unsigned int i=0; igetNumSockets(); i++) { uncore_event[ddr].skt[i].socket_id = i; const double delay_seconds = double(delay_ms) / 1000.; DRAMSpeed = double(getDRAMClocks(0, BeforeState[i], AfterState[i]))/(double(1e9) * delay_seconds); uncore_event[ddr].skt[i].rinsert = 0; uncore_event[ddr].skt[i].roccupancy = 0; uncore_event[ddr].skt[i].winsert = 0; uncore_event[ddr].skt[i].woccupancy = 0; for (size_t channel = 0; channel < m->getMCChannelsPerSocket(); ++channel) { uncore_event[ddr].skt[i].rinsert += (double)getMCCounter((uint32)channel, RPQ_INS, BeforeState[i], AfterState[i]); uncore_event[ddr].skt[i].roccupancy += (double)getMCCounter((uint32)channel, RPQ_OCC, BeforeState[i], AfterState[i]); uncore_event[ddr].skt[i].winsert += (double)getMCCounter((uint32)channel, WPQ_INS, BeforeState[i], AfterState[i]); uncore_event[ddr].skt[i].woccupancy += (double)getMCCounter((uint32)channel, WPQ_OCC, BeforeState[i], AfterState[i]); } if (uncore_event[ddr].skt[i].rinsert == 0.) { uncore_event[ddr].skt[i].rlatency = 0; } else { uncore_event[ddr].skt[i].rlatency = uncore_event[ddr].skt[i].roccupancy / uncore_event[ddr].skt[i].rinsert; } if (uncore_event[ddr].skt[i].winsert == 0.) { uncore_event[ddr].skt[i].wlatency = 0; } else { uncore_event[ddr].skt[i].wlatency = uncore_event[ddr].skt[i].woccupancy / uncore_event[ddr].skt[i].winsert; } swap(BeforeState[i], AfterState[i]); } } void collect_beforestate_core(PCM *m) { m->getAllCounterStates(*SysBeforeState.get(), *DummySocketStates.get(), *BeforeState_core.get()); } void collect_afterstate_core(PCM *m) { m->getAllCounterStates(*SysAfterState.get(), *DummySocketStates.get(), *AfterState_core.get()); } void store_latency_core(PCM *m) { unsigned int extra_clocks_for_L1_miss = 5; struct res_core core_event[3]; for (int k=0; k < 3; k++) { core_event[k].core.resize(MAX_CORES); } for (auto & s : core_latency[L1].socket) { s.occ_rd = 0; s.insert_rd = 0; } for (unsigned int i=0; igetNumCores(); i++) { const double frequency = (((double)getCycles(BeforeState_core->operator[](i), AfterState_core->operator[](i)) / (double)getRefCycles(BeforeState_core->operator[](i), AfterState_core->operator[](i))) * (double)m->getNominalFrequency()) / 1000000000; for(int j=0; j<2; j++)// 2 events { core_event[j].core[i].core_id = i; core_event[j].core[i].latency = (double)getNumberOfCustomEvents(j, BeforeState_core->operator[](i), AfterState_core->operator[](i)); } // L1 latency //Adding 5 clocks for L1 Miss core_latency[L1].core[i].latency = ((core_event[FB_OCC_RD].core[i].latency/core_event[FB_INS_RD].core[i].latency)+extra_clocks_for_L1_miss)/frequency; core_latency[L1].core[i].occ_rd = (core_event[FB_OCC_RD].core[i].latency); core_latency[L1].core[i].insert_rd = (core_event[FB_INS_RD].core[i].latency); const auto s = m->getSocketId(i); core_latency[L1].socket[s].occ_rd += (core_latency[L1].core[i].occ_rd + extra_clocks_for_L1_miss * core_latency[L1].core[i].insert_rd) / frequency; core_latency[L1].socket[s].insert_rd += core_latency[L1].core[i].insert_rd; } for (auto & s : core_latency[L1].socket) { s.latency = s.occ_rd / s.insert_rd; } swap(BeforeState_core, AfterState_core); swap(SysBeforeState, SysAfterState); } void print_verbose(PCM *m, int ddr_ip) { cout << "L1 Cache Latency ============================= \n"; for (unsigned int i=0; igetNumCores(); i++) { cout << "Core: " << i << "\n"; cout << "L1 Occupancy read: " << core_latency[0].core[i].occ_rd << "\n"; cout << "L1 Inserts read: " << core_latency[0].core[i].insert_rd << "\n"; cout << "\n"; } if (ddr_ip == DDR) { cout << "DDR Latency =================================\n"; cout << "Read Inserts Socket0: " << uncore_event[DDR].skt[0].rinsert << "\n"; cout << "Read Occupancy Socket0: " << uncore_event[DDR].skt[0].roccupancy << "\n"; cout << "Read Inserts Socket1: " << uncore_event[DDR].skt[1].rinsert << "\n"; cout << "Read Occupancy Socket1: " << uncore_event[DDR].skt[1].roccupancy << "\n"; cout << "\n"; cout << "Write Inserts Socket0: " << uncore_event[DDR].skt[0].winsert << "\n"; cout << "Write Occupancy Socket0: " << uncore_event[DDR].skt[0].woccupancy << "\n"; cout << "Write Inserts Socket1: " << uncore_event[DDR].skt[1].winsert << "\n"; cout << "Write Occupancy Socket1: " << uncore_event[DDR].skt[1].woccupancy << "\n"; } if (ddr_ip == PMM) { cout << "PMM Latency =================================\n"; cout << "Read Inserts Socket0: " << uncore_event[PMM].skt[0].rinsert << "\n"; cout << "Read Occupancy Socket0: " << uncore_event[PMM].skt[0].roccupancy << "\n"; cout << "Read Inserts Socket1: " << uncore_event[PMM].skt[1].rinsert << "\n"; cout << "Read Occupancy Socket1: " << uncore_event[PMM].skt[1].roccupancy << "\n"; cout << "\n"; cout << "Write Inserts Socket0: " << uncore_event[PMM].skt[0].winsert << "\n"; cout << "Write Occupancy Socket0: " << uncore_event[PMM].skt[0].woccupancy << "\n"; cout << "Write Inserts Socket1: " << uncore_event[PMM].skt[1].winsert << "\n"; cout << "Write Occupancy Socket1: " << uncore_event[PMM].skt[1].woccupancy << "\n"; } } void print_ddr(PCM *m, int ddr_ip) { if (ddr_ip == PMM) { if (m->PMMTrafficMetricsAvailable()) { cout << "PMM read Latency(ns)\n"; for (unsigned int n=0; ngetNumSockets(); n++) { cout << "Socket" << n << ": " << double(uncore_event[PMM].skt[n].rlatency)/DRAMSpeed; cout << "\n"; } } else { cout << "PMM metrics are not supported on your processor\n"; } } if (ddr_ip == DDR) { cout << "DDR read Latency(ns)\n"; for (unsigned int n=0; ngetNumSockets(); n++) { cout << "Socket" << n << ": " << double(uncore_event[DDR].skt[n].rlatency)/DRAMSpeed; cout << "\n"; } } } void print_core_stats(PCM *m, unsigned int core_size_per_socket, vector>> &sk_th) { auto printHeader = []() { cout << "\n\n"; cout << "L1 Cache Miss Latency(ns) [Adding 5 clocks for L1 Miss]\n\n";; }; printHeader(); for (unsigned int sid=0; sidgetNumSockets(); sid++) { for (unsigned int tid=0; tid< m->getThreadsPerCore(); tid++) { cout << "Socket" << sid << " Thread" << tid << " "; } } cout << "\n-----------------------------------------------------------------------------\n"; for (unsigned int cid=0; cidgetNumSockets(); sid++) { for (unsigned int tid=0; tidgetThreadsPerCore(); tid++) { cout << "Core" << sk_th[sid][tid][cid].core_id << ": " << fixed << setprecision(2) << sk_th[sid][tid][cid].latency << " "; } } cout << "\n"; } cout << "\n"; cout << "Average latency per socket"; printHeader(); for (unsigned int s = 0; s < m->getNumSockets(); ++s) { cout << "Socket" << s << ": " << core_latency[L1].socket[s].latency << "\n"; } cout << "\n"; } void print_all_stats(PCM *m, bool enable_pmm, bool enable_verbose) { vector < vector < vector < struct core_info >>> sk_th; unsigned int sid, cid, tid; unsigned int core_size_per_socket=0; //Populate Core info per Socket and thread_id //Create 3D vector with Socket as 1D, Thread as 2D and Core info for the 3D for (sid = 0; sid < m->getNumSockets(); sid++) { vector < vector > tmp_thread; for (tid = 0; tid < m->getThreadsPerCore(); tid++) { vector tmp_core; for (cid = 0; cid < m->getNumCores(); cid++) { if ((sid == (unsigned int)(m->getSocketId(cid))) && (tid == (unsigned int)(m->getThreadId(cid)))) { core_info tmp; tmp.core_id = cid; tmp.latency = core_latency[L1].core[cid].latency; tmp_core.push_back(tmp); } } core_size_per_socket = (unsigned int)tmp_core.size(); tmp_thread.push_back(tmp_core); } sk_th.push_back(tmp_thread); } print_core_stats(m, core_size_per_socket, sk_th); if (m->DDRLatencyMetricsAvailable()) { print_ddr(m, enable_pmm); if (enable_verbose) print_verbose(m, enable_pmm); } } EventSelectRegister build_core_register(uint64 reg_used, uint64 value, uint64 usr, uint64 os, uint64 enable, uint64 umask, uint64 event_select, uint64 edge) { regs[reg_used].value = value; regs[reg_used].fields.usr = usr; regs[reg_used].fields.os = os; regs[reg_used].fields.enable = enable; regs[reg_used].fields.umask = umask; regs[reg_used].fields.event_select = event_select; regs[reg_used].fields.edge = edge; return regs[reg_used]; } void check_status(PCM *m, PCM::ErrorCode status) { m->checkError(status); print_cpu_details(); if(!(m->LatencyMetricsAvailable())) { cerr << "Platform not Supported! Program aborted\n"; exit(EXIT_FAILURE); } if(m->getNumSockets() > max_sockets) { cerr << "Only systems with up to " <<(int)max_sockets<< " sockets are supported! Program aborted\n"; exit(EXIT_FAILURE); } } void build_registers(PCM *m, PCM::ExtendedCustomCoreEventDescription conf, bool enable_pmm, bool /*enable_verbose*/) { //Check if Online Cores = Available Cores. This version only supports available cores = online cores if (m->getNumCores() != m->getNumOnlineCores()) { cout << "Number of online cores should be equal to number of available cores\n"; exit(EXIT_FAILURE); } //Check for Maximum Custom Core Events if (m->getMaxCustomCoreEvents() < 2) { cout << "System should support a minimum of 2 Custom Core Events to run pcm-latency\n"; exit(EXIT_FAILURE); } //Creating conf conf.fixedCfg = NULL; // default conf.nGPCounters = 2; conf.gpCounterCfg = regs; conf.OffcoreResponseMsrValue[0] = 0; conf.OffcoreResponseMsrValue[1] = 0; // Registers for L1 cache regs[FB_OCC_RD] = build_core_register(FB_OCC_RD, 0, 1, 1, 1, 0x01, 0x48, 0); //L1d Fill Buffer Occupancy (Read Only) regs[FB_INS_RD] = build_core_register(FB_INS_RD, 0, 1, 1, 1, 0x48, 0xd1, 0); //MEM_LOAD_RETIRED(FB_HIT + L1_MISS) //Restructuring Counters for (int i=0; i <5; i++) { uncore_event[i].skt.resize(m->getNumSockets()); core_latency[i].core.resize(m->getNumCores()); core_latency[i].socket.resize(m->getNumSockets()); } //Program Core and Uncore m->resetPMU(); PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf); check_status(m, status); m->programServerUncoreLatencyMetrics(enable_pmm); } void collect_data(PCM *m, bool enable_pmm, bool enable_verbose, int delay_ms, MainLoop & mainLoop) { BeforeState = new ServerUncoreCounterState[m->getNumSockets()]; AfterState = new ServerUncoreCounterState[m->getNumSockets()]; mainLoop([&]() { collect_beforestate_uncore(m); collect_beforestate_core(m); MySleepMs(delay_ms); collect_afterstate_uncore(m); collect_afterstate_core(m); store_latency_uncore(m, enable_pmm, delay_ms);// 0 for DDR store_latency_core(m); print_all_stats(m, enable_pmm, enable_verbose); std::cout << std::flush; return true; }); deleteAndNullifyArray(BeforeState); deleteAndNullifyArray(AfterState); } void print_usage() { cout << "\nUsage: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " --PMM | -pmm => to enable PMM (Default DDR uncore latency)\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -v | --verbose => verbose Output\n"; cout << "\n"; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream; check_and_set_silent(argc, argv, nullStream); set_signal_handlers(); std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; std::cout << "\n This utility measures Latency information\n\n"; bool enable_pmm = false; bool enable_verbose = false; int delay_ms = 1000; MainLoop mainLoop; if(argc > 1) do { argv++; argc--; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"--PMM", "-pmm"})) { enable_pmm = true; continue; } else if (check_argument_equals(*argv, {"--verbose", "-v", "/v"})) { enable_verbose = true; continue; } } while(argc > 1); PCM::ExtendedCustomCoreEventDescription conf; PCM * m = PCM::getInstance(); SysBeforeState = std::make_shared(); SysAfterState = std::make_shared(); BeforeState_core = std::make_shared >(); AfterState_core = std::make_shared >(); DummySocketStates = std::make_shared >(); build_registers(m, conf, enable_pmm, enable_verbose); collect_data(m, enable_pmm, enable_verbose, delay_ms, mainLoop); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-lspci.cpp000066400000000000000000000113471475730356400155730ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2017-2022, Intel Corporation // written by Patrick Lu #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include "lspci.h" using namespace std; using namespace pcm; void scanBus(int bus, const PCIDB & pciDB) { if(!PciHandleType::exists(0, bus, 8, 2)) return; std::cout << "BUS 0x" << std::hex << bus << std::dec << "\n"; struct iio_skx iio_skx; PciHandleType h(0, bus, 8, 2); uint32 cpubusno = 0; h.read32(0xcc, &cpubusno); // CPUBUSNO register iio_skx.stacks[0].busno = cpubusno & 0xff; iio_skx.stacks[1].busno = (cpubusno >> 8) & 0xff; iio_skx.stacks[2].busno = (cpubusno >> 16) & 0xff; iio_skx.stacks[3].busno = (cpubusno >> 24) & 0xff; h.read32(0xd0, &cpubusno); // CPUBUSNO1 register iio_skx.stacks[4].busno = cpubusno & 0xff; iio_skx.stacks[5].busno = (cpubusno >> 8) & 0xff; for (uint8_t stack = 0; stack < 6; stack++) { uint8_t busno = iio_skx.stacks[stack].busno; std::cout << "stack" << unsigned(stack) << std::hex << ":0x" << unsigned(busno) << std::dec << ",(" << unsigned(busno) << ")\n"; for (uint8_t part = 0; part < 3; part++) { struct pci *pci = &iio_skx.stacks[stack].parts[part].root_pci_dev; struct bdf *bdf = &pci->bdf; bdf->busno = busno; bdf->devno = part; bdf->funcno = 0; /* This is a workaround to catch some IIO stack does not exist */ if (stack != 0 && busno == 0) pci->exist = false; else (void)probe_pci(pci); } } for (uint8_t stack = 0; stack < 6; stack++) { for (uint8_t part = 0; part < 4; part++) { struct pci p = iio_skx.stacks[stack].parts[part].root_pci_dev; if (!p.exist) continue; for (uint8_t b = p.secondary_bus_number; b <= p.subordinate_bus_number; b++) { /* FIXME: for 0:0.0, we may need to scan from secondary switch down; lgtm [cpp/fixme-comment] */ for (uint8_t d = 0; d < 32; d++) { for (uint8_t f = 0; f < 8; f++) { struct pci pci; pci.exist = false; pci.bdf.busno = b; pci.bdf.devno = d; pci.bdf.funcno = f; if (probe_pci(&pci)) iio_skx.stacks[stack].parts[part].child_pci_devs.push_back(pci); } } } } } for (uint8_t stack = 1; stack < 6; stack++) { /* XXX: Maybe there is no point to display all built-in devices on DMI/CBDMA stacks, if so, change stack = 1 */ for (uint8_t part = 0; part < 4; part++) { vector v = iio_skx.stacks[stack].parts[part].child_pci_devs; struct pci pp = iio_skx.stacks[stack].parts[part].root_pci_dev; if (pp.exist) print_pci(pp, pciDB); for (vector::const_iterator iunit = v.begin(); iunit != v.end(); ++iunit) { struct pci p = *iunit; if (p.exist) print_pci(p, pciDB); } } } } PCM_MAIN_NOTHROW; int mainThrows(int /*argc*/, char * /*argv*/[]) { PCIDB pciDB; load_PCIDB(pciDB); PCM * m = PCM::getInstance(); if (!m->isSkxCompatible()) { cerr << "PCI tree display is currently not supported for processor family/model 0x" << std::hex << m->getCPUFamilyModel() << std::dec << "\n"; } else { std::cout << "\n Display PCI tree information\n\n"; for (int bus = 0; bus < 256; ++bus) scanBus(bus, pciDB); } cerr << "Scanning all devices in group 0\n"; for (uint32 bus = 0; bus < 256; ++bus) { for (uint32 device = 0; device < 32; ++device) { for (uint32 function = 0; function < 8; ++function) { if (PciHandleType::exists(0, bus, device, function)) { PciHandleType h(0, bus, device, function); uint32 value = 0; h.read32(0, &value); const uint32 vendor = extract_bits_32(value, 0, 15); const uint32 deviceID = extract_bits_32(value, 16, 31); std::cout << "0:" << bus << ":" << device << ":" << function << " vendor 0x" << std::hex << vendor << " device 0x" << deviceID << std::dec << "\n"; } } } } return 0; } pcm-202502/src/pcm-memory.cpp000066400000000000000000002123471475730356400157740ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Patrick Lu // increased max sockets to 256 - Thomas Willhalm /*! \file pcm-memory.cpp \brief Example of using CPU counters: implements a performance counter monitoring utility for memory controller channels and DIMMs (ranks) + PMM memory traffic */ #include #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #include #include // for gettimeofday() #endif #include #include #include #include #include #include #include #include "cpucounters.h" #include "utils.h" #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs #define DEFAULT_DISPLAY_COLUMNS 2 using namespace std; using namespace pcm; constexpr uint32 max_sockets = 256; uint32 max_imc_channels = ServerUncoreCounterState::maxChannels; const uint32 max_edc_channels = ServerUncoreCounterState::maxChannels; const uint32 max_imc_controllers = ServerUncoreCounterState::maxControllers; bool SPR_CXL = false; // use SPR CXL monitoring implementation typedef struct memdata { float iMC_Rd_socket_chan[max_sockets][ServerUncoreCounterState::maxChannels]{}; float iMC_Wr_socket_chan[max_sockets][ServerUncoreCounterState::maxChannels]{}; float iMC_PMM_Rd_socket_chan[max_sockets][ServerUncoreCounterState::maxChannels]{}; float iMC_PMM_Wr_socket_chan[max_sockets][ServerUncoreCounterState::maxChannels]{}; float MemoryMode_Miss_socket_chan[max_sockets][ServerUncoreCounterState::maxChannels]{}; float iMC_Rd_socket[max_sockets]{}; float iMC_Wr_socket[max_sockets]{}; float iMC_PMM_Rd_socket[max_sockets]{}; float iMC_PMM_Wr_socket[max_sockets]{}; float CXLMEM_Rd_socket_port[max_sockets][ServerUncoreCounterState::maxCXLPorts]{}; float CXLMEM_Wr_socket_port[max_sockets][ServerUncoreCounterState::maxCXLPorts]{}; float CXLCACHE_Rd_socket_port[max_sockets][ServerUncoreCounterState::maxCXLPorts]{}; float CXLCACHE_Wr_socket_port[max_sockets][ServerUncoreCounterState::maxCXLPorts]{}; float MemoryMode_Miss_socket[max_sockets]{}; bool NM_hit_rate_supported{}; bool BHS_NM{}; bool BHS{}; float MemoryMode_Hit_socket[max_sockets]{}; bool M2M_NM_read_hit_rate_supported{}; float NM_hit_rate[max_sockets]{}; float M2M_NM_read_hit_rate[max_sockets][max_imc_controllers]{}; float EDC_Rd_socket_chan[max_sockets][max_edc_channels]{}; float EDC_Wr_socket_chan[max_sockets][max_edc_channels]{}; float EDC_Rd_socket[max_sockets]{}; float EDC_Wr_socket[max_sockets]{}; uint64 partial_write[max_sockets]{}; ServerUncoreMemoryMetrics metrics{}; } memdata_t; bool anyPmem(const ServerUncoreMemoryMetrics & metrics) { return (metrics == Pmem) || (metrics == PmemMixedMode) || (metrics == PmemMemoryMode); } bool skipInactiveChannels = true; bool enforceFlush = false; void print_help(const string & prog_name) { cout << "\n Usage: \n " << prog_name << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -rank=X | /rank=X => monitor DIMM rank X. At most 2 out of 8 total ranks can be monitored simultaneously.\n"; cout << " -pmm | /pmm | -pmem | /pmem => monitor PMM memory bandwidth and DRAM cache hit rate in Memory Mode (default on systems with PMM support).\n"; cout << " -mm => monitor detailed PMM Memory Mode metrics per-socket.\n"; cout << " -mixed => monitor PMM mixed mode (AppDirect + Memory Mode).\n"; cout << " -partial => monitor partial writes instead of PMM (default on systems without PMM support).\n"; cout << " -nc | --nochannel | /nc => suppress output for individual channels.\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -columns=X | /columns=X => Number of columns to display the NUMA Nodes, defaults to 2.\n"; cout << " -all | /all => Display all channels (even with no traffic)\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -u => update measurements instead of printing new ones\n"; print_enforce_flush_option_help(); #ifdef _MSC_VER cout << " --uninstallDriver | --installDriver=> (un)install driver\n"; #endif cout << " Examples:\n"; cout << " " << prog_name << " 1 => print counters every second without core and socket output\n"; cout << " " << prog_name << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << prog_name << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } void printSocketBWHeader(uint32 no_columns, uint32 skt, const bool show_channel_output) { for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|---------------------------------------|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-- Socket " << setw(2) << i << " --|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|---------------------------------------|"; } cout << "\n"; if (show_channel_output) { for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-- Memory Channel Monitoring --|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|---------------------------------------|"; } cout << "\n"; } } void printSocketRankBWHeader(uint32 no_columns, uint32 skt) { for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-------------------------------------------|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-- Socket " << setw(2) << i << " --|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-------------------------------------------|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-- DIMM Rank Monitoring --|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|-------------------------------------------|"; } cout << "\n"; } void printSocketRankBWHeader_cvt(const uint32 numSockets, const uint32 num_imc_channels, const int rankA, const int rankB) { printDateForCSV(Header1); for (uint32 skt = 0 ; skt < (numSockets) ; ++skt) { for (uint32 channel = 0; channel < num_imc_channels; ++channel) { if (rankA >= 0) cout << "SKT" << skt << "," << "SKT" << skt << ","; if (rankB >= 0) cout << "SKT" << skt << "," << "SKT" << skt << ","; } } cout << endl; printDateForCSV(Header2); for (uint32 skt = 0 ; skt < (numSockets) ; ++skt) { for (uint32 channel = 0; channel < num_imc_channels; ++channel) { if (rankA >= 0) { cout << "Mem_Ch" << channel << "_R" << rankA << "_reads," << "Mem_Ch" << channel << "_R" << setw(1) << rankA << "_writes,"; } if (rankB >= 0) { cout << "Mem_Ch" << channel << "_R" << rankB << "_reads," << "Mem_Ch" << channel << "_R" << setw(1) << rankB << "_writes,"; } } } cout << endl; } void printSocketChannelBW(PCM *, memdata_t *md, uint32 no_columns, uint32 skt) { for (uint32 channel = 0; channel < max_imc_channels; ++channel) { // check all the sockets for bad channel "channel" unsigned bad_channels = 0; for (uint32 i=skt; i<(skt+no_columns); ++i) { if (md->iMC_Rd_socket_chan[i][channel] < 0.0 || md->iMC_Wr_socket_chan[i][channel] < 0.0) //If the channel read neg. value, the channel is not working; skip it. ++bad_channels; } if (bad_channels == no_columns) { // the channel is missing on all sockets in the row continue; } for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- Mem Ch " << setw(2) << channel << ": Reads (MB/s): " << setw(8) << md->iMC_Rd_socket_chan[i][channel] << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- Writes(MB/s): " << setw(8) << md->iMC_Wr_socket_chan[i][channel] << " --|"; } cout << "\n"; if (md->metrics == Pmem) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- PMM Reads(MB/s) : " << setw(8) << md->iMC_PMM_Rd_socket_chan[i][channel] << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- PMM Writes(MB/s) : " << setw(8) << md->iMC_PMM_Wr_socket_chan[i][channel] << " --|"; } cout << "\n"; } } } void printSocketChannelBW(uint32 no_columns, uint32 skt, uint32 num_imc_channels, const std::vector& uncState1, const std::vector& uncState2, uint64 elapsedTime, int rankA, int rankB) { for (uint32 channel = 0; channel < num_imc_channels; ++channel) { if(rankA >= 0) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- Mem Ch " << setw(2) << channel << " R " << setw(1) << rankA << ": Reads (MB/s): " << setw(8) << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::READ_RANK_A,uncState1[i],uncState2[i]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- Writes(MB/s): " << setw(8) << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::WRITE_RANK_A,uncState1[i],uncState2[i]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << " --|"; } cout << "\n"; } if(rankB >= 0) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- Mem Ch " << setw(2) << channel << " R " << setw(1) << rankB << ": Reads (MB/s): " << setw(8) << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::READ_RANK_B,uncState1[i],uncState2[i]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- Writes(MB/s): " << setw(8) << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::WRITE_RANK_B,uncState1[i],uncState2[i]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << " --|"; } cout << "\n"; } } } void printSocketChannelBW_cvt(const uint32 numSockets, const uint32 num_imc_channels, const std::vector& uncState1, const std::vector& uncState2, const uint64 elapsedTime, const int rankA, const int rankB) { printDateForCSV(Data); for (uint32 skt = 0 ; skt < numSockets; ++skt) { for (uint32 channel = 0 ; channel < num_imc_channels ; ++channel) { if(rankA >= 0) { cout << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::READ_RANK_A,uncState1[skt],uncState2[skt]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << "," << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::WRITE_RANK_A,uncState1[skt],uncState2[skt]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << ","; } if(rankB >= 0) { cout << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::READ_RANK_B,uncState1[skt],uncState2[skt]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << "," << (float) (getMCCounter(channel,ServerUncorePMUs::EventPosition::WRITE_RANK_B,uncState1[skt],uncState2[skt]) * 64 / 1000000.0 / (elapsedTime/1000.0)) << ","; } } } cout << endl; } uint32 getNumCXLPorts(PCM* m) { static int numPorts = -1; if (numPorts < 0) { for (uint32 s = 0; s < m->getNumSockets(); ++s) { numPorts = (std::max)(numPorts, (int)m->getNumCXLPorts(s)); } assert(numPorts >= 0); } return (uint32)numPorts; } void printSocketCXLBW(PCM* m, memdata_t* md, uint32 no_columns, uint32 skt) { uint32 numPorts = getNumCXLPorts(m); if (numPorts > 0) { for (uint32 i = skt; i < (no_columns + skt); ++i) { cout << "|---------------------------------------|"; } cout << "\n"; for (uint32 i = skt; i < (no_columns + skt); ++i) { cout << "|-- CXL Port Monitoring --|"; } cout << "\n"; for (uint32 i = skt; i < (no_columns + skt); ++i) { cout << "|---------------------------------------|"; } cout << "\n"; } for (uint32 port = 0; port < numPorts; ++port) { if (md->BHS) { for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- .mem " << setw(2) << port << " Reads (MB/s): " << setw(8) << md->CXLMEM_Rd_socket_port[i][port] << " --|"; } } else { for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- .mem --|"; } } cout << "\n"; for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- Writes(MB/s): " << setw(8) << md->CXLMEM_Wr_socket_port[i][port] << " --|"; } cout << "\n"; if (md->BHS) { for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- .cache " << setw(2) << port << " dv->hst(MB/s): " << setw(8) << md->CXLCACHE_Rd_socket_port[i][port] << " --|"; } } else { for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- .cache --|"; } } cout << "\n"; for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- hst->dv(MB/s): " << setw(8) << md->CXLCACHE_Wr_socket_port[i][port] << " --|"; } cout << "\n"; } } float AD_BW(const memdata_t *md, const uint32 skt) { const auto totalPMM = md->iMC_PMM_Rd_socket[skt] + md->iMC_PMM_Wr_socket[skt]; return (max)(totalPMM - md->MemoryMode_Miss_socket[skt], float(0.0)); } float PMM_MM_Ratio(const memdata_t *md, const uint32 skt) { const auto dram = md->iMC_Rd_socket[skt] + md->iMC_Wr_socket[skt]; return md->MemoryMode_Miss_socket[skt] / dram; } void printSocketBWFooter(PCM *m, uint32 no_columns, uint32 skt, const memdata_t *md) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " Mem Read (MB/s) :" << setw(9) << md->iMC_Rd_socket[i] << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " Mem Write(MB/s) :" << setw(9) << md->iMC_Wr_socket[i] << " --|"; } cout << "\n"; if (anyPmem(md->metrics)) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " PMM Read (MB/s): " << setw(8) << md->iMC_PMM_Rd_socket[i] << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " PMM Write(MB/s): " << setw(8) << md->iMC_PMM_Wr_socket[i] << " --|"; } cout << "\n"; } if (md->metrics == PmemMixedMode) { for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " PMM AD Bw(MB/s): " << setw(8) << AD_BW(md, i) << " --|"; } cout << "\n"; for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " PMM MM Bw(MB/s): " << setw(8) << md->MemoryMode_Miss_socket[i] << " --|"; } cout << "\n"; for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " PMM MM Bw/DRAM Bw:" << setw(8) << PMM_MM_Ratio(md, i) << " --|"; } cout << "\n"; } else if (md->metrics == Pmem && md->M2M_NM_read_hit_rate_supported) { for (uint32 ctrl = 0; ctrl < max_imc_controllers; ++ctrl) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << "." << ctrl << " NM read hit rate :" << setw(6) << md->M2M_NM_read_hit_rate[i][ctrl] << " --|"; } cout << "\n"; } } if ((md->metrics == PmemMemoryMode && md->NM_hit_rate_supported) || md->BHS_NM == true) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " NM hit rate: " << setw(6) << md->NM_hit_rate[i] << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " NM hits (M/s): " << setw(7) << (md->MemoryMode_Hit_socket[i])/1000000. << " --|"; } cout << "\n"; for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " NM misses (M/s): " << setw(7) << (md->MemoryMode_Miss_socket[i])/1000000. << " --|"; } cout << "\n"; } if (md->BHS_NM == true) { for (uint32 i = skt; i < (skt + no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " NM miss Bw(MB/s):" << setw(9) << (md->MemoryMode_Miss_socket[i] * 64. * 2.) / 1000000. << " --|"; } cout << "\n"; } if ( md->metrics == PartialWrites && m->getCPUFamilyModel() != PCM::SRF && m->getCPUFamilyModel() != PCM::GNR && m->getCPUFamilyModel() != PCM::GNR_D && m->getCPUFamilyModel() != PCM::GRR ) { for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " P. Write (T/s): " << dec << setw(10) << md->partial_write[i] << " --|"; } cout << "\n"; } for (uint32 i=skt; i<(skt+no_columns); ++i) { cout << "|-- SKT " << setw(2) << i << " Memory (MB/s): " << setw(11) << right << (md->iMC_Rd_socket[i]+md->iMC_Wr_socket[i]+ md->iMC_PMM_Rd_socket[i]+md->iMC_PMM_Wr_socket[i]) << " --|"; } cout << "\n"; for (uint32 i=skt; i<(no_columns+skt); ++i) { cout << "|---------------------------------------|"; } cout << "\n"; } void display_bandwidth(PCM *m, memdata_t *md, const uint32 no_columns, const bool show_channel_output, const bool print_update, const float CXL_Read_BW) { float sysReadDRAM = 0.0, sysWriteDRAM = 0.0, sysReadPMM = 0.0, sysWritePMM = 0.0; uint32 numSockets = m->getNumSockets(); uint32 skt = 0; cout.setf(ios::fixed); cout.precision(2); if (print_update) clear_screen(); while (skt < numSockets) { auto printHBM = [&]() { cout << "\ \r|---------------------------------------||---------------------------------------|\n\ \r|-- Processor socket " << skt << " --|\n\ \r|---------------------------------------||---------------------------------------|\n\ \r|-- DRAM Channel Monitoring --||-- HBM Channel Monitoring --|\n\ \r|---------------------------------------||---------------------------------------|\n\ \r"; const uint32 max_channels = (std::max)(max_edc_channels, max_imc_channels); if (show_channel_output) { float iMC_Rd, iMC_Wr, EDC_Rd, EDC_Wr; for (uint64 channel = 0; channel < max_channels; ++channel) { if (channel < max_imc_channels) { iMC_Rd = md->iMC_Rd_socket_chan[skt][channel]; iMC_Wr = md->iMC_Wr_socket_chan[skt][channel]; } else { iMC_Rd = -1.0; iMC_Wr = -1.0; } if (channel < max_edc_channels) { EDC_Rd = md->EDC_Rd_socket_chan[skt][channel]; EDC_Wr = md->EDC_Wr_socket_chan[skt][channel]; } else { EDC_Rd = -1.0; EDC_Wr = -1.0; } if (iMC_Rd >= 0.0 && iMC_Wr >= 0.0 && EDC_Rd >= 0.0 && EDC_Wr >= 0.0) cout << "|-- DRAM Ch " << setw(2) << channel << ": Reads (MB/s):" << setw(8) << iMC_Rd << " --||-- HBM Ch " << setw(2) << channel << ": Reads (MB/s):" << setw(9) << EDC_Rd << " --|\n|-- Writes(MB/s):" << setw(8) << iMC_Wr << " --||-- Writes(MB/s):" << setw(9) << EDC_Wr << " --|\n"; else if ((iMC_Rd < 0.0 || iMC_Wr < 0.0) && EDC_Rd >= 0.0 && EDC_Wr >= 0.0) cout << "|-- " << " --||-- HBM Ch " << setw(2) << channel << ": Reads (MB/s):" << setw(9) << EDC_Rd << " --|\n|-- " << " --||-- Writes(MB/s):" << setw(9) << EDC_Wr << " --|\n"; else if (iMC_Rd >= 0.0 && iMC_Wr >= 0.0 && (EDC_Rd < 0.0 || EDC_Wr < 0.0)) cout << "|-- DRAM Ch " << setw(2) << channel << ": Reads (MB/s):" << setw(8) << iMC_Rd << " --||-- " << " --|\n|-- Writes(MB/s):" << setw(8) << iMC_Wr << " --||-- " << " --|\n"; else continue; } } cout << "\ \r|-- DRAM Mem Read (MB/s):" << setw(11) << md->iMC_Rd_socket[skt] << " --||-- HBM Read (MB/s):" << setw(14+3) << md->EDC_Rd_socket[skt] << " --|\n\ \r|-- DRAM Mem Write (MB/s):" << setw(11) << md->iMC_Wr_socket[skt] << " --||-- HBM Write(MB/s):" << setw(14+3) << md->EDC_Wr_socket[skt] << " --|\n\ \r|-- DRAM Memory (MB/s) :" << setw(11) << md->iMC_Rd_socket[skt] + md->iMC_Wr_socket[skt] << " --||-- HBM (MB/s) :" << setw(14+3) << md->EDC_Rd_socket[skt] + md->EDC_Wr_socket[skt] << " --|\n\ \r|---------------------------------------||---------------------------------------|\n\ \r"; sysReadDRAM += (md->iMC_Rd_socket[skt] + md->EDC_Rd_socket[skt]); sysWriteDRAM += (md->iMC_Wr_socket[skt] + md->EDC_Wr_socket[skt]); skt += 1; }; auto printRow = [&skt,&show_channel_output,&m,&md,&sysReadDRAM,&sysWriteDRAM, &sysReadPMM, &sysWritePMM](const uint32 no_columns) { printSocketBWHeader(no_columns, skt, show_channel_output); if (show_channel_output) printSocketChannelBW(m, md, no_columns, skt); printSocketBWFooter(m, no_columns, skt, md); printSocketCXLBW(m, md, no_columns, skt); for (uint32 i = skt; i < (skt + no_columns); i++) { sysReadDRAM += md->iMC_Rd_socket[i]; sysWriteDRAM += md->iMC_Wr_socket[i]; sysReadPMM += md->iMC_PMM_Rd_socket[i]; sysWritePMM += md->iMC_PMM_Wr_socket[i]; } skt += no_columns; }; if (m->HBMmemoryTrafficMetricsAvailable()) { printHBM(); // no_columns is ignored, always 1 socket at a time } else if ((skt + no_columns) <= numSockets) // Full row { printRow(no_columns); } else //Display the remaining sockets in this row { printRow(numSockets - skt); } } { cout << "\ \r|---------------------------------------||---------------------------------------|\n"; if (anyPmem(md->metrics)) { cout << "\ \r|-- System DRAM Read Throughput(MB/s):" << setw(14) << sysReadDRAM << " --|\n\ \r|-- System DRAM Write Throughput(MB/s):" << setw(14) << sysWriteDRAM << " --|\n\ \r|-- System PMM Read Throughput(MB/s):" << setw(14) << sysReadPMM << " --|\n\ \r|-- System PMM Write Throughput(MB/s):" << setw(14) << sysWritePMM << " --|\n"; } if (SPR_CXL) { cout << "\ \r|-- System CXL Read Throughput(MB/s):" << setw(14) << CXL_Read_BW << " --|\n"; } cout << "\ \r|-- System Read Throughput(MB/s):" << setw(14) << sysReadDRAM+sysReadPMM << " --|\n\ \r|-- System Write Throughput(MB/s):" << setw(14) << sysWriteDRAM+sysWritePMM << " --|\n\ \r|-- System Memory Throughput(MB/s):" << setw(14) << sysReadDRAM+sysReadPMM+sysWriteDRAM+sysWritePMM << " --|\n\ \r|---------------------------------------||---------------------------------------|\n"; } } constexpr float CXLBWWrScalingFactor = 0.5; void display_bandwidth_csv(PCM *m, memdata_t *md, uint64 /*elapsedTime*/, const bool show_channel_output, const CsvOutputType outputType, const float CXL_Read_BW) { const uint32 numSockets = m->getNumSockets(); printDateForCSV(outputType); float sysReadDRAM = 0.0, sysWriteDRAM = 0.0, sysReadPMM = 0.0, sysWritePMM = 0.0; for (uint32 skt = 0; skt < numSockets; ++skt) { auto printSKT = [skt](int c = 1) { for (int i = 0; i < c; ++i) cout << "SKT" << skt << ','; }; if (show_channel_output) { for (uint64 channel = 0; channel < max_imc_channels; ++channel) { bool invalid_data = false; if (md->iMC_Rd_socket_chan[skt][channel] < 0.0 && md->iMC_Wr_socket_chan[skt][channel] < 0.0) //If the channel read neg. value, the channel is not working; skip it. invalid_data = true; choose(outputType, [printSKT]() { printSKT(2); }, [&channel]() { cout << "Ch" << channel << "Read," << "Ch" << channel << "Write,"; }, [&md, &skt, &channel, &invalid_data]() { if (invalid_data) cout << ",,"; else cout << setw(8) << md->iMC_Rd_socket_chan[skt][channel] << ',' << setw(8) << md->iMC_Wr_socket_chan[skt][channel] << ','; }); if (md->metrics == Pmem) { choose(outputType, [printSKT]() { printSKT(2); }, [&channel]() { cout << "Ch" << channel << "PMM_Read," << "Ch" << channel << "PMM_Write,"; }, [&skt, &md, &channel, &invalid_data]() { if (invalid_data) cout << ",,"; else cout << setw(8) << md->iMC_PMM_Rd_socket_chan[skt][channel] << ',' << setw(8) << md->iMC_PMM_Wr_socket_chan[skt][channel] << ','; }); } } } choose(outputType, [printSKT]() { printSKT(2); }, []() { cout << "Mem Read (MB/s),Mem Write (MB/s),"; }, [&md, &skt]() { cout << setw(8) << md->iMC_Rd_socket[skt] << ',' << setw(8) << md->iMC_Wr_socket[skt] << ','; }); if (anyPmem(md->metrics)) { choose(outputType, [printSKT]() { printSKT(2); }, []() { cout << "PMM_Read (MB/s), PMM_Write (MB/s),"; }, [&md, &skt]() { cout << setw(8) << md->iMC_PMM_Rd_socket[skt] << ',' << setw(8) << md->iMC_PMM_Wr_socket[skt] << ','; }); } if ((md->metrics == PmemMemoryMode && md->NM_hit_rate_supported) || md->BHS_NM == true) { choose(outputType, [printSKT]() { printSKT(3); }, []() { cout << "NM hit rate,"; cout << "NM hits (M/s),"; cout << "NM misses (M/s),"; }, [&md, &skt]() { cout << setw(8) << md->NM_hit_rate[skt]<< ','; cout << setw(8) << md->MemoryMode_Hit_socket[skt]/1000000. << ','; cout << setw(8) << md->MemoryMode_Miss_socket[skt]/1000000. << ','; }); } if (md->BHS_NM == true) { choose(outputType, [printSKT]() { printSKT(); }, []() { cout << "NM miss Bw (MB/s),"; }, [&md, &skt]() { cout << setw(9) << (md->MemoryMode_Miss_socket[skt] * 64. * 2.) / 1000000. << ','; }); } if (md->metrics == Pmem && md->M2M_NM_read_hit_rate_supported) { for (uint32 c = 0; c < max_imc_controllers; ++c) { choose(outputType, [printSKT]() { printSKT(); }, [c]() { cout << "iMC" << c << " NM read hit rate,"; }, [&md, &skt, c]() { cout << setw(8) << md->M2M_NM_read_hit_rate[skt][c] << ','; }); } } if (md->metrics == PmemMixedMode) { choose(outputType, [printSKT]() { printSKT(3); }, []() { cout << "PMM_AD (MB/s), PMM_MM (MB/s), PMM_MM_Bw/DRAM_Bw,"; }, [&md, &skt]() { cout << setw(8) << AD_BW(md, skt) << ',' << setw(8) << md->MemoryMode_Miss_socket[skt] << ',' << setw(8) << PMM_MM_Ratio(md, skt) << ','; }); } if (m->HBMmemoryTrafficMetricsAvailable() == false) { if ( md->metrics == PartialWrites && m->getCPUFamilyModel() != PCM::GNR && m->getCPUFamilyModel() != PCM::GNR_D && m->getCPUFamilyModel() != PCM::SRF && m->getCPUFamilyModel() != PCM::GRR ) { choose(outputType, [printSKT]() { printSKT(); }, []() { cout << "P. Write (T/s),"; }, [&md, &skt]() { cout << setw(10) << dec << md->partial_write[skt] << ','; }); } } choose(outputType, [printSKT]() { printSKT(); }, []() { cout << "Memory (MB/s),"; }, [&]() { cout << setw(8) << md->iMC_Rd_socket[skt] + md->iMC_Wr_socket[skt] << ','; sysReadDRAM += md->iMC_Rd_socket[skt]; sysWriteDRAM += md->iMC_Wr_socket[skt]; sysReadPMM += md->iMC_PMM_Rd_socket[skt]; sysWritePMM += md->iMC_PMM_Wr_socket[skt]; }); if (m->HBMmemoryTrafficMetricsAvailable()) { if (show_channel_output) { for (uint64 channel = 0; channel < max_edc_channels; ++channel) { if (md->EDC_Rd_socket_chan[skt][channel] < 0.0 && md->EDC_Wr_socket_chan[skt][channel] < 0.0) //If the channel read neg. value, the channel is not working; skip it. continue; choose(outputType, [printSKT]() { printSKT(2); }, [&channel]() { cout << "EDC_Ch" << channel << "Read," << "EDC_Ch" << channel << "Write,"; }, [&md, &skt, &channel]() { cout << setw(8) << md->EDC_Rd_socket_chan[skt][channel] << ',' << setw(8) << md->EDC_Wr_socket_chan[skt][channel] << ','; }); } } choose(outputType, [printSKT]() { printSKT(3); }, []() { cout << "HBM Read (MB/s), HBM Write (MB/s), HBM (MB/s),"; }, [&]() { cout << setw(8) << md->EDC_Rd_socket[skt] << ',' << setw(8) << md->EDC_Wr_socket[skt] << ',' << setw(8) << md->EDC_Rd_socket[skt] + md->EDC_Wr_socket[skt] << ','; sysReadDRAM += md->EDC_Rd_socket[skt]; sysWriteDRAM += md->EDC_Wr_socket[skt]; }); } for (uint64 port = 0; port < m->getNumCXLPorts(skt); ++port) { choose(outputType, [printSKT, &md]() { printSKT((md->BHS)? 4 : 2 ); }, [&port,&md]() { if (md->BHS) { cout << "CXL.mem_P" << port << "Read," << "CXL.mem_P" << port << "Write," << "CXL.cache_P" << port << "dv->hst," << "CXL.cache_P" << port << "hst->dv,"; } else { cout << "CXL.mem_P" << port << "Write," << "CXL.cache_P" << port << "hst->dv,"; } }, [&md, &skt, &port]() { if (md->BHS) { cout << setw(8) << md->CXLMEM_Rd_socket_port[skt][port] << ',' << setw(8) << md->CXLMEM_Wr_socket_port[skt][port] << ',' << setw(8) << md->CXLCACHE_Rd_socket_port[skt][port] << ',' << setw(8) << md->CXLCACHE_Wr_socket_port[skt][port] << ','; } else { cout << setw(8) << md->CXLMEM_Wr_socket_port[skt][port] << ',' << setw(8) << md->CXLCACHE_Wr_socket_port[skt][port] << ','; } }); } } if (anyPmem(md->metrics)) { choose(outputType, []() { cout << "System,System,System,System,"; }, []() { cout << "DRAMRead,DRAMWrite,PMMREAD,PMMWrite,"; }, [&]() { cout << setw(10) << sysReadDRAM << ',' << setw(10) << sysWriteDRAM << ',' << setw(10) << sysReadPMM << ',' << setw(10) << sysWritePMM << ','; }); } if (SPR_CXL) { choose(outputType, []() { cout << "System,"; }, []() { cout << "CXLRead,"; }, [&]() { cout << setw(10) << CXL_Read_BW << ','; }); } choose(outputType, []() { cout << "System,System,System\n"; }, []() { cout << "Read,Write,Memory\n"; }, [&]() { cout << setw(10) << sysReadDRAM + sysReadPMM << ',' << setw(10) << sysWriteDRAM + sysWritePMM << ',' << setw(10) << sysReadDRAM + sysReadPMM + sysWriteDRAM + sysWritePMM << "\n"; }); } void calculate_bandwidth(PCM *m, const std::vector& uncState1, const std::vector& uncState2, const uint64 elapsedTime, const bool csv, bool & csvheader, uint32 no_columns, const ServerUncoreMemoryMetrics & metrics, const bool show_channel_output, const bool print_update, const uint64 SPR_CHA_CXL_Count) { //const uint32 num_imc_channels = m->getMCChannelsPerSocket(); //const uint32 num_edc_channels = m->getEDCChannelsPerSocket(); memdata_t md; md.metrics = metrics; const auto cpu_family_model = m->getCPUFamilyModel(); md.M2M_NM_read_hit_rate_supported = (cpu_family_model == PCM::SKX); md.NM_hit_rate_supported = (cpu_family_model == PCM::ICX); md.BHS_NM = m->nearMemoryMetricsAvailable(); md.BHS = md.BHS_NM; static bool mm_once = true; if (metrics == Pmem && md.M2M_NM_read_hit_rate_supported == false && md.NM_hit_rate_supported == true && mm_once) { cerr << "INFO: Use -mm option to monitor NM Memory Mode metrics\n"; mm_once = false; } static bool mm_once1 = true; if (metrics == PmemMemoryMode && md.M2M_NM_read_hit_rate_supported == true && md.NM_hit_rate_supported == false && mm_once1) { cerr << "INFO: Use -pmem option to monitor NM Memory Mode metrics\n"; mm_once1 = false; } for(uint32 skt = 0; skt < max_sockets; ++skt) { md.iMC_Rd_socket[skt] = 0.0; md.iMC_Wr_socket[skt] = 0.0; md.iMC_PMM_Rd_socket[skt] = 0.0; md.iMC_PMM_Wr_socket[skt] = 0.0; md.MemoryMode_Miss_socket[skt] = 0.0; md.MemoryMode_Hit_socket[skt] = 0.0; md.NM_hit_rate[skt] = 0.0; md.EDC_Rd_socket[skt] = 0.0; md.EDC_Wr_socket[skt] = 0.0; md.partial_write[skt] = 0; for (uint32 i = 0; i < max_imc_controllers; ++i) { md.M2M_NM_read_hit_rate[skt][i] = 0.; } for (size_t p = 0; p < ServerUncoreCounterState::maxCXLPorts; ++p) { md.CXLMEM_Rd_socket_port[skt][p] = 0.0; md.CXLMEM_Wr_socket_port[skt][p] = 0.0; md.CXLCACHE_Rd_socket_port[skt][p] = 0.0; md.CXLCACHE_Wr_socket_port[skt][p] = 0.0; } } auto toBW = [&elapsedTime](const uint64 nEvents) { return (float)(nEvents * 64 / 1000000.0 / (elapsedTime / 1000.0)); }; auto toRate = [&elapsedTime](const uint64 nEvents) { return (float)(nEvents / (elapsedTime / 1000.0)); }; for(uint32 skt = 0; skt < m->getNumSockets(); ++skt) { const uint32 numChannels1 = (uint32)m->getMCChannels(skt, 0); // number of channels in the first controller if (m->HBMmemoryTrafficMetricsAvailable()) { const float scalingFactor = ((float)m->getHBMCASTransferSize()) / float(64.); for (uint32 channel = 0; channel < max_edc_channels; ++channel) { if (skipInactiveChannels && getEDCCounter(channel, ServerUncorePMUs::EventPosition::READ, uncState1[skt], uncState2[skt]) == 0.0 && getEDCCounter(channel, ServerUncorePMUs::EventPosition::WRITE, uncState1[skt], uncState2[skt]) == 0.0) { md.EDC_Rd_socket_chan[skt][channel] = -1.0; md.EDC_Wr_socket_chan[skt][channel] = -1.0; continue; } md.EDC_Rd_socket_chan[skt][channel] = scalingFactor * toBW(getEDCCounter(channel, ServerUncorePMUs::EventPosition::READ, uncState1[skt], uncState2[skt])); md.EDC_Wr_socket_chan[skt][channel] = scalingFactor * toBW(getEDCCounter(channel, ServerUncorePMUs::EventPosition::WRITE, uncState1[skt], uncState2[skt])); md.EDC_Rd_socket[skt] += md.EDC_Rd_socket_chan[skt][channel]; md.EDC_Wr_socket[skt] += md.EDC_Wr_socket_chan[skt][channel]; } } { for (uint32 channel = 0; channel < max_imc_channels; ++channel) { uint64 reads = 0, writes = 0, pmmReads = 0, pmmWrites = 0, memoryModeCleanMisses = 0, memoryModeDirtyMisses = 0; uint64 memoryModeHits = 0; reads = getMCCounter(channel, ServerUncorePMUs::EventPosition::READ, uncState1[skt], uncState2[skt]); writes = getMCCounter(channel, ServerUncorePMUs::EventPosition::WRITE, uncState1[skt], uncState2[skt]); switch (cpu_family_model) { case PCM::GNR: case PCM::GNR_D: case PCM::GRR: case PCM::SRF: reads += getMCCounter(channel, ServerUncorePMUs::EventPosition::READ2, uncState1[skt], uncState2[skt]); writes += getMCCounter(channel, ServerUncorePMUs::EventPosition::WRITE2, uncState1[skt], uncState2[skt]); break; } if (metrics == Pmem) { pmmReads = getMCCounter(channel, ServerUncorePMUs::EventPosition::PMM_READ, uncState1[skt], uncState2[skt]); pmmWrites = getMCCounter(channel, ServerUncorePMUs::EventPosition::PMM_WRITE, uncState1[skt], uncState2[skt]); } else if (metrics == PmemMixedMode || metrics == PmemMemoryMode) { memoryModeCleanMisses = getMCCounter(channel, ServerUncorePMUs::EventPosition::MM_MISS_CLEAN, uncState1[skt], uncState2[skt]); memoryModeDirtyMisses = getMCCounter(channel, ServerUncorePMUs::EventPosition::MM_MISS_DIRTY, uncState1[skt], uncState2[skt]); } if (metrics == PmemMemoryMode) { memoryModeHits = getMCCounter(channel, ServerUncorePMUs::EventPosition::NM_HIT, uncState1[skt], uncState2[skt]); } if (skipInactiveChannels && (reads + writes == 0)) { if ((metrics != Pmem) || (pmmReads + pmmWrites == 0)) { if ((metrics != PmemMixedMode) || (memoryModeCleanMisses + memoryModeDirtyMisses == 0)) { md.iMC_Rd_socket_chan[skt][channel] = -1.0; md.iMC_Wr_socket_chan[skt][channel] = -1.0; continue; } } } if (metrics != PmemMemoryMode) { md.iMC_Rd_socket_chan[skt][channel] = toBW(reads); md.iMC_Wr_socket_chan[skt][channel] = toBW(writes); md.iMC_Rd_socket[skt] += md.iMC_Rd_socket_chan[skt][channel]; md.iMC_Wr_socket[skt] += md.iMC_Wr_socket_chan[skt][channel]; } if (metrics == Pmem) { md.iMC_PMM_Rd_socket_chan[skt][channel] = toBW(pmmReads); md.iMC_PMM_Wr_socket_chan[skt][channel] = toBW(pmmWrites); md.iMC_PMM_Rd_socket[skt] += md.iMC_PMM_Rd_socket_chan[skt][channel]; md.iMC_PMM_Wr_socket[skt] += md.iMC_PMM_Wr_socket_chan[skt][channel]; md.M2M_NM_read_hit_rate[skt][(channel < numChannels1) ? 0 : 1] += (float)reads; } else if (metrics == PmemMixedMode) { md.MemoryMode_Miss_socket_chan[skt][channel] = toBW(memoryModeCleanMisses + 2 * memoryModeDirtyMisses); md.MemoryMode_Miss_socket[skt] += md.MemoryMode_Miss_socket_chan[skt][channel]; } else if (metrics == PmemMemoryMode) { md.MemoryMode_Miss_socket[skt] += toRate(memoryModeCleanMisses + memoryModeDirtyMisses); md.MemoryMode_Hit_socket[skt] += toRate(memoryModeHits); } else if ( cpu_family_model != PCM::GNR && cpu_family_model != PCM::GNR_D && cpu_family_model != PCM::SRF && cpu_family_model != PCM::GRR ) { md.partial_write[skt] += (uint64)(getMCCounter(channel, ServerUncorePMUs::EventPosition::PARTIAL, uncState1[skt], uncState2[skt]) / (elapsedTime / 1000.0)); } } } if (metrics == PmemMemoryMode) { const int64 imcReads = getFreeRunningCounter(ServerUncoreCounterState::ImcReads, uncState1[skt], uncState2[skt]); if (imcReads >= 0) { md.iMC_Rd_socket[skt] += toBW(imcReads); } const int64 imcWrites = getFreeRunningCounter(ServerUncoreCounterState::ImcWrites, uncState1[skt], uncState2[skt]); if (imcWrites >= 0) { md.iMC_Wr_socket[skt] += toBW(imcWrites); } } if (metrics == PmemMixedMode || metrics == PmemMemoryMode) { const int64 pmmReads = getFreeRunningCounter(ServerUncoreCounterState::PMMReads, uncState1[skt], uncState2[skt]); if (pmmReads >= 0) { md.iMC_PMM_Rd_socket[skt] += toBW(pmmReads); } else for (uint32 c = 0; c < max_imc_controllers; ++c) { md.iMC_PMM_Rd_socket[skt] += toBW(getM2MCounter(c, ServerUncorePMUs::EventPosition::PMM_READ, uncState1[skt], uncState2[skt])); } const int64 pmmWrites = getFreeRunningCounter(ServerUncoreCounterState::PMMWrites, uncState1[skt], uncState2[skt]); if (pmmWrites >= 0) { md.iMC_PMM_Wr_socket[skt] += toBW(pmmWrites); } else for (uint32 c = 0; c < max_imc_controllers; ++c) { md.iMC_PMM_Wr_socket[skt] += toBW(getM2MCounter(c, ServerUncorePMUs::EventPosition::PMM_WRITE, uncState1[skt], uncState2[skt]));; } } if (metrics == Pmem) { for (uint32 c = 0; c < max_imc_controllers; ++c) { if (md.M2M_NM_read_hit_rate[skt][c] != 0.0) { md.M2M_NM_read_hit_rate[skt][c] = ((float)getM2MCounter(c, ServerUncorePMUs::EventPosition::NM_HIT, uncState1[skt], uncState2[skt])) / md.M2M_NM_read_hit_rate[skt][c]; } } } if (md.BHS_NM) { for (uint32 c = 0; c < max_imc_controllers; ++c) { md.MemoryMode_Hit_socket[skt] += toRate(getM2MCounter(c, ServerUncorePMUs::EventPosition::NM_HIT, uncState1[skt], uncState2[skt])); md.MemoryMode_Miss_socket[skt] += toRate(getM2MCounter(c, ServerUncorePMUs::EventPosition::MM_MISS_CLEAN, uncState1[skt], uncState2[skt])); md.MemoryMode_Miss_socket[skt] += toRate(getM2MCounter(c, ServerUncorePMUs::EventPosition::MM_MISS_DIRTY, uncState1[skt], uncState2[skt])); } } const auto all = md.MemoryMode_Miss_socket[skt] + md.MemoryMode_Hit_socket[skt]; if ((metrics == PmemMemoryMode || md.BHS_NM == true) && all != 0.0) { md.NM_hit_rate[skt] = md.MemoryMode_Hit_socket[skt] / all; } for (size_t p = 0; p < m->getNumCXLPorts(skt); ++p) { if (md.BHS) { md.CXLMEM_Rd_socket_port[skt][p] = toBW(getCXLCMCounter((uint32)p, PCM::EventPosition::CXL_RxC_MEM, uncState1[skt], uncState2[skt])); md.CXLMEM_Wr_socket_port[skt][p] = toBW(getCXLDPCounter((uint32)p, PCM::EventPosition::CXL_TxC_MEM, uncState1[skt], uncState2[skt])); md.CXLCACHE_Rd_socket_port[skt][p] = toBW(getCXLCMCounter((uint32)p, PCM::EventPosition::CXL_RxC_CACHE, uncState1[skt], uncState2[skt])); md.CXLCACHE_Wr_socket_port[skt][p] = toBW(getCXLCMCounter((uint32)p, PCM::EventPosition::CXL_TxC_CACHE, uncState1[skt], uncState2[skt])); } else { md.CXLMEM_Wr_socket_port[skt][p] = CXLBWWrScalingFactor * toBW(getCXLCMCounter((uint32)p, PCM::EventPosition::CXL_TxC_MEM, uncState1[skt], uncState2[skt])); md.CXLCACHE_Wr_socket_port[skt][p] = CXLBWWrScalingFactor * toBW(getCXLCMCounter((uint32)p, PCM::EventPosition::CXL_TxC_CACHE, uncState1[skt], uncState2[skt])); } } } const auto CXL_Read_BW = toBW(SPR_CHA_CXL_Count); if (csv) { if (csvheader) { display_bandwidth_csv(m, &md, elapsedTime, show_channel_output, Header1, CXL_Read_BW); display_bandwidth_csv(m, &md, elapsedTime, show_channel_output, Header2, CXL_Read_BW); csvheader = false; } display_bandwidth_csv(m, &md, elapsedTime, show_channel_output, Data, CXL_Read_BW); } else { display_bandwidth(m, &md, no_columns, show_channel_output, print_update, CXL_Read_BW); } } void calculate_bandwidth_rank(PCM *m, const std::vector & uncState1, const std::vector& uncState2, const uint64 elapsedTime, const bool csv, bool &csvheader, const uint32 no_columns, const int rankA, const int rankB) { uint32 skt = 0; cout.setf(ios::fixed); cout.precision(2); uint32 numSockets = m->getNumSockets(); if (csv) { if (csvheader) { printSocketRankBWHeader_cvt(numSockets, max_imc_channels, rankA, rankB); csvheader = false; } printSocketChannelBW_cvt(numSockets, max_imc_channels, uncState1, uncState2, elapsedTime, rankA, rankB); } else { while(skt < numSockets) { auto printRow = [&skt, &uncState1, &uncState2, &elapsedTime, &rankA, &rankB](const uint32 no_columns) { printSocketRankBWHeader(no_columns, skt); printSocketChannelBW(no_columns, skt, max_imc_channels, uncState1, uncState2, elapsedTime, rankA, rankB); for (uint32 i = skt; i < (no_columns + skt); ++i) cout << "|-------------------------------------------|"; cout << "\n"; skt += no_columns; }; // Full row if ((skt + no_columns) <= numSockets) printRow(no_columns); else //Display the remaining sockets in this row printRow(numSockets - skt); } } } void readState(std::vector& state) { auto* pcm = PCM::getInstance(); assert(pcm); for (uint32 i = 0; i < pcm->getNumSockets(); ++i) state[i] = pcm->getServerUncoreCounterState(i); }; class CHAEventCollector { std::vector eventGroups; double delay; const char* sysCmd; const MainLoop& mainLoop; PCM* pcm; std::vector > MidStates; size_t curGroup = 0ULL; uint64 totalCount = 0ULL; CHAEventCollector() = delete; CHAEventCollector(const CHAEventCollector&) = delete; CHAEventCollector & operator = (const CHAEventCollector &) = delete; uint64 extractCHATotalCount(const std::vector& before, const std::vector& after) { uint64 result = 0; for (uint32 i = 0; i < pcm->getNumSockets(); ++i) { for (uint32 cbo = 0; cbo < pcm->getMaxNumOfUncorePMUs(PCM::CBO_PMU_ID); ++cbo) { for (uint32 ctr = 0; ctr < 4 && ctr < eventGroups[curGroup].size(); ++ctr) { result += getUncoreCounter(PCM::CBO_PMU_ID, cbo, ctr, before[i], after[i]); } } } return result; } void programGroup(const size_t group) { uint64 events[4] = { 0, 0, 0, 0 }; assert(group < eventGroups.size()); for (size_t i = 0; i < 4 && i < eventGroups[group].size(); ++i) { events[i] = eventGroups[group][i]; } pcm->programCboRaw(events, 0, 0); } public: CHAEventCollector(const double delay_, const char* sysCmd_, const MainLoop& mainLoop_, PCM* m) : sysCmd(sysCmd_), mainLoop(mainLoop_), pcm(m) { assert(pcm); switch (pcm->getCPUFamilyModel()) { case PCM::SPR: eventGroups = { { UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10C80B82) , // UNC_CHA_TOR_INSERTS.IA_MISS_CRDMORPH_CXL_ACC UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10c80782) , // UNC_CHA_TOR_INSERTS.IA_MISS_RFO_CXL_ACC UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10c81782) , // UNC_CHA_TOR_INSERTS.IA_MISS_DRD_CXL_ACC UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10C88782) // UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFRFO_CXL_ACC }, { UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10CCC782) , // UNC_CHA_TOR_INSERTS.IA_MISS_RFO_PREF_CXL_ACC UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10C89782) , // UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_CXL_ACC UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10CCD782) , // UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA_CXL_ACC UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x10CCCF82) // UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFCODE_CXL_ACC } }; break; case PCM::EMR: eventGroups = { { UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x20C80682) , // UNC_CHA_TOR_INSERTS.IA_MISS_RFO_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x20C81682) , // UNC_CHA_TOR_INSERTS.IA_MISS_DRD_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x20C88682) // UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFRFO_CXL_EXP_LOCAL }, { UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x20CCC682) , // UNC_CHA_TOR_INSERTS.IA_MISS_RFO_PREF_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x20C89682) , // UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x01) + UNC_PMON_CTL_UMASK_EXT(0x20CCD682) , // UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x40) + UNC_PMON_CTL_UMASK_EXT(0x20E87E82) , // UNC_CHA_TOR_INSERTS.RRQ_MISS_INVXTOM_CXL_EXP_LOCAL }, { UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x40) + UNC_PMON_CTL_UMASK_EXT(0x20E80682) , // UNC_CHA_TOR_INSERTS.RRQ_MISS_RDCUR_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x40) + UNC_PMON_CTL_UMASK_EXT(0x20E80E82) , // UNC_CHA_TOR_INSERTS.RRQ_MISS_RDCODE_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x40) + UNC_PMON_CTL_UMASK_EXT(0x20E81682) , // UNC_CHA_TOR_INSERTS.RRQ_MISS_RDDATA_CXL_EXP_LOCAL UNC_PMON_CTL_EVENT(0x35) + UNC_PMON_CTL_UMASK(0x40) + UNC_PMON_CTL_UMASK_EXT(0x20E82682) , // UNC_CHA_TOR_INSERTS.RRQ_MISS_RDINVOWN_OPT_CXL_EXP_LOCAL } }; break; } assert(eventGroups.size() > 1); delay = delay_ / double(eventGroups.size()); MidStates.resize(eventGroups.size() - 1); for (auto& e : MidStates) { e.resize(pcm->getNumSockets()); } } void programFirstGroup() { programGroup(0); } void multiplexEvents(const std::vector& BeforeState) { for (curGroup = 0; curGroup < eventGroups.size() - 1; ++curGroup) { assert(curGroup < MidStates.size()); calibratedSleep(delay, sysCmd, mainLoop, pcm); readState(MidStates[curGroup]); // TODO: read only CHA counters (performance optmization) totalCount += extractCHATotalCount((curGroup > 0) ? MidStates[curGroup - 1] : BeforeState, MidStates[curGroup]); programGroup(curGroup + 1); readState(MidStates[curGroup]); // TODO: read only CHA counters (performance optmization) } calibratedSleep(delay, sysCmd, mainLoop, pcm); } uint64 getTotalCount(const std::vector& AfterState) { return eventGroups.size() * (totalCount + extractCHATotalCount(MidStates.back(), AfterState)); } void reset() { totalCount = 0; } }; #ifndef UNIT_TEST PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; cout.rdbuf(&nullStream1); cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor: Memory Bandwidth Monitoring Utility " << PCM_VERSION << "\n"; cerr << "\n"; cerr << " This utility measures memory bandwidth per channel or per DIMM rank in real-time\n"; cerr << "\n"; double delay = -1.0; bool csv = false, csvheader = false, show_channel_output = true, print_update = false; uint32 no_columns = DEFAULT_DISPLAY_COLUMNS; // Default number of columns is 2 char * sysCmd = NULL; char ** sysArgv = NULL; int rankA = -1, rankB = -1; MainLoop mainLoop; string program = string(argv[0]); PCM * m = PCM::getInstance(); assert(m); if (m->getNumSockets() > max_sockets) { cerr << "Only systems with up to " << max_sockets << " sockets are supported! Program aborted\n"; exit(EXIT_FAILURE); } ServerUncoreMemoryMetrics metrics; metrics = m->PMMTrafficMetricsAvailable() ? Pmem : PartialWrites; if (argc > 1) do { argv++; argc--; string arg_value; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_help(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = csvheader = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; csvheader = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (extract_argument_value(*argv, {"-columns", "/columns"}, arg_value)) { if(arg_value.empty()) { continue; } no_columns = stoi(arg_value); if (no_columns == 0) no_columns = DEFAULT_DISPLAY_COLUMNS; if (no_columns > m->getNumSockets()) no_columns = m->getNumSockets(); continue; } else if (extract_argument_value(*argv, {"-rank", "/rank"}, arg_value)) { if(arg_value.empty()) { continue; } int rank = stoi(arg_value); if (rankA >= 0 && rankB >= 0) { cerr << "At most two DIMM ranks can be monitored \n"; exit(EXIT_FAILURE); } else { if(rank > 7) { cerr << "Invalid rank number " << rank << "\n"; exit(EXIT_FAILURE); } if(rankA < 0) rankA = rank; else if(rankB < 0) rankB = rank; metrics = PartialWrites; } continue; } else if (check_argument_equals(*argv, {"--nochannel", "/nc", "-nc"})) { show_channel_output = false; continue; } else if (check_argument_equals(*argv, {"-pmm", "/pmm", "-pmem", "/pmem"})) { metrics = Pmem; continue; } else if (check_argument_equals(*argv, {"-all", "/all"})) { skipInactiveChannels = false; continue; } else if (check_argument_equals(*argv, {"-mixed", "/mixed"})) { metrics = PmemMixedMode; continue; } else if (check_argument_equals(*argv, {"-mm", "/mm"})) { metrics = PmemMemoryMode; show_channel_output = false; continue; } else if (check_argument_equals(*argv, {"-partial", "/partial"})) { metrics = PartialWrites; continue; } else if (check_argument_equals(*argv, {"-u", "/u"})) { print_update = true; continue; } PCM_ENFORCE_FLUSH_OPTION #ifdef _MSC_VER else if (check_argument_equals(*argv, {"--uninstallDriver"})) { Driver tmpDrvObject; tmpDrvObject.uninstall(); cerr << "msr.sys driver has been uninstalled. You might need to reboot the system to make this effective.\n"; exit(EXIT_SUCCESS); } else if (check_argument_equals(*argv, {"--installDriver"})) { Driver tmpDrvObject = Driver(Driver::msrLocalPath()); if (!tmpDrvObject.start()) { tcerr << "Can not access CPU counters\n"; tcerr << "You must have a signed driver at " << tmpDrvObject.driverPath() << " and have administrator rights to run this program\n"; exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } #endif else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_help); continue; } } while (argc > 1); // end of command line parsing loop m->disableJKTWorkaround(); print_cpu_details(); const auto cpu_family_model = m->getCPUFamilyModel(); if (!m->hasPCICFGUncore()) { cerr << "Unsupported processor model (0x" << std::hex << cpu_family_model << std::dec << ").\n"; if (m->memoryTrafficMetricsAvailable()) cerr << "For processor-level memory bandwidth statistics please use 'pcm' utility\n"; exit(EXIT_FAILURE); } if (anyPmem(metrics) && (m->PMMTrafficMetricsAvailable() == false)) { cerr << "PMM/Pmem traffic metrics are not available on your processor.\n"; exit(EXIT_FAILURE); } if (metrics == PmemMemoryMode && m->PMMMemoryModeMetricsAvailable() == false) { cerr << "PMM Memory Mode metrics are not available on your processor.\n"; exit(EXIT_FAILURE); } if (metrics == PmemMixedMode && m->PMMMixedModeMetricsAvailable() == false) { cerr << "PMM Mixed Mode metrics are not available on your processor.\n"; exit(EXIT_FAILURE); } if((rankA >= 0 || rankB >= 0) && anyPmem(metrics)) { cerr << "PMM/Pmem traffic metrics are not available on rank level\n"; exit(EXIT_FAILURE); } if((rankA >= 0 || rankB >= 0) && !show_channel_output) { cerr << "Rank level output requires channel output\n"; exit(EXIT_FAILURE); } PCM::ErrorCode status = m->programServerUncoreMemoryMetrics(metrics, rankA, rankB); m->checkError(status); max_imc_channels = (pcm::uint32)m->getMCChannelsPerSocket(); std::vector BeforeState(m->getNumSockets()); std::vector AfterState(m->getNumSockets()); uint64 BeforeTime = 0, AfterTime = 0; if ( (sysCmd != NULL) && (delay<=0.0) ) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (csv) { if( delay<=0.0 ) delay = PCM_DELAY_DEFAULT; } else { // for non-CSV mode delay < 1.0 does not make a lot of practical sense: // hard to read from the screen, or // in case delay is not provided in command line => set default if( ((delay<1.0) && (delay>0.0)) || (delay<=0.0) ) delay = PCM_DELAY_DEFAULT; } shared_ptr chaEventCollector; SPR_CXL = (PCM::SPR == cpu_family_model || PCM::EMR == cpu_family_model) && (getNumCXLPorts(m) > 0); if (SPR_CXL) { chaEventCollector = std::make_shared(delay, sysCmd, mainLoop, m); assert(chaEventCollector.get()); chaEventCollector->programFirstGroup(); } cerr << "Update every " << delay << " seconds\n"; if (csv) cerr << "Read/Write values expressed in (MB/s)" << endl; readState(BeforeState); uint64 SPR_CHA_CXL_Event_Count = 0; BeforeTime = m->getTickCount(); if( sysCmd != NULL ) { MySystem(sysCmd, sysArgv); } mainLoop([&]() { if (enforceFlush || !csv) cout << flush; if (chaEventCollector.get()) { chaEventCollector->multiplexEvents(BeforeState); } else { calibratedSleep(delay, sysCmd, mainLoop, m); } AfterTime = m->getTickCount(); readState(AfterState); if (chaEventCollector.get()) { SPR_CHA_CXL_Event_Count = chaEventCollector->getTotalCount(AfterState); chaEventCollector->reset(); chaEventCollector->programFirstGroup(); readState(AfterState); // TODO: re-read only CHA counters (performance optmization) } if (!csv) { //cout << "Time elapsed: " << dec << fixed << AfterTime-BeforeTime << " ms\n"; //cout << "Called sleep function for " << dec << fixed << delay_ms << " ms\n"; } if(rankA >= 0 || rankB >= 0) calculate_bandwidth_rank(m,BeforeState, AfterState, AfterTime - BeforeTime, csv, csvheader, no_columns, rankA, rankB); else calculate_bandwidth(m,BeforeState,AfterState,AfterTime-BeforeTime,csv,csvheader, no_columns, metrics, show_channel_output, print_update, SPR_CHA_CXL_Event_Count); swap(BeforeTime, AfterTime); swap(BeforeState, AfterState); if ( m->isBlocked() ) { // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } #endif // UNIT_TESTpcm-202502/src/pcm-mmio.cpp000066400000000000000000000140431475730356400154160ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012-2022, Intel Corporation // written by Roman Dementiev #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif using namespace pcm; #define MAX_BATCH_OPERATE_BYTES 1024 #define MAX_BATCH_READ_ROW_DISPLAY_BYTES 16 void print_usage(const char* progname) { std::cout << "Usage " << progname << " [-w value] [-q] [-d] [-c core] address\n\n"; std::cout << " Reads/writes MMIO (memory mapped) register in the specified address\n"; std::cout << " -w value : write the value before reading \n"; std::cout << " -b low:high : read or write only low..high bits of the register\n"; std::cout << " -q : read/write 64-bit quad word (default is 32-bit double word)\n"; std::cout << " -d : output all numbers in dec (default is hex)\n"; std::cout << " -n size : number of bytes read from specified address(batch read mode), max bytes=" << MAX_BATCH_OPERATE_BYTES << "\n"; std::cout << " -c core : perform the operation from specified core\n"; std::cout << " --version : print application version\n"; std::cout << "\n"; } template void doOp( const std::pair & bits, const uint64 address, const uint64 offset, const uint32 batch_bytes, const bool write, T value, RD readOp, WR writeOp, const bool dec, const int core) { auto printCoreEndl = [&]() { if (core >= 0) { std::cout << " on core " << core; } std::cout << "\n\n"; }; if (batch_bytes == 0) //single mode { if (!dec) std::cout << std::hex << std::showbase; constexpr auto bit = sizeof(T) * 8; readOldValueHelper(bits, value, write, [&readOp, & offset](T & old_value){ old_value = readOp(offset); return true; }); if (write) { std::cout << " Writing " << value << " to " << std::dec << bit; if (!dec) std::cout << std::hex << std::showbase; std::cout <<"-bit MMIO register " << address << "\n"; writeOp(offset, value); } value = readOp(offset); extractBitsPrintHelper(bits, value, dec); std::cout << " from " << std::dec << bit; if (!dec) std::cout << std::hex << std::showbase; std::cout << "-bit MMIO register " << address; printCoreEndl(); } else //batch mode { uint32 i = 0, j= 0; std::cout << std::hex << " Dumping MMIO register range from 0x" << address << ", number of bytes=0x" << batch_bytes; printCoreEndl(); for(i = 0; i < batch_bytes; i+=MAX_BATCH_READ_ROW_DISPLAY_BYTES) { std::ostringstream row_disp_str(std::ostringstream::out); std::cout << " 0x" << (address + i) << ": "; for(j = 0; j < (MAX_BATCH_READ_ROW_DISPLAY_BYTES/sizeof(T)); j++) { value = readOp(offset + i + j*sizeof(T)); row_disp_str << "0x" << std::hex << std::setw(sizeof(T)*2) << std::setfill('0') << value << " "; } std::cout << row_disp_str.str() << ";\n"; } std::cout << "\n"; } } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; std::cout << "\n MMIO register read/write utility\n\n"; uint64 value = ~(0ULL); bool write = false; uint64 address = 0; bool dec = false; bool quad = false; uint32 batch_bytes = 0; std::pair bits{-1, -1}; int core = -1; int my_opt = -1; while ((my_opt = getopt(argc, argv, "w:dqn:b:c:")) != -1) { switch (my_opt) { case 'w': write = true; value = read_number(optarg); break; case 'd': dec = true; break; case 'q': quad = true; break; case 'b': bits = parseBitsParameter(optarg); break; case 'n': batch_bytes = read_number(optarg); if (batch_bytes > MAX_BATCH_OPERATE_BYTES) { batch_bytes = MAX_BATCH_OPERATE_BYTES; } break; case 'c': core = read_number(optarg); break; default: print_usage(argv[0]); return -1; } } if (optind >= argc) { print_usage(argv[0]); return -1; } address = read_number(argv[optind]); if (write == true) { batch_bytes = 0; //batch mode only support read. } try { constexpr uint64 rangeSize = 4096ULL; const uint64 baseAddr = address & (~(rangeSize - 1ULL)); // round down to 4K boundary const uint64 offset = address - baseAddr; if ((batch_bytes != 0) && (offset + batch_bytes > rangeSize)) { batch_bytes = (rangeSize - offset); //limit the boundary } MMIORange mmio(baseAddr, rangeSize, !write, false, core); using namespace std::placeholders; if (quad) { doOp(bits, address, offset, batch_bytes, write, (uint64)value, std::bind(&MMIORange::read64, &mmio, _1), std::bind(&MMIORange::write64, &mmio, _1, _2), dec, core); } else { doOp(bits, address, offset, batch_bytes, write, (uint32)value, std::bind(&MMIORange::read32, &mmio, _1), std::bind(&MMIORange::write32, &mmio, _1, _2), dec, core); } } catch (std::exception & e) { std::cerr << "Error accessing MMIO registers: " << e.what() << "\n"; std::cerr << "Please check if the program can access MMIO drivers.\n"; } return 0; } pcm-202502/src/pcm-msr.cpp000066400000000000000000000166151475730356400152650ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012-2022, Intel Corporation // written by Roman Dementiev #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include #include #include using namespace pcm; void print_usage(const char * progname) { std::cout << "Usage " << progname << " [-w value] [-c core] [-a] [-d] msr\n\n"; std::cout << " Reads/writes specified msr (model specific register) \n"; std::cout << " -w value : write the value before reading \n"; std::cout << " -c corelist : perform msr read/write on specified cores (default is 0)\n"; std::cout << " (examples: -c 10 -c 10-11 -c 4,6,12-20,6)\n"; std::cout << " -x : print core number in hex (instead of decimal)\n"; std::cout << " -b low:high : read or write only low..high bits of the register\n"; std::cout << " -d : output all numbers in dec (default is hex)\n"; std::cout << " -a : perform msr read/write operations on all cores (same as -c -1)\n"; std::cout << " -s : iterate with seconds between each iteration\n"; std::cout << " -o : write results of each iteration to file \n"; std::cout << " --version : print application version\n"; std::cout << "\n"; } PCM_MAIN_NOTHROW; bool outflag = false; FILE *ofile; int loop_cnt = 0; std::list corelist; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) return 0; std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; std::cout << "\n MSR read/write utility\n\n"; uint64 value = 0; bool write = false; bool core_in_dec = true; int msr = -1; bool dec = false; std::pair bits{-1, -1}; float sleep_delay = -1; std::string outfile; int my_opt = -1; while ((my_opt = getopt(argc, argv, "xw:c:dab:s:o:")) != -1) { switch (my_opt) { case 'w': write = true; value = read_number(optarg); break; case 'x': core_in_dec = false; break; case 's': sleep_delay = atof(optarg); break; case 'o': outfile = optarg; break; case 'c': corelist = extract_integer_list(optarg); break; case 'd': dec = true; break; case 'a': corelist.clear(); corelist.push_back(-1); break; case 'b': bits = parseBitsParameter(optarg); break; default: print_usage(argv[0]); return -1; } } if (corelist.size()==0) corelist.push_back(0); if (1==2){ for (auto const &v : corelist){ printf("coreid=%d\n",v); } } if (optind >= argc) { print_usage(argv[0]); return -1; } msr = (int)read_number(argv[optind]); #ifdef _MSC_VER // Increase the priority a bit to improve context switching delays on Windows SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); // WARNING: This driver code (msr.sys) is only for testing purposes, not for production use Driver drv = Driver(Driver::msrLocalPath()); // drv.stop(); // restart driver (usually not needed) if (!drv.start()) { tcerr << "Can not load MSR driver.\n"; tcerr << "You must have a signed driver at " << drv.driverPath() << " and have administrator rights to run this program\n"; return -1; } #endif if (outfile.length() > 0){ outflag = true; ofile = fopen(outfile.c_str(),"w"); if (ofile==NULL){ printf("ERROR: can not open '%s' (skipping write)\n",outfile.c_str()); printf(" (maybe a sudo issue .. need o+rwx on directory)\n"); outflag = false; } } while(1){ for (std::list::iterator it=corelist.begin(); it != corelist.end(); ++it){ int core = *it; // lambda funtion [) auto doOne = [&dec, &write, &msr, &bits, &it, &core_in_dec](int core, uint64 value) { try { MsrHandle h(core); if (!dec) std::cout << std::hex << std::showbase; if (!readOldValueHelper(bits, value, write, [&h, &msr](uint64 & old_value){ return h.read(msr, &old_value) == 8; })) { std::cout << " Read error!\n"; return; } if (write) { std::cout << " Writing " << value << " to MSR " << msr << " on core " << core << "\n"; if (h.write(msr, value) != 8) { std::cout << " Write error!\n"; } } value = 0; if (h.read(msr, &value) == 8) { uint64 value2 = value; extractBitsPrintHelper(bits, value, dec); char cname[100]; if (core_in_dec) std::snprintf(cname, sizeof(cname), "%d", core); else std::snprintf(cname, sizeof(cname), "0x%x", core); std::cout << " from MSR " << msr << " on core " << cname << "\n"; auto itx = it; itx++; if (itx == corelist.end()) std::cout << "\n"; if (outflag){ if (bits.first >= 0){ uint32 value3 = extract_bits(value2,bits.first,bits.second); if (dec)fprintf(ofile,"%d,%u\n",loop_cnt,value3); else fprintf(ofile,"%d,0x%x\n",loop_cnt,value3); }else{ if (dec)fprintf(ofile,"%d,%llu\n",loop_cnt,value2); else fprintf(ofile,"%d,0x%llx\n",loop_cnt,value2); } fflush(ofile); } } else { std::cout << " Read error!\n"; } } catch (std::exception & e) { std::cerr << "Error accessing MSRs: " << e.what() << "\n"; std::cerr << "Please check if the program can access MSR drivers.\n"; } }; // end of lambda definition if (core >= 0) { doOne(core, value); } else { set_signal_handlers(); auto m = PCM::getInstance(); for (uint32 i = 0; i < m->getNumCores(); ++i) { if (m->isCoreOnline(i)) { doOne(i, value); } } } } if (sleep_delay == -1) break; loop_cnt++; MySleepMs(sleep_delay*1000.0); } return 0; } pcm-202502/src/pcm-numa.cpp000066400000000000000000000237431475730356400154240ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev /*! \file pcm-numa.cpp \brief Example of using CPU counters: implements a performance counter monitoring utility for NUMA (remote and local memory accesses counting). Example for programming offcore response events */ #include #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #include #include // for gettimeofday() #endif #include #include #include #include #include #include #include #include #include "cpucounters.h" #include "utils.h" #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs using namespace std; using namespace pcm; void print_usage(const string & progname) { cout << "\n Usage: \n " << progname << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " -c=corelist => check specified cores (default all cores)\n"; cout << " (examples: -c=10 -c=10-11 -c=4,6,12-20,6)\n"; cout << " --version => print application version\n"; cout << " -pid PID | /pid PID => collect core metrics only for specified process ID\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " Examples:\n"; cout << " " << progname << " 1 => print counters every second without core and socket output\n"; cout << " " << progname << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << progname << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } template void print_stats(const StateType & BeforeState, const StateType & AfterState, bool csv) { uint64 cycles = getCycles(BeforeState, AfterState); uint64 instr = getInstructionsRetired(BeforeState, AfterState); if (csv) { cout << double(instr) / double(cycles) << ","; cout << instr << ","; cout << cycles << ","; } else { cout << double(instr) / double(cycles) << " "; cout << unit_format(instr) << " "; cout << unit_format(cycles) << " "; } for (int i = 0; i < 2; ++i) if (!csv) cout << unit_format(getNumberOfCustomEvents(i, BeforeState, AfterState)) << " "; else cout << getNumberOfCustomEvents(i, BeforeState, AfterState) << ","; cout << "\n"; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; cout.rdbuf(&nullStream1); cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor: NUMA monitoring utility \n"; cerr << "\n"; double delay = -1.0; int pid{ -1 }; char * sysCmd = NULL; char ** sysArgv = NULL; bool csv = false; MainLoop mainLoop; string program = string(argv[0]); PCM * m = PCM::getInstance(); parsePID(argc, argv, pid); std::list corelist; if (argc > 1) do { argv++; argc--; string arg_value; if (*argv == nullptr) { continue; } else if (extract_argument_value(*argv, {"-c"}, arg_value)) { const char *pstr = arg_value.c_str(); corelist = extract_integer_list(pstr); } else if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; if (!arg_value.empty()) { m->setOutput(arg_value); } } else if (isPIDOption(argv)) { argv++; argc--; continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } while (argc > 1); // end of command line partsing loop EventSelectRegister def_event_select_reg; def_event_select_reg.value = 0; def_event_select_reg.fields.usr = 1; def_event_select_reg.fields.os = 1; def_event_select_reg.fields.enable = 1; PCM::ExtendedCustomCoreEventDescription conf; conf.fixedCfg = NULL; // default conf.nGPCounters = 2; try { m->setupCustomCoreEventsForNuma(conf); } catch (UnsupportedProcessorException& ) { cerr << "pcm-numa tool does not support your processor currently.\n"; exit(EXIT_FAILURE); } EventSelectRegister regs[4]; conf.gpCounterCfg = regs; for (int i = 0; i < 4; ++i) regs[i] = def_event_select_reg; regs[0].fields.event_select = m->getOCREventNr(0, 0).first; // OFFCORE_RESPONSE 0 event regs[0].fields.umask = m->getOCREventNr(0, 0).second; regs[1].fields.event_select = m->getOCREventNr(1, 0).first; // OFFCORE_RESPONSE 1 event regs[1].fields.umask = m->getOCREventNr(1, 0).second; print_pid_collection_message(pid); PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf, false, pid); m->checkError(status); print_cpu_details(); uint64 BeforeTime = 0, AfterTime = 0; SystemCounterState SysBeforeState, SysAfterState; const uint32 ncores = m->getNumCores(); if (corelist.size()==0){ for (int ii = 0; ii < (int)ncores; ++ii) corelist.push_back(ii); } vector BeforeState, AfterState; vector DummySocketStates; if ((sysCmd != NULL) && (delay <= 0.0)) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (csv) { if (delay <= 0.0) delay = PCM_DELAY_DEFAULT; } else { // for non-CSV mode delay < 1.0 does not make a lot of practical sense: // hard to read from the screen, or // in case delay is not provided in command line => set default if (((delay < 1.0) && (delay > 0.0)) || (delay <= 0.0)) delay = PCM_DELAY_DEFAULT; } cerr << "Update every " << delay << " seconds\n"; cout.precision(2); cout << fixed; BeforeTime = m->getTickCount(); m->getAllCounterStates(SysBeforeState, DummySocketStates, BeforeState); if (sysCmd != NULL) { MySystem(sysCmd, sysArgv); } mainLoop([&]() { if (!csv) cout << flush; calibratedSleep(delay, sysCmd, mainLoop, m); AfterTime = m->getTickCount(); m->getAllCounterStates(SysAfterState, DummySocketStates, AfterState); cout << "Time elapsed: " << dec << fixed << AfterTime - BeforeTime << " ms\n"; //cout << "Called sleep function for " << dec << fixed << delay_ms << " ms\n"; if (csv) cout << "Core,IPC,Instructions,Cycles,Local DRAM accesses,Remote DRAM accesses \n"; else cout << "Core | IPC | Instructions | Cycles | Local DRAM accesses | Remote DRAM Accesses \n"; for (int ix : corelist) { uint32 i = ix; if (csv) cout << i << ","; else cout << " " << setw(3) << i << " " << setw(2); print_stats(BeforeState[i], AfterState[i], csv); } if (csv) cout << "*,"; else { cout << "-------------------------------------------------------------------------------------------------------------------\n"; cout << " * "; } print_stats(SysBeforeState, SysAfterState, csv); cout << "\n"; swap(BeforeTime, AfterTime); swap(BeforeState, AfterState); swap(SysBeforeState, SysAfterState); if (m->isBlocked()) { // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-pcicfg.cpp000066400000000000000000000115301475730356400157060ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2012, 2018-2022 Intel Corporation // written by Roman Dementiev #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif using namespace pcm; void print_usage(const char * progname) { std::cout << "Usage " << progname << " [-w value] [-d] [-i ID] [group bus device function] offset\n\n"; std::cout << " Reads/writes 32-bit PCICFG register \n"; std::cout << " -w value : write the value before reading \n"; std::cout << " -b low:high : read or write only low..high bits of the register\n"; std::cout << " -d : output all numbers in dec (default is hex)\n"; std::cout << " -i ID : specify Intel device ID instead of group bus device function\n"; std::cout << " --version : print application version\n"; std::cout << "\n"; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) return 0; std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; std::cout << "\n PCICFG read/write utility\n\n"; #ifdef __linux__ #ifndef PCM_USE_PCI_MM_LINUX std::cout << "\n To access *extended* configuration space recompile with -DPCM_USE_PCI_MM_LINUX option.\n"; #endif #endif uint32 value = 0; bool write = false; bool dec = false; uint32 deviceID = 0; std::pair bits{-1, -1}; int my_opt = -1; while ((my_opt = getopt(argc, argv, "i:w:db:")) != -1) { switch (my_opt) { case 'i': deviceID = (uint32)read_number(optarg); break; case 'w': write = true; value = (pcm::uint32)read_number(optarg); break; case 'b': bits = parseBitsParameter(optarg); break; case 'd': dec = true; break; default: print_usage(argv[0]); return -1; } } if (optind + ((deviceID)?0:4) >= argc) { print_usage(argv[0]); return -1; } int group = -1; int bus = -1; int device = -1; int function = -1; int offset = -1; #ifdef _MSC_VER // Increase the priority a bit to improve context switching delays on Windows SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); // WARNING: This driver code (msr.sys) is only for testing purposes, not for production use Driver drv = Driver(Driver::msrLocalPath()); // drv.stop(); // restart driver (usually not needed) if (!drv.start()) { tcerr << "Can not load MSR driver.\n"; tcerr << "You must have a signed driver at " << drv.driverPath() << " and have administrator rights to run this program\n"; return -1; } #endif auto one = [&dec,&write,&bits](const uint32 & group, const uint32 & bus, const uint32 & device, const uint32 & function, const uint32 & offset, uint32 value) { try { PciHandleType h(group, bus, device, function); if (!dec) std::cout << std::hex << std::showbase; readOldValueHelper(bits, value, write, [&h, &offset](uint32 & old_value){ h.read32(offset, &old_value); return true; }); if (write) { std::cout << " Writing " << value << " to " << group << ":" << bus << ":" << device << ":" << function << "@" << offset << "\n"; h.write32(offset, value); } value = 0; h.read32(offset, &value); extractBitsPrintHelper(bits, value, dec); std::cout << " from " << group << ":" << bus << ":" << device << ":" << function << "@" << offset << "\n\n"; } catch (std::exception& e) { std::cerr << "Error accessing registers: " << e.what() << "\n"; std::cerr << "Please check if the program can access MSR/PCICFG drivers.\n"; } }; if (deviceID) { offset = (int)read_number(argv[optind]); forAllIntelDevices([&deviceID,&one,&offset, &value](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 device_id) { if (deviceID == device_id) { one(group, bus, device, function, offset, value); } }); } else { group = (int)read_number(argv[optind]); bus = (int)read_number(argv[optind + 1]); device = (int)read_number(argv[optind + 2]); function = (int)read_number(argv[optind + 3]); offset = (int)read_number(argv[optind + 4]); one(group, bus, device, function, offset, value); } return 0; } pcm-202502/src/pcm-pcie.cpp000066400000000000000000000241501475730356400153750ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // originally written by Patrick Lu // redesigned by Roman Sudarikov /*! \file pcm-pcie.cpp \brief Example of using uncore CBo counters: implements a performance counter monitoring utility for monitoring PCIe bandwidth */ #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #include #endif #include #include #include #include #include #include #include #include "pcm-pcie.h" #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs using namespace std; bool events_printed = false; void print_events() { if(events_printed) { return; } cout << " PCIe event definitions (each event counts as a transfer): \n"; cout << " PCIe read events (PCI devices reading from memory - application writes to disk/network/PCIe device):\n"; cout << " PCIePRd - PCIe UC read transfer (partial cache line)\n"; cout << " PCIeRdCur* - PCIe read current transfer (full cache line)\n"; cout << " On Haswell Server PCIeRdCur counts both full/partial cache lines\n"; cout << " RFO* - Demand Data RFO\n"; cout << " CRd* - Demand Code Read\n"; cout << " DRd - Demand Data Read\n"; cout << " PCIeNSWr - PCIe Non-snoop write transfer (partial cache line)\n"; cout << " PCIe write events (PCI devices writing to memory - application reads from disk/network/PCIe device):\n"; cout << " PCIeWiLF - PCIe Write transfer (non-allocating) (full cache line)\n"; cout << " PCIeItoM - PCIe Write transfer (allocating) (full cache line)\n"; cout << " PCIeNSWr - PCIe Non-snoop write transfer (partial cache line)\n"; cout << " PCIeNSWrF - PCIe Non-snoop write transfer (full cache line)\n"; cout << " ItoM - PCIe write full cache line\n"; cout << " RFO - PCIe partial Write\n"; cout << " CPU MMIO events (CPU reading/writing to PCIe devices):\n"; cout << " UCRdF - read from uncacheable memory, including MMIO\n"; cout << " WCiL - streaming store (partial cache line), includes MOVDIRI\n\n"; cout << " WCiLF - streaming store (full cache line), includes MOVDIR64\n\n"; cout << " PRd - MMIO Read [Haswell Server only] (Partial Cache Line)\n"; cout << " WiL - MMIO Write (Full/Partial)\n\n"; cout << " * - NOTE: Depending on the configuration of your BIOS, this tool may report '0' if the message\n"; cout << " has not been selected.\n\n"; events_printed = true; } void print_usage(const string & progname) { cout << "\n Usage: \n " << progname << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -B => Estimate PCIe B/W (in Bytes/sec) by multiplying\n"; cout << " the number of transfers by the cache line size (=64 bytes).\n"; cout << " -e => print additional PCIe LLC miss/hit statistics.\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " It overestimates the bandwidth under traffic with many partial cache line transfers.\n"; cout << "\n"; print_events(); cout << "\n"; cout << " Examples:\n"; cout << " " << progname << " 1 => print counters every second without core and socket output\n"; cout << " " << progname << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << progname << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } IPlatform *IPlatform::getPlatform(PCM *m, bool csv, bool print_bandwidth, bool print_additional_info, uint32 delay) { switch (m->getCPUFamilyModel()) { case PCM::GNR: case PCM::GNR_D: case PCM::SRF: return new BirchStreamPlatform(m, csv, print_bandwidth, print_additional_info, delay); case PCM::GRR: return new LoganvillePlatform(m, csv, print_bandwidth, print_additional_info, delay); case PCM::SPR: case PCM::EMR: return new EagleStreamPlatform(m, csv, print_bandwidth, print_additional_info, delay); case PCM::ICX: case PCM::SNOWRIDGE: return new WhitleyPlatform(m, csv, print_bandwidth, print_additional_info, delay); case PCM::SKX: return new PurleyPlatform(m, csv, print_bandwidth, print_additional_info, delay); case PCM::BDX_DE: case PCM::BDX: case PCM::KNL: case PCM::HASWELLX: return new GrantleyPlatform(m, csv, print_bandwidth, print_additional_info, delay); case PCM::IVYTOWN: case PCM::JAKETOWN: return new BromolowPlatform(m, csv, print_bandwidth, print_additional_info, delay); default: return NULL; } } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; cout.rdbuf(&nullStream1); cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor: PCIe Bandwidth Monitoring Utility \n"; cerr << " This utility measures PCIe bandwidth in real-time\n"; cerr << "\n"; print_events(); double delay = -1.0; bool csv = false; bool print_bandwidth = false; bool print_additional_info = false; char * sysCmd = NULL; char ** sysArgv = NULL; MainLoop mainLoop; string program = string(argv[0]); PCM * m = PCM::getInstance(); if (argc > 1) do { argv++; argc--; string arg_value; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"-B", "/b"})) { print_bandwidth = true; continue; } else if (check_argument_equals(*argv, {"-e"})) { print_additional_info = true; continue; } else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } while(argc > 1); // end of command line partsing loop if ( (sysCmd != NULL) && (delay<=0.0) ) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (csv) { if ( delay<=0.0 ) delay = PCM_DELAY_DEFAULT; } else { // for non-CSV mode delay < 1.0 does not make a lot of practical sense: // hard to read from the screen, or // in case delay is not provided in command line => set default if ( ((delay < 1.0) && (delay > 0.0)) || (delay <= 0.0) ) { cerr << "For non-CSV mode delay < 1.0s does not make a lot of practical sense. Default delay 1s is used. Consider to use CSV mode for lower delay values\n"; delay = PCM_DELAY_DEFAULT; } } cerr << "Update every " << delay << " seconds\n"; // Delay in milliseconds unique_ptr platform(IPlatform::getPlatform(m, csv, print_bandwidth, print_additional_info, (uint)(delay * 1000))); if (!platform) { print_cpu_details(); cerr << "Jaketown, Ivytown, Haswell, Broadwell-DE, Skylake, Icelake, Snowridge and Sapphirerapids Server CPU is required for this tool! Program aborted\n"; exit(EXIT_FAILURE); } if ( sysCmd != NULL ) { MySystem(sysCmd, sysArgv); } // ================================== Begin Printing Output ================================== mainLoop([&]() { if (!csv) cout << flush; for(uint i=0; i < NUM_SAMPLES; i++) platform->getEvents(); platform->printHeader(); platform->printEvents(); platform->printAggregatedEvents(); platform->cleanup(); if (m->isBlocked()) return false; return true; }); // ================================== End Printing Output ================================== exit(EXIT_SUCCESS); } pcm-202502/src/pcm-pcie.h000066400000000000000000001062421475730356400150450ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #pragma once //written by Roman Sudarikov #include #include "cpucounters.h" #include "utils.h" #include #include #include #include #include #if defined(_MSC_VER) typedef unsigned int uint; #endif using namespace std; using namespace pcm; #define NUM_SAMPLES (1) static void print(const vector &listNames, bool csv) { for(auto& name : listNames) if (csv) cout << "," << name; else cout << "| " << name << " "; } static uint getIdent (const string &s) { /* * We are adding "| " before and " " after the event name hence +5 to * strlen(eventNames). Rest of the logic is to center the event name. */ uint ident = 5 + (uint)s.size(); return (3 + ident / 2); } class IPlatform { void init(); public: IPlatform(PCM *m, bool csv, bool bandwidth, bool verbose); virtual void getEvents() = 0; virtual void printHeader() = 0; virtual void printEvents() = 0; virtual void printAggregatedEvents() = 0; virtual void cleanup() = 0; static IPlatform *getPlatform(PCM* m, bool csv, bool bandwidth, bool verbose, uint32 delay); virtual ~IPlatform() { } protected: PCM *m_pcm; bool m_csv; bool m_bandwidth; bool m_verbose; uint m_socketCount; enum eventFilter {TOTAL, MISS, HIT, fltLast}; vector filterNames, bwNames; }; void IPlatform::init() { print_cpu_details(); if (m_pcm->isSomeCoreOfflined()) { cerr << "Core offlining is not supported. Program aborted\n"; exit(EXIT_FAILURE); } } IPlatform::IPlatform(PCM *m, bool csv, bool bandwidth, bool verbose) : m_pcm(m), filterNames {"(Total)", "(Miss)", "(Hit)"}, bwNames {"PCIe Rd (B)", "PCIe Wr (B)"} { m_csv = csv; m_bandwidth = bandwidth; m_verbose = verbose; m_socketCount = m_pcm->getNumSockets(); init(); } /* * Common API to program, access and represent required Uncore counters. * The only difference is event opcodes and the way how bandwidth is calculated. */ class LegacyPlatform: public IPlatform { enum { before, after, total }; vector eventNames; vector eventGroups; uint32 m_delay; typedef vector > eventCount_t; array eventCount; virtual void getEvents() final; virtual void printHeader() final; virtual void printEvents() final; virtual void printAggregatedEvents() final; virtual void cleanup() final; void printBandwidth(uint socket, eventFilter filter); void printBandwidth(); void printSocketScopeEvent(uint socket, eventFilter filter, uint idx); void printSocketScopeEvents(uint socket, eventFilter filter); uint64 getEventCount (uint socket, uint idx); uint eventGroupOffset(eventGroup_t &eventGroup); void getEventGroup(eventGroup_t &eventGroup); void printAggregatedEvent(uint idx); public: LegacyPlatform(initializer_list events, initializer_list eventCodes, PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : IPlatform(m, csv, bandwidth, verbose), eventNames(events), eventGroups(eventCodes) { int eventsCount = 0; for (auto &group : eventGroups) eventsCount += (int)group.size(); // Delay for each multiplexing group. Counters will be scaled. m_delay = uint32(delay / eventGroups.size() / NUM_SAMPLES); eventSample.resize(m_socketCount); for (auto &e: eventSample) e.resize(eventsCount); for (auto &run : eventCount) { run.resize(m_socketCount); for (auto &events_ : run) events_.resize(eventsCount); } }; protected: vector> eventSample; virtual uint64 getReadBw(uint socket, eventFilter filter) = 0; virtual uint64 getWriteBw(uint socket, eventFilter filter) = 0; virtual uint64 getReadBw() = 0; virtual uint64 getWriteBw() = 0; virtual uint64 event(uint socket, eventFilter filter, uint idx) = 0; }; void LegacyPlatform::cleanup() { for(auto& socket : eventSample) fill(socket.begin(), socket.end(), 0); } inline uint64 LegacyPlatform::getEventCount (uint skt, uint idx) { return eventGroups.size() * (eventCount[after][skt][idx] - eventCount[before][skt][idx]); } uint LegacyPlatform::eventGroupOffset(eventGroup_t &eventGroup) { uint offset = 0; uint grpIdx = (uint)(&eventGroup - eventGroups.data()); for (auto iter = eventGroups.begin(); iter < eventGroups.begin() + grpIdx; iter++) offset += (uint)iter->size(); return offset; } void LegacyPlatform::getEventGroup(eventGroup_t &eventGroup) { m_pcm->programPCIeEventGroup(eventGroup); uint offset = eventGroupOffset(eventGroup); for (int run = before; run < total; run++) { for (uint skt = 0; skt < m_socketCount; ++skt) for (uint ctr = 0; ctr < eventGroup.size(); ++ctr) eventCount[run][skt][ctr + offset] = m_pcm->getPCIeCounterData(skt, ctr); if (run == before) MySleepMs(m_delay); } for(uint skt = 0; skt < m_socketCount; ++skt) for (uint idx = offset; idx < offset + eventGroup.size(); ++idx) eventSample[skt][idx] += getEventCount(skt, idx); } void LegacyPlatform::getEvents() { for (auto& evGroup : eventGroups) getEventGroup(evGroup); } void LegacyPlatform::printHeader() { cout << "Skt"; if (!m_csv) cout << ' '; print(eventNames, m_csv); if (m_bandwidth) print(bwNames, m_csv); cout << "\n"; } void LegacyPlatform::printBandwidth(uint skt, eventFilter filter) { typedef uint64 (LegacyPlatform::*bwFunc_t)(uint, eventFilter); vector bwFunc = { &LegacyPlatform::getReadBw, &LegacyPlatform::getWriteBw, }; if (!m_csv) for(auto& bw_f : bwFunc) { int ident = getIdent(bwNames[&bw_f - bwFunc.data()]); cout << setw(ident) << unit_format((this->*bw_f)(skt,filter)) << setw(5 + bwNames[&bw_f - bwFunc.data()].size() - ident) << ' '; } else for(auto& bw_f : bwFunc) cout << ',' << (this->*bw_f)(skt,filter); } void LegacyPlatform::printSocketScopeEvent(uint skt, eventFilter filter, uint idx) { uint64 value = event(skt, filter, idx); if (m_csv) cout << ',' << value; else { int ident = getIdent(eventNames[idx]); cout << setw(ident) << unit_format(value) << setw(5 + eventNames[idx].size() - ident) << ' '; } } void LegacyPlatform::printSocketScopeEvents(uint skt, eventFilter filter) { if (!m_csv) { int ident = (int)strlen("Skt |") / 2; cout << setw(ident) << skt << setw(ident) << ' '; } else cout << skt; for(uint idx = 0; idx < eventNames.size(); ++idx) printSocketScopeEvent(skt, filter, idx); if (m_bandwidth) printBandwidth(skt, filter); if(m_verbose) cout << filterNames[filter]; cout << "\n"; } void LegacyPlatform::printEvents() { for(uint skt =0; skt < m_socketCount; ++skt) if (!m_verbose) printSocketScopeEvents(skt, TOTAL); else for (uint flt = TOTAL; flt < fltLast; ++flt) printSocketScopeEvents(skt, static_cast(flt)); } void LegacyPlatform::printAggregatedEvent(uint idx) { uint64 value = 0; for(uint skt =0; skt < m_socketCount; ++skt) value += event(skt, TOTAL, idx); int ident = getIdent(eventNames[idx]); cout << setw(ident) << unit_format(value) << setw(5 + eventNames[idx].size() - ident) << ' '; } void LegacyPlatform::printBandwidth() { typedef uint64 (LegacyPlatform::*bwFunc_t)(); vector bwFunc = { &LegacyPlatform::getReadBw, &LegacyPlatform::getWriteBw, }; for(auto& bw_f : bwFunc) { int ident = getIdent(bwNames[&bw_f - bwFunc.data()]); cout << setw(ident) << unit_format((this->*bw_f)()) << setw(5 + bwNames[&bw_f - bwFunc.data()].size() - ident) << ' '; } } void LegacyPlatform::printAggregatedEvents() { if (!m_csv) { uint len = (uint)strlen("Skt "); for(auto& evt : eventNames) len += (5 + (uint)evt.size()); if (m_bandwidth) for(auto& bw : bwNames) len += (5 + (uint)bw.size()); while (len--) cout << '-'; cout << "\n"; int ident = (int)strlen("Skt |") /2 ; cout << setw(ident) << "*" << setw(ident) << ' '; for (uint idx = 0; idx < eventNames.size(); ++idx) printAggregatedEvent(idx); if (m_bandwidth) printBandwidth(); if (m_verbose) cout << "(Aggregate)\n\n"; else cout << "\n\n"; } } // BHS class BirchStreamPlatform: public LegacyPlatform { public: BirchStreamPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIRdCur", "ItoM", "ItoMCacheNear", "UCRdF", "WiL", "WCiL", "WCiLF"}, { {0xC8F3FE00000435, 0xC8F3FD00000435, 0xCC43FE00000435, 0xCC43FD00000435}, {0xCD43FE00000435, 0xCD43FD00000435, 0xC877DE00000135, 0xC87FDE00000135}, {0xC86FFE00000135, 0xC867FE00000135,}, }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIRdCur, ItoM, ItoMCacheNear, UCRdF, WiL, WCiL, WCiLF }; enum Events { PCIRdCur_miss, PCIRdCur_hit, ItoM_miss, ItoM_hit, ItoMCacheNear_miss, ItoMCacheNear_hit, UCRdF_miss, WiL_miss, WCiL_miss, WCiLF_miss, eventLast }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 BirchStreamPlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; switch (idx) { case PCIRdCur: if (filter == TOTAL) event = eventSample[socket][PCIRdCur_miss] + eventSample[socket][PCIRdCur_hit]; else if (filter == MISS) event = eventSample[socket][PCIRdCur_miss]; else if (filter == HIT) event = eventSample[socket][PCIRdCur_hit]; break; case ItoM: if (filter == TOTAL) event = eventSample[socket][ItoM_miss] + eventSample[socket][ItoM_hit]; else if (filter == MISS) event = eventSample[socket][ItoM_miss]; else if (filter == HIT) event = eventSample[socket][ItoM_hit]; break; case ItoMCacheNear: if (filter == TOTAL) event = eventSample[socket][ItoMCacheNear_miss] + eventSample[socket][ItoMCacheNear_hit]; else if (filter == MISS) event = eventSample[socket][ItoMCacheNear_miss]; else if (filter == HIT) event = eventSample[socket][ItoMCacheNear_hit]; break; case UCRdF: if (filter == TOTAL || filter == MISS) event = eventSample[socket][UCRdF_miss]; break; case WiL: if (filter == TOTAL || filter == MISS) event = eventSample[socket][WiL_miss]; break; case WCiL: if (filter == TOTAL || filter == MISS) event = eventSample[socket][WCiL_miss]; break; case WCiLF: if (filter == TOTAL || filter == MISS) event = eventSample[socket][WCiLF_miss]; break; default: break; } return event; } uint64 BirchStreamPlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIRdCur); return (readBw * 64ULL); } uint64 BirchStreamPlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, ItoM) + event(socket, filter, ItoMCacheNear); return (writeBw * 64ULL); } uint64 BirchStreamPlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIRdCur)); return (readBw * 64ULL); } uint64 BirchStreamPlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, ItoM) + event(socket, TOTAL, ItoMCacheNear)); return (writeBw * 64ULL); } // GRR class LoganvillePlatform: public LegacyPlatform { public: LoganvillePlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIRdCur", "ItoM", "ItoMCacheNear", "UCRdF", "WiL", "WCiL", "WCiLF"}, { {0xC8F3FE00000435, 0xC8F3FD00000435, 0xCC43FE00000435, 0xCC43FD00000435}, {0xCD43FE00000435, 0xCD43FD00000435, 0xC877DE00000135, 0xC87FDE00000135}, {0xC86FFE00000135, 0xC867FE00000135,}, }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIRdCur, ItoM, ItoMCacheNear, UCRdF, WiL, WCiL, WCiLF }; enum Events { PCIRdCur_miss, PCIRdCur_hit, ItoM_miss, ItoM_hit, ItoMCacheNear_miss, ItoMCacheNear_hit, UCRdF_miss, WiL_miss, WCiL_miss, WCiLF_miss, eventLast }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 LoganvillePlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; switch (idx) { case PCIRdCur: if (filter == TOTAL) event = eventSample[socket][PCIRdCur_miss] + eventSample[socket][PCIRdCur_hit]; else if (filter == MISS) event = eventSample[socket][PCIRdCur_miss]; else if (filter == HIT) event = eventSample[socket][PCIRdCur_hit]; break; case ItoM: if (filter == TOTAL) event = eventSample[socket][ItoM_miss] + eventSample[socket][ItoM_hit]; else if (filter == MISS) event = eventSample[socket][ItoM_miss]; else if (filter == HIT) event = eventSample[socket][ItoM_hit]; break; case ItoMCacheNear: if (filter == TOTAL) event = eventSample[socket][ItoMCacheNear_miss] + eventSample[socket][ItoMCacheNear_hit]; else if (filter == MISS) event = eventSample[socket][ItoMCacheNear_miss]; else if (filter == HIT) event = eventSample[socket][ItoMCacheNear_hit]; break; case UCRdF: if (filter == TOTAL || filter == MISS) event = eventSample[socket][UCRdF_miss]; break; case WiL: if (filter == TOTAL || filter == MISS) event = eventSample[socket][WiL_miss]; break; case WCiL: if (filter == TOTAL || filter == MISS) event = eventSample[socket][WCiL_miss]; break; case WCiLF: if (filter == TOTAL || filter == MISS) event = eventSample[socket][WCiLF_miss]; break; default: break; } return event; } uint64 LoganvillePlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIRdCur); return (readBw * 64ULL); } uint64 LoganvillePlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, ItoM) + event(socket, filter, ItoMCacheNear); return (writeBw * 64ULL); } uint64 LoganvillePlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIRdCur)); return (readBw * 64ULL); } uint64 LoganvillePlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, ItoM) + event(socket, TOTAL, ItoMCacheNear)); return (writeBw * 64ULL); } //SPR class EagleStreamPlatform: public LegacyPlatform { public: EagleStreamPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIRdCur", "ItoM", "ItoMCacheNear", "UCRdF", "WiL", "WCiL", "WCiLF"}, { {0xC8F3FE00000435, 0xC8F3FD00000435, 0xCC43FE00000435, 0xCC43FD00000435}, {0xCD43FE00000435, 0xCD43FD00000435, 0xC877DE00000135, 0xC87FDE00000135}, {0xC86FFE00000135, 0xC867FE00000135,}, }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIRdCur, ItoM, ItoMCacheNear, UCRdF, WiL, WCiL, WCiLF }; enum Events { PCIRdCur_miss, PCIRdCur_hit, ItoM_miss, ItoM_hit, ItoMCacheNear_miss, ItoMCacheNear_hit, UCRdF_miss, WiL_miss, WCiL_miss, WCiLF_miss, eventLast }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 EagleStreamPlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; switch (idx) { case PCIRdCur: if(filter == TOTAL) event = eventSample[socket][PCIRdCur_miss] + eventSample[socket][PCIRdCur_hit]; else if (filter == MISS) event = eventSample[socket][PCIRdCur_miss]; else if (filter == HIT) event = eventSample[socket][PCIRdCur_hit]; break; case ItoM: if(filter == TOTAL) event = eventSample[socket][ItoM_miss] + eventSample[socket][ItoM_hit]; else if (filter == MISS) event = eventSample[socket][ItoM_miss]; else if (filter == HIT) event = eventSample[socket][ItoM_hit]; break; case ItoMCacheNear: if(filter == TOTAL) event = eventSample[socket][ItoMCacheNear_miss] + eventSample[socket][ItoMCacheNear_hit]; else if (filter == MISS) event = eventSample[socket][ItoMCacheNear_miss]; else if (filter == HIT) event = eventSample[socket][ItoMCacheNear_hit]; break; case UCRdF: if(filter == TOTAL || filter == MISS) event = eventSample[socket][UCRdF_miss]; break; case WiL: if(filter == TOTAL || filter == MISS) event = eventSample[socket][WiL_miss]; break; case WCiL: if(filter == TOTAL || filter == MISS) event = eventSample[socket][WCiL_miss]; break; case WCiLF: if(filter == TOTAL || filter == MISS) event = eventSample[socket][WCiLF_miss]; break; default: break; } return event; } uint64 EagleStreamPlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIRdCur); return (readBw * 64ULL); } uint64 EagleStreamPlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, ItoM) + event(socket, filter, ItoMCacheNear); return (writeBw * 64ULL); } uint64 EagleStreamPlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIRdCur)); return (readBw * 64ULL); } uint64 EagleStreamPlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, ItoM) + event(socket, TOTAL, ItoMCacheNear)); return (writeBw * 64ULL); } //ICX class WhitleyPlatform: public LegacyPlatform { public: WhitleyPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIRdCur", "ItoM", "ItoMCacheNear", "UCRdF","WiL"}, { {0xC8F3FE00000435, 0xC8F3FD00000435, 0xCC43FE00000435, 0xCC43FD00000435}, {0xCD43FE00000435, 0xCD43FD00000435, 0xC877DE00000135, 0xC87FDE00000135}, }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIRdCur, ItoM, ItoMCacheNear, UCRdF, WiL, }; enum Events { PCIRdCur_miss, PCIRdCur_hit, ItoM_miss, ItoM_hit, ItoMCacheNear_miss, ItoMCacheNear_hit, UCRdF_miss, WiL_miss, eventLast }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 WhitleyPlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; switch (idx) { case PCIRdCur: if(filter == TOTAL) event = eventSample[socket][PCIRdCur_miss] + eventSample[socket][PCIRdCur_hit]; else if (filter == MISS) event = eventSample[socket][PCIRdCur_miss]; else if (filter == HIT) event = eventSample[socket][PCIRdCur_hit]; break; case ItoM: if(filter == TOTAL) event = eventSample[socket][ItoM_miss] + eventSample[socket][ItoM_hit]; else if (filter == MISS) event = eventSample[socket][ItoM_miss]; else if (filter == HIT) event = eventSample[socket][ItoM_hit]; break; case ItoMCacheNear: if(filter == TOTAL) event = eventSample[socket][ItoMCacheNear_miss] + eventSample[socket][ItoMCacheNear_hit]; else if (filter == MISS) event = eventSample[socket][ItoMCacheNear_miss]; else if (filter == HIT) event = eventSample[socket][ItoMCacheNear_hit]; break; case UCRdF: if(filter == TOTAL || filter == MISS) event = eventSample[socket][UCRdF_miss]; break; case WiL: if(filter == TOTAL || filter == MISS) event = eventSample[socket][WiL_miss]; break; default: break; } return event; } uint64 WhitleyPlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIRdCur); return (readBw * 64ULL); } uint64 WhitleyPlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, ItoM) + event(socket, filter, ItoMCacheNear); return (writeBw * 64ULL); } uint64 WhitleyPlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIRdCur)); return (readBw * 64ULL); } uint64 WhitleyPlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, ItoM) + event(socket, TOTAL, ItoMCacheNear)); return (writeBw * 64ULL); } // CLX, SKX class PurleyPlatform: public LegacyPlatform { public: PurleyPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIRdCur", "RFO", "CRd", "DRd","ItoM", "PRd", "WiL"}, { {0x00043c33}, //PCIRdCur_miss {0x00043c37}, //PCIRdCur_hit {0x00040033}, //RFO_miss {0x00040037}, //RFO_hit {0x00040233}, //CRd_miss {0x00040237}, //CRd_hit {0x00040433}, //DRd_miss {0x00040437}, //DRd_hit {0x00049033}, //ItoM_miss {0x00049037}, //ItoM_hit {0x40040e33}, //PRd_miss {0x40040e37}, //PRd_hit {0x40041e33}, //WiL_miss {0x40041e37}, //WiL_hit }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIRdCur, RFO, CRd, DRd, ItoM, PRd, WiL, }; enum Events { PCIRdCur_miss, PCIRdCur_hit, RFO_miss, RFO_hit, CRd_miss, CRd_hit, DRd_miss, DRd_hit, ItoM_miss, ItoM_hit, PRd_miss, PRd_hit, WiL_miss, WiL_hit, }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 PurleyPlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; if(filter == TOTAL) event = eventSample[socket][2 * idx] + eventSample[socket][2 * idx + 1]; else if (filter == MISS) event = eventSample[socket][2 * idx]; else if (filter == HIT) event = eventSample[socket][2 * idx + 1]; return event; } uint64 PurleyPlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIRdCur) + event(socket, filter, RFO) + event(socket, filter, CRd) + event(socket, filter, DRd); return (readBw * 64ULL); } uint64 PurleyPlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIRdCur) + event(socket, TOTAL, RFO) + event(socket, TOTAL, CRd) + event(socket, TOTAL, DRd)); return (readBw * 64ULL); } uint64 PurleyPlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, RFO) + event(socket, filter, ItoM); return (writeBw * 64ULL); } uint64 PurleyPlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, RFO) + event(socket, TOTAL, ItoM)); return (writeBw * 64ULL); } //BDX, HSX class GrantleyPlatform: public LegacyPlatform { public: GrantleyPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIRdCur", "RFO", "CRd", "DRd","ItoM", "PRd", "WiL"}, { {0x19e10000}, //PCIRdCur_miss {0x19e00000}, //PCIRdCur_total {0x18030000}, //RFO_miss {0x18020000}, //RFO_total {0x18110000}, //CRd_miss {0x18100000}, //CRd_total {0x18210000}, //DRd_miss {0x18200000}, //DRd_total {0x1c830000}, //ItoM_miss {0x1c820000}, //ItoM_total {0x18710000}, //PRd_miss {0x18700000}, //PRd_total {0x18f10000}, //WiL_miss {0x18f00000}, //WiL_total }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIRdCur, RFO, CRd, DRd, ItoM, PRd, WiL, }; enum Events { PCIRdCur_miss, PCIRdCur_total, RFO_miss, RFO_total, CRd_miss, CRd_total, DRd_miss, DRd_total, ItoM_miss, ItoM_total, PRd_miss, PRd_total, WiL_miss, WiL_total, }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 GrantleyPlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; if(filter == HIT) if (eventSample[socket][2 * idx] < eventSample[socket][2 * idx + 1]) event = eventSample[socket][2 * idx + 1] - eventSample[socket][2 * idx]; else event = 0; else if (filter == MISS) event = eventSample[socket][2 * idx]; else if (filter == TOTAL) event = eventSample[socket][2 * idx + 1]; return event; } uint64 GrantleyPlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIRdCur) + event(socket, filter, RFO) + event(socket, filter, CRd) + event(socket, filter, DRd); return (readBw * 64ULL); } uint64 GrantleyPlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIRdCur) + event(socket, TOTAL, RFO) + event(socket, TOTAL, CRd) + event(socket, TOTAL, DRd)); return (readBw * 64ULL); } uint64 GrantleyPlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, RFO) + event(socket, filter, ItoM); return (writeBw * 64ULL); } uint64 GrantleyPlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, RFO) + event(socket, TOTAL, ItoM)); return (writeBw * 64ULL); } //IVT, JKT class BromolowPlatform: public LegacyPlatform { public: BromolowPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) : LegacyPlatform( {"PCIeRdCur", "PCIeNSRd", "PCIeWiLF", "PCIeItoM","PCIeNSWr", "PCIeNSWrF"}, { {0x19e10000}, //PCIeRdCur_miss {0x19e00000}, //PCIeRdCur_total {0x1e410000}, //PCIeNSRd_miss {0x1e400000}, //PCIeNSRd_total {0x19410000}, //PCIeWiLF_miss {0x19400000}, //PCIeWiLF_total {0x19c10000}, //PCIeItoM_miss {0x19c00000}, //PCIeItoM_total {0x1e510000}, //PCIeNSWr_miss {0x1e500000}, //PCIeNSWr_total {0x1e610000}, //PCIeNSWrF_miss {0x1e600000}, //PCIeNSWrF_total }, m, csv, bandwidth, verbose, delay) { }; private: enum eventIdx { PCIeRdCur, PCIeNSRd, PCIeWiLF, PCIeItoM, PCIeNSWr, PCIeNSWrF, }; enum Events { PCIeRdCur_miss, PCIeRdCur_total, PCIeNSRd_miss, PCIeNSRd_total, PCIeWiLF_miss, PCIeWiLF_total, PCIeItoM_miss, PCIeItoM_total, PCIeNSWr_miss, PCIeNSWr_total, PCIeNSWrF_miss, PCIeNSWrF_total, }; virtual uint64 getReadBw(uint socket, eventFilter filter); virtual uint64 getWriteBw(uint socket, eventFilter filter); virtual uint64 getReadBw(); virtual uint64 getWriteBw(); virtual uint64 event(uint socket, eventFilter filter, uint idx); }; uint64 BromolowPlatform::event(uint socket, eventFilter filter, uint idx) { uint64 event = 0; if(filter == HIT) if (eventSample[socket][2 * idx] < eventSample[socket][2 * idx + 1]) event = eventSample[socket][2 * idx + 1] - eventSample[socket][2 * idx]; else event = 0; else if (filter == MISS) event = eventSample[socket][2 * idx]; else if (filter == TOTAL) event = eventSample[socket][2 * idx + 1]; return event; } uint64 BromolowPlatform::getReadBw(uint socket, eventFilter filter) { uint64 readBw = event(socket, filter, PCIeRdCur) + event(socket, filter, PCIeNSWr); return (readBw * 64ULL); } uint64 BromolowPlatform::getReadBw() { uint64 readBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) readBw += (event(socket, TOTAL, PCIeRdCur) + event(socket, TOTAL, PCIeNSWr)); return (readBw * 64ULL); } uint64 BromolowPlatform::getWriteBw(uint socket, eventFilter filter) { uint64 writeBw = event(socket, filter, PCIeWiLF) + event(socket, filter, PCIeItoM) + event(socket, filter, PCIeNSWr) + event(socket, filter, PCIeNSWrF); return (writeBw * 64ULL); } uint64 BromolowPlatform::getWriteBw() { uint64 writeBw = 0; for (uint socket = 0; socket < m_socketCount; socket++) writeBw += (event(socket, TOTAL, PCIeWiLF) + event(socket, TOTAL, PCIeItoM) + event(socket, TOTAL, PCIeNSWr) + event(socket, TOTAL, PCIeNSWrF)); return (writeBw * 64ULL); } pcm-202502/src/pcm-power.cpp000066400000000000000000001246541475730356400156230ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // added PPD cycles by Thomas Willhalm #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #include #include // for gettimeofday() #endif #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include "utils.h" #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs using namespace std; using namespace pcm; int getFirstRank(int imc_profile) { return imc_profile * 2; } int getSecondRank(int imc_profile) { return (imc_profile * 2) + 1; } double getCKEOffResidency(uint32 channel, uint32 rank, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { return double(getMCCounter(channel, (rank & 1) ? 2 : 0, before, after)) / double(getDRAMClocks(channel, before, after)); } int64 getCKEOffAverageCycles(uint32 channel, uint32 rank, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { uint64 div = getMCCounter(channel, (rank & 1) ? 3 : 1, before, after); if (div) return getMCCounter(channel, (rank & 1) ? 2 : 0, before, after) / div; return -1; } int64 getCyclesPerTransition(uint32 channel, uint32 rank, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { uint64 div = getMCCounter(channel, (rank & 1) ? 3 : 1, before, after); if (div) return getDRAMClocks(channel, before, after) / div; return -1; } uint64 getSelfRefreshCycles(uint32 channel, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { return getMCCounter(channel, 0, before, after); } uint64 getSelfRefreshTransitions(uint32 channel, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { return getMCCounter(channel, 1, before, after); } uint64 getPPDCycles(uint32 channel, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { return getMCCounter(channel, 2, before, after); } double getNormalizedPCUCounter(uint32 unit, uint32 counter, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after) { const auto clk = getPCUClocks(unit, before, after); if (clk) { return double(getUncoreCounter(PCM::PCU_PMU_ID, unit, counter, before, after)) / double(clk); } return -1.0; } double getNormalizedPCUCounter(uint32 unit, uint32 counter, const ServerUncoreCounterState & before, const ServerUncoreCounterState & after, PCM * m) { const uint64 PCUClocks = (m->getPCUFrequency() * getInvariantTSC(before, after)) / m->getNominalFrequency(); // cout << "PCM Debug: PCU clocks " << PCUClocks << " PCU frequency: " << m->getPCUFrequency() << "\n"; return double(getUncoreCounter(PCM::PCU_PMU_ID, unit, counter, before, after)) / double(PCUClocks); } namespace PERF_LIMIT_REASON_TPMI { // from https://github.com/intel/tpmi_power_management // https://github.com/intel/tpmi_power_management/blob/main/TPMI_Perf_Limit_reasons_rev3.pdf const auto PERF_LIMIT_REASON_TPMI_ID = 0xC; const auto PERF_LIMIT_REASON_TPMI_HEADER = 0x0; const auto PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE = 0x8; const auto PERF_LIMIT_REASON_TPMI_MAILBOX_DATA = 0x10; const auto PERF_LIMIT_REASON_TPMI_DIE_LEVEL = 0x18; const auto PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_COMMAND_WRITE = 1ULL; const auto PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_ID_SHIFT = 12ULL; const auto PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_RUN_BUSY = (1ULL << 63ULL); bool isSupported() { bool PERF_LIMIT_REASON_TPMI_Supported = false; auto numTPMIInstances = TPMIHandle::getNumInstances(); for (size_t i = 0; i < numTPMIInstances; ++i) { TPMIHandle h(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_HEADER); for (uint32 j = 0; j < h.getNumEntries(); ++j) { const auto header = h.read64(j); if (header == ~0ULL) { return false; } const auto version = extract_bits_64(header, 7, 0); const auto majorVersion = extract_bits_64(header, 7, 5); const auto minorVersion = extract_bits_64(header, 4, 0); DBG(1, "PLR_HEADER.INTERFACE_VERSION instance ", i, " die ", j, " version ", version, " (major version ", majorVersion, " minor version ", minorVersion, ")"); PERF_LIMIT_REASON_TPMI_Supported = true; } } return PERF_LIMIT_REASON_TPMI_Supported; } int getMaxPMModuleID(const PCM * m) { int max_pm_module_id = -1; for (unsigned int core = 0; core < m->getNumCores(); ++core) { if (m->isCoreOnline(core) == false) continue; MsrHandle msr(core); uint64 val = 0; constexpr auto MSR_PM_LOGICAL_ID = 0x54; msr.read(MSR_PM_LOGICAL_ID, &val); const auto module_id = (int)extract_bits(val, 10, 3); max_pm_module_id = (std::max)(max_pm_module_id, module_id); } return max_pm_module_id; } void reset(const size_t max_pm_modules) { auto numTPMIInstances = TPMIHandle::getNumInstances(); for (size_t i = 0; i < numTPMIInstances; ++i) { TPMIHandle die_level(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_DIE_LEVEL, false); for (uint32 j = 0; j < die_level.getNumEntries(); ++j) { die_level.write64(j, 0); } for (size_t module = 0; module < max_pm_modules; ++module) { TPMIHandle mailbox_data(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_MAILBOX_DATA, false); TPMIHandle mailbox_interface(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE, false); assert(mailbox_data.getNumEntries() == mailbox_interface.getNumEntries()); for (uint32 j = 0; j < mailbox_data.getNumEntries(); ++j) { mailbox_data.write64(j, 0); const uint64 value = PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_COMMAND_WRITE | (module << PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_ID_SHIFT) | PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_RUN_BUSY ; mailbox_interface.write64(j, value); while (mailbox_interface.read64(j) & PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_RUN_BUSY) { // wait for the command to be processed } } } } }; std::vector> readDies() { auto numTPMIInstances = TPMIHandle::getNumInstances(); std::vector> data; for (size_t i = 0; i < numTPMIInstances; ++i) { std::vector instanceData; TPMIHandle die_level(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_DIE_LEVEL); for (uint32 j = 0; j < die_level.getNumEntries(); ++j) { instanceData.push_back(die_level.read64(j)); } data.push_back(instanceData); } return data; }; std::vector>> readModules(const size_t max_pm_modules) { auto numTPMIInstances = TPMIHandle::getNumInstances(); std::vector>> data; for (size_t i = 0; i < numTPMIInstances; ++i) { std::vector> moduleData; for (size_t module = 0; module < max_pm_modules; ++module) { std::vector instanceData; TPMIHandle mailbox_data(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_MAILBOX_DATA); TPMIHandle mailbox_interface(i, PERF_LIMIT_REASON_TPMI_ID, PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE, false); assert(mailbox_data.getNumEntries() == mailbox_interface.getNumEntries()); for (uint32 j = 0; j < mailbox_data.getNumEntries(); ++j) { const uint64 value = (module << PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_ID_SHIFT) | PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_RUN_BUSY ; mailbox_interface.write64(j, value); while (mailbox_interface.read64(j) & PERF_LIMIT_REASON_TPMI_MAILBOX_INTERFACE_RUN_BUSY) { // wait for the command to be processed } instanceData.push_back(mailbox_data.read64(j)); } moduleData.push_back(instanceData); } data.push_back(moduleData); } return data; }; enum Coarse_Grained_PLR_Bit_Definition { FREQUENCY = 0, CURRENT = 1, POWER = 2, THERMAL = 3, PLATFORM = 4, MCP = 5, RAS = 6, MISC = 7, QOS = 8, DFC = 9, MAX = 10 }; const char * Coarse_Grained_PLR_Bit_Definition_Strings[] = { "FREQUENCY", // Limitation due to Turbo Ratio Limit (TRL) "CURRENT", // Package ICCmax or MT-Pmax "POWER", // Socket or Platform RAPL "THERMAL", // Thermal Throttling "PLATFORM", // Prochot or Hot VR "MCP", // freq limit due to a companion die like PCH "RAS", // freq limit due to RAS "MISC", // Freq limit from out-of-band SW (e.g. BMC) "QOS", // SST-CP, SST-BF, SST-TF "DFC" // Freq limitation due to Dynamic Freq Capping }; enum Fine_Grained_PLR_Bit_Definition { CDYN0 = 0, CDYN1 = 1, CDYN2 = 2, CDYN3 = 3, CDYN4 = 4, CDYN5 = 5, FCT = 6, PCS_TRL = 7, MTPMAX = 8, FAST_RAPL = 9, PKG_PL1_MSR_TPMI = 10, PKG_PL1_MMIO = 11, PKG_PL1_PCS = 12, PKG_PL2_MSR_TPMI = 13, PKG_PL2_MMIO = 14, PKG_PL2_PCS = 15, PLATFORM_PL1_MSR_TPMI = 16, PLATFORM_PL1_MMIO = 17, PLATFORM_PL1_PCS = 18, PLATFORM_PL2_MSR_TPMI = 19, PLATFORM_PL2_MMIO = 20, PLATFORM_PL2_PCS = 21, RSVD = 22, PER_CORE_THERMAL = 23, UFS_DFC = 24, XXPROCHOT = 25, HOT_VR = 26, RSVD2 = 27, RSVD3 = 28, PCS_PSTATE = 29, MAX_FINE = 30 }; struct FGData { const char * name; int coarse_grained_mapping; FGData(const char * n, int c) : name(n), coarse_grained_mapping(c) {} }; const FGData Fine_Grained_PLR_Bit_Definition_Data[] = { FGData("TRL/CDYN0", FREQUENCY), // Turbo Ratio Limit 0 FGData("TRL/CDYN1", FREQUENCY), // Turbo Ratio Limit 1 FGData("TRL/CDYN2", FREQUENCY), // Turbo Ratio Limit 2 FGData("TRL/CDYN3", FREQUENCY), // Turbo Ratio Limit 3 FGData("TRL/CDYN4", FREQUENCY), // Turbo Ratio Limit 4 FGData("TRL/CDYN5", FREQUENCY), // Turbo Ratio Limit 5 FGData("FCT", FREQUENCY), // Favored Core Turbo FGData("PCS_TRL", FREQUENCY), // Turbo Ratio Limit from out-of-band (BMC) FGData("MTPMAX", CURRENT), FGData("FAST_RAPL", POWER), FGData("PKG_PL1_MSR_TPMI", POWER), FGData("PKG_PL1_MMIO", POWER), FGData("PKG_PL1_PCS", POWER), FGData("PKG_PL2_MSR_TPMI", POWER), FGData("PKG_PL2_MMIO", POWER), FGData("PKG_PL2_PCS", POWER), FGData("PLATFORM_PL1_MSR_TPMI", POWER), FGData("PLATFORM_PL1_MMIO", POWER), FGData("PLATFORM_PL1_PCS", POWER), FGData("PLATFORM_PL2_MSR_TPMI", POWER), FGData("PLATFORM_PL2_MMIO", POWER), FGData("PLATFORM_PL2_PCS", POWER), FGData("RSVD", POWER), FGData("PER_CORE_THERMAL", THERMAL), // Thermal Throttling FGData("UFS_DFC", DFC), // Dynamic Freq Capping FGData("XXPROCHOT", PLATFORM), FGData("HOT_VR", PLATFORM), FGData("RSVD2", PLATFORM), FGData("RSVD3", PLATFORM), FGData("PCS_PSTATE", MISC) }; }; int default_freq_band[3] = { 12, 20, 40 }; int freq_band[3]; void print_usage(const string & progname) { cout << "\n Usage: \n " << progname << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; // cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" // << " to a file, in case filename is provided\n"; cout << " [-m imc_profile] [-p pcu_profile] [-a freq_band0] [-b freq_band1] [-c freq_band2]\n\n"; cout << " Where: imc_profile, pcu_profile, freq_band0, freq_band1 and freq_band2 are the following:\n"; cout << " - profile (counter group) for IMC PMU. Possible values are: 0,1,2,3,4,-1 \n"; cout << " profile 0 - rank 0 and rank 1 residencies (default) \n"; cout << " profile 1 - rank 2 and rank 3 residencies \n"; cout << " profile 2 - rank 4 and rank 5 residencies \n"; cout << " profile 3 - rank 6 and rank 7 residencies \n"; cout << " profile 4 - self-refresh residencies \n"; cout << " profile -1 - omit IMC PMU output\n"; cout << " - profile (counter group) for PCU PMU. Possible values are: 0,1,2,3,4,5,-1 \n"; cout << " profile 0 - frequency residencies (default) \n"; cout << " profile 1 - core C-state residencies. The unit is the number of physical cores on the socket who were in C0, C3 or C6 during the measurement interval (e.g. 'C0 residency is 3.5' means on average 3.5 physical cores were resident in C0 state)\n"; cout << " profile 2 - Prochot (throttled) residencies and thermal frequency limit cycles \n"; cout << " profile 3 - {Thermal,Power,Clipped} frequency limit cycles \n"; cout << " profile 4 - {OS,Power,Clipped} frequency limit cycles \n"; cout << " profile 5 - frequency transition statistics \n"; cout << " profile 6 - package C-states residency and transition statistics \n"; cout << " profile 7 - UFS transition statistics (1) \n"; cout << " profile 8 - UFS transition statistics (2) \n"; cout << " profile -1 - omit PCU PMU output\n"; cout << " - frequency minimum for band 0 for PCU frequency residency profile [in 100MHz units] (default is " << default_freq_band[0] << "= " << 100 * default_freq_band[0] << "MHz)\n"; cout << " - frequency minimum for band 1 for PCU frequency residency profile [in 100MHz units] (default is " << default_freq_band[1] << "= " << 100 * default_freq_band[1] << "MHz)\n"; cout << " - frequency minimum for band 2 for PCU frequency residency profile [in 100MHz units] (default is " << default_freq_band[2] << "= " << 100 * default_freq_band[2] << "MHz)\n"; cout << "\n"; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream; check_and_set_silent(argc, argv, nullStream); set_signal_handlers(); cerr << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; cerr << "\n Power Monitoring Utility\n"; int imc_profile = 0; int pcu_profile = 0; double delay = -1.0; char * sysCmd = NULL; char ** sysArgv = NULL; freq_band[0] = default_freq_band[0]; freq_band[1] = default_freq_band[1]; freq_band[2] = default_freq_band[2]; bool csv = false; MainLoop mainLoop; string program = string(argv[0]); PCM * m = PCM::getInstance(); if (argc > 1) do { argv++; argc--; string arg_value; if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"-m"})) { argv++; argc--; imc_profile = atoi(*argv); continue; } else if (check_argument_equals(*argv, {"-p"})) { argv++; argc--; pcu_profile = atoi(*argv); continue; } else if (check_argument_equals(*argv, {"-a"})) { argv++; argc--; freq_band[0] = atoi(*argv); continue; } else if (check_argument_equals(*argv, {"-b"})) { argv++; argc--; freq_band[1] = atoi(*argv); continue; } else if (check_argument_equals(*argv, {"-c"})) { argv++; argc--; freq_band[2] = atoi(*argv); continue; } else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } while (argc > 1); // end of command line partsing loop m->disableJKTWorkaround(); const int cpu_family_model = m->getCPUFamilyModel(); if (!(m->hasPCICFGUncore())) { cerr << "Unsupported processor model (0x" << std::hex << cpu_family_model << std::dec << ").\n"; exit(EXIT_FAILURE); } EventSelectRegister regs[PERF_MAX_CUSTOM_COUNTERS]; PCM::ExtendedCustomCoreEventDescription conf; int32 nCorePowerLicenses = 0; std::vector licenseStr; switch (cpu_family_model) { case PCM::SKX: case PCM::ICX: { std::vector skxLicenseStr = { "Core cycles where the core was running with power-delivery for baseline license level 0. This includes non-AVX codes, SSE, AVX 128-bit, and low-current AVX 256-bit codes.", "Core cycles where the core was running with power-delivery for license level 1. This includes high current AVX 256-bit instructions as well as low current AVX 512-bit instructions.", "Core cycles where the core was running with power-delivery for license level 2 (introduced in Skylake Server michroarchtecture). This includes high current AVX 512-bit instructions." }; licenseStr = skxLicenseStr; regs[0].fields.event_select = 0x28; // CORE_POWER.LVL0_TURBO_LICENSE regs[0].fields.umask = 0x07; // CORE_POWER.LVL0_TURBO_LICENSE regs[1].fields.event_select = 0x28; // CORE_POWER.LVL1_TURBO_LICENSE regs[1].fields.umask = 0x18; // CORE_POWER.LVL1_TURBO_LICENSE regs[2].fields.event_select = 0x28; // CORE_POWER.LVL2_TURBO_LICENSE regs[2].fields.umask = 0x20; // CORE_POWER.LVL2_TURBO_LICENSE conf.nGPCounters = 3; nCorePowerLicenses = 3; conf.gpCounterCfg = regs; } break; } for (size_t l = 0; l < licenseStr.size(); ++l) { cout << "Core Power License " << std::to_string(l) << ": " << licenseStr[l] << "\n"; } if (conf.gpCounterCfg) { m->checkError(m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf)); } m->checkError(m->programServerUncorePowerMetrics(imc_profile, pcu_profile, freq_band)); const auto numSockets = m->getNumSockets(); std::vector BeforeState(numSockets); std::vector AfterState(numSockets); SystemCounterState dummySystemState; std::vector dummyCoreStates; std::vector beforeSocketState, afterSocketState; uint64 BeforeTime = 0, AfterTime = 0; cerr << dec << "\n"; cerr.precision(2); cerr << fixed; cout << dec << "\n"; cout.precision(2); cout << fixed; cerr << "\nMC counter group: " << imc_profile << "\n"; cerr << "PCU counter group: " << pcu_profile << "\n"; if (pcu_profile == 0) { if (cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX_DE || cpu_family_model == PCM::SKX) cerr << "Your processor does not support frequency band statistics\n"; else cerr << "Freq bands [0/1/2]: " << freq_band[0] * 100 << " MHz; " << freq_band[1] * 100 << " MHz; " << freq_band[2] * 100 << " MHz; \n"; } if (sysCmd != NULL) cerr << "Update every " << delay << " seconds\n"; if ((sysCmd != NULL) && (delay <= 0.0)) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (delay <= 0.0) delay = PCM_DELAY_DEFAULT; const bool PERF_LIMIT_REASON_TPMI_Supported = PERF_LIMIT_REASON_TPMI::isSupported(); const size_t max_pm_modules = PERF_LIMIT_REASON_TPMI_Supported ? (size_t)(PERF_LIMIT_REASON_TPMI::getMaxPMModuleID(m) + 1) : 0; DBG(1, "max_pm_modules = ", max_pm_modules); std::vector> PERF_LIMIT_REASON_TPMI_dies_data; std::vector>> PERF_LIMIT_REASON_TPMI_modules_data; uint32 i = 0; for (i = 0; i < numSockets; ++i) BeforeState[i] = m->getServerUncoreCounterState(i); m->getAllCounterStates(dummySystemState, beforeSocketState, dummyCoreStates, false); BeforeTime = m->getTickCount(); if (sysCmd != NULL) { MySystem(sysCmd, sysArgv); } auto getPowerLicenseResidency = [nCorePowerLicenses](const int32 license, const SocketCounterState & before, const SocketCounterState& after) { uint64 all = 0; for (int32 l = 0; l < nCorePowerLicenses; ++l) { all += getNumberOfCustomEvents(l, before, after); } assert(license < nCorePowerLicenses); if (all > 0) return 100.0 * double(getNumberOfCustomEvents(license, before, after)) / double(all); return -1.; }; const auto uncoreFreqFactor = double(m->getNumOnlineSockets()) / double(m->getNumOnlineCores()); if (PERF_LIMIT_REASON_TPMI_Supported) PERF_LIMIT_REASON_TPMI::reset(max_pm_modules); mainLoop([&]() { cout << "----------------------------------------------------------------------------------------------\n"; if (!csv) cout << flush; const auto delay_ms = calibratedSleep(delay, sysCmd, mainLoop, m); AfterTime = m->getTickCount(); for (i = 0; i < numSockets; ++i) AfterState[i] = m->getServerUncoreCounterState(i); m->getAllCounterStates(dummySystemState, afterSocketState, dummyCoreStates, false); if (PERF_LIMIT_REASON_TPMI_Supported) { PERF_LIMIT_REASON_TPMI_dies_data = PERF_LIMIT_REASON_TPMI::readDies(); PERF_LIMIT_REASON_TPMI_modules_data = PERF_LIMIT_REASON_TPMI::readModules(max_pm_modules); PERF_LIMIT_REASON_TPMI::reset(max_pm_modules); } cout << "Time elapsed: " << AfterTime - BeforeTime << " ms\n"; cout << "Called sleep function for " << delay_ms << " ms\n"; for (uint32 socket = 0; socket < numSockets; ++socket) { if (nCorePowerLicenses) { cout << "S" << socket << "; " << "Uncore Freq: " << getAverageUncoreFrequency(BeforeState[socket], AfterState[socket]) * uncoreFreqFactor / 1e9 << " Ghz; " "Core Freq: " << getActiveAverageFrequency(beforeSocketState[socket], afterSocketState[socket]) / 1e9 << " Ghz; "; for (int32 l = 0; l < nCorePowerLicenses; ++l) { cout << "Core Power License " << std::to_string(l) << ": " << getPowerLicenseResidency(l, beforeSocketState[socket], afterSocketState[socket]) << "%; "; } cout << "\n"; } for (uint32 port = 0; port < m->getQPILinksPerSocket(); ++port) { cout << "S" << socket << "P" << port << "; " + std::string(m->xPI()) + " Clocks: " << getQPIClocks(port, BeforeState[socket], AfterState[socket]) << "; L0p Tx Cycles: " << 100. * getNormalizedQPIL0pTxCycles(port, BeforeState[socket], AfterState[socket]) << "%" << "; L1 Cycles: " << 100. * getNormalizedQPIL1Cycles(port, BeforeState[socket], AfterState[socket]) << "%" << "\n"; } for (uint32 channel = 0; channel < m->getMCChannelsPerSocket(); ++channel) { if (imc_profile <= 3 && imc_profile >= 0) { cout << "S" << socket << "CH" << channel << "; DRAMClocks: " << getDRAMClocks(channel, BeforeState[socket], AfterState[socket]) << "; Rank" << getFirstRank(imc_profile) << " CKE Off Residency: " << setw(3) << 100. * getCKEOffResidency(channel, getFirstRank(imc_profile), BeforeState[socket], AfterState[socket]) << "%" << "; Rank" << getFirstRank(imc_profile) << " CKE Off Average Cycles: " << getCKEOffAverageCycles(channel, getFirstRank(imc_profile), BeforeState[socket], AfterState[socket]) << "; Rank" << getFirstRank(imc_profile) << " Cycles per transition: " << getCyclesPerTransition(channel, getFirstRank(imc_profile), BeforeState[socket], AfterState[socket]) << "\n"; cout << "S" << socket << "CH" << channel << "; DRAMClocks: " << getDRAMClocks(channel, BeforeState[socket], AfterState[socket]) << "; Rank" << getSecondRank(imc_profile) << " CKE Off Residency: " << setw(3) << 100. * getCKEOffResidency(channel, getSecondRank(imc_profile), BeforeState[socket], AfterState[socket]) << "%" << "; Rank" << getSecondRank(imc_profile) << " CKE Off Average Cycles: " << getCKEOffAverageCycles(channel, getSecondRank(imc_profile), BeforeState[socket], AfterState[socket]) << "; Rank" << getSecondRank(imc_profile) << " Cycles per transition: " << getCyclesPerTransition(channel, getSecondRank(imc_profile), BeforeState[socket], AfterState[socket]) << "\n"; } else if (imc_profile == 4) { cout << "S" << socket << "CH" << channel << "; DRAMClocks: " << getDRAMClocks(channel, BeforeState[socket], AfterState[socket]) << "; Self-refresh cycles: " << getSelfRefreshCycles(channel, BeforeState[socket], AfterState[socket]) << "; Self-refresh transitions: " << getSelfRefreshTransitions(channel, BeforeState[socket], AfterState[socket]) << "; PPD cycles: " << getPPDCycles(channel, BeforeState[socket], AfterState[socket]) << "\n"; } } for (uint32 u = 0; u < m->getMaxNumOfUncorePMUs(PCM::PCU_PMU_ID); ++u) { auto printHeader = [&socket,&m,&u, &BeforeState, &AfterState] (const bool printPCUClocks) { cout << "S" << socket; if (m->getMaxNumOfUncorePMUs(PCM::PCU_PMU_ID) > 1) { cout << "U" << u; } if (printPCUClocks) { cout << "; PCUClocks: " << getPCUClocks(u, BeforeState[socket], AfterState[socket]); } }; switch (pcu_profile) { case 0: if (cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX_DE || cpu_family_model == PCM::SKX) break; printHeader(true); cout << "; Freq band 0/1/2 cycles: " << 100. * getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket]) << "%" << "; " << 100. * getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket]) << "%" << "; " << 100. * getNormalizedPCUCounter(u, 3, BeforeState[socket], AfterState[socket]) << "%" << "\n"; break; case 1: printHeader(true); cout << ((cpu_family_model == PCM::SKX) ? "; core C0_1/C3/C6_7-state residency: " : "; core C0/C3/C6-state residency: ") << getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket]) << "; " << getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket]) << "; " << getNormalizedPCUCounter(u, 3, BeforeState[socket], AfterState[socket]) << "\n"; break; case 2: printHeader(true); cout << "; Internal prochot cycles: " << getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket]) * 100. << " %" << "; External prochot cycles:" << getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket]) * 100. << " %" << "; Thermal freq limit cycles:" << getNormalizedPCUCounter(u, 3, BeforeState[socket], AfterState[socket]) * 100. << " %" << "\n"; break; case 3: printHeader(true); cout << "; Thermal freq limit cycles: " << getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket]) * 100. << " %" << "; Power freq limit cycles:" << getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket]) * 100. << " %"; if( cpu_family_model != PCM::SKX && cpu_family_model != PCM::ICX && cpu_family_model != PCM::SNOWRIDGE && cpu_family_model != PCM::SPR && cpu_family_model != PCM::EMR && cpu_family_model != PCM::SRF && cpu_family_model != PCM::GNR && cpu_family_model != PCM::GNR_D ) cout << "; Clipped freq limit cycles:" << getNormalizedPCUCounter(u, 3, BeforeState[socket], AfterState[socket]) * 100. << " %"; cout << "\n"; break; case 4: if ( cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D ) { cout << "This PCU profile is not supported on your processor\n"; break; } printHeader(true); cout << "; OS freq limit cycles: " << getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket]) * 100. << " %" << "; Power freq limit cycles:" << getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket]) * 100. << " %" << "; Clipped freq limit cycles:" << getNormalizedPCUCounter(u, 3, BeforeState[socket], AfterState[socket]) * 100. << " %" << "\n"; break; case 5: printHeader(true); cout << "; Frequency transition count: " << getUncoreCounter(PCM::PCU_PMU_ID, u, 1, BeforeState[socket], AfterState[socket]) << " " << "; Cycles spent changing frequency: " << getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket], m) * 100. << " %"; if (PCM::HASWELLX == cpu_family_model) { cout << "; UFS transition count: " << getUncoreCounter(PCM::PCU_PMU_ID, u, 3, BeforeState[socket], AfterState[socket]) << " "; cout << "; UFS transition cycles: " << getNormalizedPCUCounter(u, 0, BeforeState[socket], AfterState[socket], m) * 100. << " %"; } cout << "\n"; break; case 6: printHeader(false); if (cpu_family_model == PCM::HASWELLX || PCM::BDX_DE == cpu_family_model) cout << "; PC1e+ residency: " << getNormalizedPCUCounter(u, 0, BeforeState[socket], AfterState[socket], m) * 100. << " %" "; PC1e+ transition count: " << getUncoreCounter(PCM::PCU_PMU_ID, u, 1, BeforeState[socket], AfterState[socket]) << " "; switch (cpu_family_model) { case PCM::IVYTOWN: case PCM::HASWELLX: case PCM::BDX_DE: case PCM::SKX: case PCM::ICX: case PCM::SNOWRIDGE: case PCM::SPR: case PCM::EMR: case PCM::SRF: case PCM::GNR: case PCM::GNR_D: cout << "; PC2 residency: " << getPackageCStateResidency(2, BeforeState[socket], AfterState[socket]) * 100. << " %"; cout << "; PC2 transitions: " << getUncoreCounter(PCM::PCU_PMU_ID, u, 2, BeforeState[socket], AfterState[socket]) << " "; cout << "; PC3 residency: " << getPackageCStateResidency(3, BeforeState[socket], AfterState[socket]) * 100. << " %"; cout << "; PC6 residency: " << getPackageCStateResidency(6, BeforeState[socket], AfterState[socket]) * 100. << " %"; cout << "; PC6 transitions: " << getUncoreCounter(PCM::PCU_PMU_ID, u, 3, BeforeState[socket], AfterState[socket]) << " "; break; } cout << "\n"; break; case 7: if (PCM::HASWELLX == cpu_family_model || PCM::BDX_DE == cpu_family_model || PCM::BDX == cpu_family_model) { printHeader(false); cout << "; UFS_TRANSITIONS_PERF_P_LIMIT: " << getNormalizedPCUCounter(u, 0, BeforeState[socket], AfterState[socket], m) * 100. << " %" << "; UFS_TRANSITIONS_IO_P_LIMIT: " << getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket], m) * 100. << " %" << "; UFS_TRANSITIONS_UP_RING_TRAFFIC: " << getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket], m) * 100. << " %" << "; UFS_TRANSITIONS_UP_STALL_CYCLES: " << getNormalizedPCUCounter(u, 3, BeforeState[socket], AfterState[socket], m) * 100. << " %" << "\n"; } break; case 8: if (PCM::HASWELLX == cpu_family_model || PCM::BDX_DE == cpu_family_model || PCM::BDX == cpu_family_model) { printHeader(false); cout << "; UFS_TRANSITIONS_DOWN: " << getNormalizedPCUCounter(u, 0, BeforeState[socket], AfterState[socket], m) * 100. << " %" << "\n"; } break; } } cout << "S" << socket << "; Consumed energy units: " << getConsumedEnergy(BeforeState[socket], AfterState[socket]) << "; Consumed Joules: " << getConsumedJoules(BeforeState[socket], AfterState[socket]) << "; Watts: " << 1000. * getConsumedJoules(BeforeState[socket], AfterState[socket]) / double(AfterTime - BeforeTime) << "; Thermal headroom below TjMax: " << AfterState[socket].getPackageThermalHeadroom() << "\n"; cout << "S" << socket << "; Consumed DRAM energy units: " << getDRAMConsumedEnergy(BeforeState[socket], AfterState[socket]) << "; Consumed DRAM Joules: " << getDRAMConsumedJoules(BeforeState[socket], AfterState[socket]) << "; DRAM Watts: " << 1000. * getDRAMConsumedJoules(BeforeState[socket], AfterState[socket]) / double(AfterTime - BeforeTime) << "\n"; } for (auto instance = 0ULL; instance < PERF_LIMIT_REASON_TPMI_dies_data.size(); ++instance) { for (auto die = 0ULL; die < PERF_LIMIT_REASON_TPMI_dies_data[instance].size(); ++die) { cout << "S" << instance << "D" << die << "; PERF LIMIT REASONS (DIE LEVEL): "; const auto data = PERF_LIMIT_REASON_TPMI_dies_data[instance][die]; for (auto l = 0; l < PERF_LIMIT_REASON_TPMI::Coarse_Grained_PLR_Bit_Definition::MAX; ++l) { if (extract_bits(data, l, l)) cout << PERF_LIMIT_REASON_TPMI::Coarse_Grained_PLR_Bit_Definition_Strings[l] << "; "; } cout << "\n"; } } for (auto instance = 0ULL; instance < PERF_LIMIT_REASON_TPMI_modules_data.size(); ++instance) { assert(PERF_LIMIT_REASON_TPMI_modules_data[instance].size()); std::vector> coarseGrainedData, fineGrainedData; std::array empty; empty.fill(0); coarseGrainedData.resize(PERF_LIMIT_REASON_TPMI_modules_data[instance][0].size(), empty); fineGrainedData.resize(coarseGrainedData.size(), empty); for (auto module = 0ULL; module < PERF_LIMIT_REASON_TPMI_modules_data[instance].size(); ++module) { assert(PERF_LIMIT_REASON_TPMI_modules_data[instance][module].size() == coarseGrainedData.size()); for (auto die = 0ULL; die < PERF_LIMIT_REASON_TPMI_modules_data[instance][module].size(); ++die) { const auto data = PERF_LIMIT_REASON_TPMI_modules_data[instance][module][die]; DBG(1, "S", instance, "M", module, "D", die, " data = ", data); for (auto l = 0; l < PERF_LIMIT_REASON_TPMI::Coarse_Grained_PLR_Bit_Definition::MAX; ++l) { if (extract_bits(data, l, l)) ++(coarseGrainedData[die][l]); } for (auto l = 0; l < PERF_LIMIT_REASON_TPMI::Fine_Grained_PLR_Bit_Definition::MAX_FINE; ++l) { if (extract_bits(data, 32 + l, 32 + l)) ++(fineGrainedData[die][l]); } } } for (auto die = 0ULL; die < coarseGrainedData.size(); ++die) { cout << "S" << instance << "D" << die << "; PERF LIMIT REASONS (#CORE MODULES): "; for (auto l = 0; l < PERF_LIMIT_REASON_TPMI::Coarse_Grained_PLR_Bit_Definition::MAX; ++l) { if (coarseGrainedData[die][l]) cout << PERF_LIMIT_REASON_TPMI::Coarse_Grained_PLR_Bit_Definition_Strings[l] << ": " << coarseGrainedData[die][l] << "; "; } for (auto l = 0; l < PERF_LIMIT_REASON_TPMI::Fine_Grained_PLR_Bit_Definition::MAX_FINE; ++l) { if (fineGrainedData[die][l]) { cout << PERF_LIMIT_REASON_TPMI::Coarse_Grained_PLR_Bit_Definition_Strings[PERF_LIMIT_REASON_TPMI::Fine_Grained_PLR_Bit_Definition_Data[l].coarse_grained_mapping] << "." << PERF_LIMIT_REASON_TPMI::Fine_Grained_PLR_Bit_Definition_Data[l].name << ": " << fineGrainedData[die][l] << "; "; } } cout << "\n"; } } swap(BeforeState, AfterState); swap(BeforeTime, AfterTime); swap(beforeSocketState, afterSocketState); if (m->isBlocked()) { cout << "----------------------------------------------------------------------------------------------\n"; // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-raw.cpp000066400000000000000000003444651475730356400152640ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2023, Intel Corporation /*! \file pcm-raw.cpp \brief Example of using CPU counters: implements a performance counter monitoring utility with raw events interface */ #include #ifdef _MSC_VER #define strtok_r strtok_s #include #include "windows/windriver.h" #else #include #include #include // for gettimeofday() #endif #include #include #include #include #include #include #include #include #include #include #include "cpucounters.h" #include "utils.h" #if PCM_SIMDJSON_AVAILABLE #include "simdjson.h" #endif #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs #define MAX_CORES 4096 using namespace std; using namespace pcm; void print_usage(const string & progname) { cout << "\n Usage: \n " << progname << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -e event1 [-e event2] [-e event3] .. => list of custom events to monitor\n"; cout << " -pid PID | /pid PID => collect core metrics only for specified process ID\n"; cout << " -r | --reset | /reset => reset PMU configuration (at your own risk)\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -json[=file.json] | /json[=file.json] => output json format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -out filename | /out filename => write all output (stdout and stderr) to specified file\n"; cout << " event description example: -e core/config=0x30203,name=LD_BLOCKS.STORE_FORWARD/ -e core/fixed,config=0x333/ \n"; cout << " -e cha/config=0,name=UNC_CHA_CLOCKTICKS/ -e imc/fixed,name=DRAM_CLOCKS/\n"; #ifdef PCM_SIMDJSON_AVAILABLE cout << " -e NAME where the NAME is an event from https://github.com/intel/perfmon event lists\n"; cout << " -ep path | /ep path => path to event list directory (default is the current directory)\n"; #endif cout << " -yc | --yescores | /yc => enable specific cores to output\n"; cout << " -f | /f => enforce flushing each line for interactive output\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " -tr | /tr => transpose output (print single event data in a row)\n"; cout << " -ext | /ext => add headers to transposed output and extend printout to match it\n"; cout << " -single-header | /single-header => headers for transposed output are merged into single header\n"; cout << " -s | /s => print a sample separator line between samples in transposed output\n"; cout << " -v | /v => verbose mode (print additional diagnostic messages)\n"; cout << " -l => use locale for printing values, calls -tab for readability\n"; cout << " -tab => replace default comma separator with tab\n"; cout << " -el event_list.txt | /el event_list.txt => read event list from event_list.txt file, \n"; cout << " each line represents an event,\n"; cout << " event groups are separated by a semicolon\n"; cout << " -edp | /edp => 'edp' output mode\n"; print_help_force_rtm_abort_mode(41); cout << " Examples:\n"; cout << " " << progname << " 1 => print counters every second without core and socket output\n"; cout << " " << progname << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << progname << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } bool verbose = false; double defaultDelay = 1.0; // in seconds TelemetryDB telemDB; PCM::RawEventConfig initCoreConfig() { return PCM::RawEventConfig{ {0,0,0, PCM::ExtendedCustomCoreEventDescription::invalidMsrValue(),PCM::ExtendedCustomCoreEventDescription::invalidMsrValue(), 0 }, "" }; } void printEvent(const std::string & pmuName, const bool fixed, const PCM::RawEventConfig & config) { cerr << "parsed " << (fixed ? "fixed " : "") << " " << pmuName << " event: \"" << hex << config.second << "\" : {" << hex << "0x" << config.first[0] << ", 0x" << config.first[1] << ", 0x" << config.first[2] << ", 0x" << config.first[3] << ", 0x" << config.first[4] << ", 0x" << config.first[5] << "}\n" << dec; } void lowerCase(std::string & str) { std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { #ifdef _MSC_VER return std::tolower(c, std::locale()); #else return std::tolower(c); // std::locale has some bad_cast issues in g++ #endif }); } enum AddEventStatus { OK, Failed, FailedTooManyEvents }; bool tooManyEvents(const std::string & pmuName, const int event_pos, const std::string& fullEventStr) { if (isRegisterEvent(pmuName)) { return false; } PCM* m = PCM::getInstance(); assert(m); const int maxCounters = (pmuName == "core" || pmuName == "atom") ? m->getMaxCustomCoreEvents() : ServerUncoreCounterState::maxCounters; if (event_pos >= maxCounters) { std::cerr << "ERROR: trying to add event " << fullEventStr << " at position " << event_pos << " of an event group, which exceeds the max num possible (" << maxCounters << ").\n"; return true; } return false; } #ifdef PCM_SIMDJSON_AVAILABLE using namespace simdjson; std::vector > JSONparsers; std::unordered_map PMUEventMapJSON; std::vector>> PMUEventMapsTSV; std::shared_ptr PMURegisterDeclarations; std::string eventFileLocationPrefix = "."; bool parse_tsv(const string &path) { bool col_names_parsed = false; int event_name_pos = -1; ifstream inFile; string line; inFile.open(path); std::unordered_map> PMUEventMap; while (getline(inFile, line)) { if (line.size() == 1 && line[0] == '\n') continue; // Trim whitespaces left/right // MOVE to utils auto ws_left_count = 0; for (size_t i = 0 ; i < line.size() ; i++) { if (line[i] == ' ') ws_left_count++; else break; } auto ws_right_count = 0; for (size_t i = line.size() - 1 ; i > 0 ; i--) { if (line[i] == ' ') ws_right_count++; else break; } line.erase(0, ws_left_count); line.erase(line.size() - ws_right_count, ws_right_count); if (line[0] == '#') continue; if (!col_names_parsed) { // Consider first row as Column name row std::vector col_names = split(line, '\t'); PMUEventMap["COL_NAMES"] = col_names; const auto event_name_it = std::find(col_names.begin(), col_names.end(), "EventName"); if (event_name_it == col_names.end()) { cerr << "ERROR: First row does not contain EventName\n"; inFile.close(); return false; } event_name_pos = (int)(event_name_it - col_names.begin()); col_names_parsed = true; continue; } std::vector entry = split(line, '\t'); std::string event_name = entry[event_name_pos]; PMUEventMap[event_name] = entry; } inFile.close(); PMUEventMapsTSV.push_back(PMUEventMap); return true; } bool initPMUEventMap() { static bool inited = false; if (inited == true) { return true; } inited = true; const auto mapfile = "mapfile.csv"; const auto mapfilePath = eventFileLocationPrefix + "/" + mapfile; std::ifstream in(mapfilePath); std::string line, item; if (!in.is_open()) { cerr << "ERROR: File " << mapfilePath << " can't be open. \n"; cerr << " Use -ep /perfmon option if you cloned PCM source repository recursively with submodules,\n"; cerr << " or run 'git clone https://github.com/intel/perfmon' to download the perfmon event repository and use -ep option\n"; cerr << " or download the file from https://raw.githubusercontent.com/intel/perfmon/main/" << mapfile << " \n"; return false; } int32 FMSPos = -1; int32 FilenamePos = -1; int32 EventTypetPos = -1; if (std::getline(in, line)) { auto header = split(line, ','); for (int32 i = 0; i < (int32)header.size(); ++i) { if (header[i] == "Family-model") { FMSPos = i; } else if (header[i] == "Filename") { FilenamePos = i; } else if (header[i] == "EventType") { EventTypetPos = i; } } } else { cerr << "Can't read first line from " << mapfile << " \n"; return false; } // cout << FMSPos << " " << FilenamePos << " " << EventTypetPos << "\n"; assert(FMSPos >= 0); assert(FilenamePos >= 0); assert(EventTypetPos >= 0); const std::string ourFMS = PCM::getInstance()->getCPUFamilyModelString(); // cout << "Our FMS: " << ourFMS << "\n"; std::multimap eventFiles; cerr << "Matched event files:\n"; while (std::getline(in, line)) { auto tokens = split(line, ','); assert(FMSPos < (int32)tokens.size()); assert(FilenamePos < (int32)tokens.size()); assert(EventTypetPos < (int32)tokens.size()); std::regex FMSRegex(tokens[FMSPos]); std::cmatch FMSMatch; if (std::regex_search(ourFMS.c_str(), FMSMatch, FMSRegex)) { cerr << tokens[FMSPos] << " " << tokens[EventTypetPos] << " " << tokens[FilenamePos] << "\n"; eventFiles.insert(std::make_pair(tokens[EventTypetPos], tokens[FilenamePos])); } } in.close(); if (eventFiles.empty()) { cerr << "ERROR: CPU " << ourFMS << " not found in " << mapfile << "\n"; return false; } for (const auto& evfile : eventFiles) { std::string path; auto printError = [&evfile]() { cerr << "Make sure you have downloaded " << evfile.second << " from https://raw.githubusercontent.com/intel/perfmon/main/" + evfile.second + " \n"; }; try { cerr << evfile.first << " " << evfile.second << "\n"; if (evfile.first == "core" || evfile.first == "uncore" || evfile.first == "uncore experimental") { const std::string path1 = eventFileLocationPrefix + evfile.second; const std::string path2 = eventFileLocationPrefix + evfile.second.substr(evfile.second.rfind('/')); if (std::ifstream(path1).good()) { path = path1; } else if (std::ifstream(path2).good()) { path = path2; } else { std::cerr << "ERROR: Can't open event file at location " << path1 << " or " << path2 << "\n"; printError(); return false; } if (path.find(".json") != std::string::npos) { JSONparsers.push_back(std::make_shared()); auto JSONObjects = JSONparsers.back()->load(path); if (JSONObjects["Header"].error() != NO_SUCH_FIELD) { JSONObjects = JSONObjects["Events"]; } for (simdjson::dom::object eventObj : JSONObjects) { // cout << "Event ----------------\n"; const std::string EventName{eventObj["EventName"].get_c_str()}; if (EventName.empty()) { cerr << "Did not find EventName in JSON object:\n"; for (const auto& keyValue : eventObj) { cout << "key: " << keyValue.key << " value: " << keyValue.value << "\n"; } } else { PMUEventMapJSON[EventName] = eventObj; } } } else if (path.find(".tsv") != std::string::npos) { if (!parse_tsv(path)) return false; } else { cerr << "ERROR: Could not determine Event file type (JSON/TSV)\n"; return false; } } } catch (std::exception& e) { cerr << "Error while opening and/or parsing " << path << " : " << e.what() << "\n"; printError(); return false; } } if (PMUEventMapJSON.empty() && PMUEventMapsTSV.empty()) { return false; } return true; } class EventMap { public: static bool isEvent(const std::string &eventStr) { if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) return true; for (const auto &EventMapTSV : PMUEventMapsTSV) { if (EventMapTSV.find(eventStr) != EventMapTSV.end()) return true; } return false; } static bool isField(const std::string &eventStr, const std::string event) { if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { const auto eventObj = PMUEventMapJSON[eventStr]; const auto unitObj = eventObj[event]; return unitObj.error() != NO_SUCH_FIELD; } for (auto &EventMapTSV : PMUEventMapsTSV) { if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { const auto &col_names = EventMapTSV["COL_NAMES"]; const auto event_name_it = std::find(col_names.begin(), col_names.end(), event); if (event_name_it != col_names.end()) { const size_t event_name_pos = event_name_it - col_names.begin(); return event_name_pos < EventMapTSV[eventStr].size(); } } } return false; } static std::string getField(const std::string &eventStr, const std::string &event) { std::string res; if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { const auto eventObj = PMUEventMapJSON[eventStr]; const auto unitObj = eventObj[event]; return std::string(unitObj.get_c_str()); } for (auto &EventMapTSV : PMUEventMapsTSV) { if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { const auto col_names = EventMapTSV["COL_NAMES"]; const auto event_name_it = std::find(col_names.begin(), col_names.end(), event); if (event_name_it != col_names.end()) { const auto event_name_pos = event_name_it - col_names.begin(); res = EventMapTSV[eventStr][event_name_pos]; } } } return res; } static void print_event(const std::string &eventStr) { if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { const auto eventObj = PMUEventMapJSON[eventStr]; for (const auto & keyValue : eventObj) std::cout << keyValue.key << " : " << keyValue.value << "\n"; return; } for (auto &EventMapTSV : PMUEventMapsTSV) { if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { const auto &col_names = EventMapTSV["COL_NAMES"]; const auto event = EventMapTSV[eventStr]; if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { for (size_t i = 0 ; i < col_names.size() ; i++) std::cout << col_names[i] << " : " << event[i] << "\n"; return; } } } } }; AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEventStr) { if (initPMUEventMap() == false) { cerr << "ERROR: PMU Event map can not be initialized\n"; return AddEventStatus::Failed; } // cerr << "Parsing event " << fullEventStr << "\n"; // cerr << "size: " << fullEventStr.size() << "\n"; while (fullEventStr.empty() == false && fullEventStr.back() == ' ') { fullEventStr.resize(fullEventStr.size() - 1); // remove trailing spaces } while (fullEventStr.empty() == false && fullEventStr.front() == ' ') { fullEventStr = fullEventStr.substr(1); // remove leading spaces } if (fullEventStr.empty()) { return AddEventStatus::OK; } const auto EventTokens = split(fullEventStr, ':'); assert(!EventTokens.empty()); auto mod = EventTokens.begin(); ++mod; const auto eventStr = EventTokens[0]; // cerr << "size: " << eventStr.size() << "\n"; PCM::RawEventConfig config = { {0,0,0,0,0}, "" }; std::string pmuName; auto unsupported = [&]() { cerr << "Unsupported event modifier: " << *mod << " in event " << fullEventStr << "\n"; }; if (eventStr == "MSR_EVENT") { while (mod != EventTokens.end()) { auto assignment = split(*mod, '='); for (auto& s : assignment) { lowerCase(s); } if (assignment.size() == 2 && assignment[0] == "msr") { config.first[PCM::MSREventPosition::index] = read_number(assignment[1].c_str()); } else if (assignment.size() == 2 && assignment[0] == "type") { if (assignment[1] == "static") { config.first[PCM::MSREventPosition::type] = PCM::MSRType::Static; } else if (assignment[1] == "freerun") { config.first[PCM::MSREventPosition::type] = PCM::MSRType::Freerun; } else { unsupported(); return AddEventStatus::Failed; } } else if (assignment.size() == 2 && assignment[0] == "scope") { if (assignment[1] == "package") { pmuName = "package_msr"; } else if (assignment[1] == "thread") { pmuName = "thread_msr"; } else { unsupported(); return AddEventStatus::Failed; } } else { unsupported(); return AddEventStatus::Failed; } ++mod; } if (pmuName.empty()) { cerr << "ERROR: scope is not defined in event " << fullEventStr << ". Possible values: package, thread\n"; return AddEventStatus::Failed; } config.second = fullEventStr; curPMUConfigs[pmuName].fixed.push_back(config); return AddEventStatus::OK; } if (!EventMap::isEvent(eventStr)) { cerr << "ERROR: event " << eventStr << " could not be found in event database. Ignoring the event.\n"; return AddEventStatus::OK; } bool fixed = false; static size_t offcoreEventIndex = 0; auto * pcm = PCM::getInstance(); assert(pcm); int stepping = pcm->getCPUStepping(); assert(stepping >= 0); std::string path, err_msg; for (; stepping >= 0; --stepping) { try { path = std::string("PMURegisterDeclarations/") + pcm->getCPUFamilyModelString(pcm->getCPUFamily(), pcm->getInternalCPUModel(), (uint32)stepping) + ".json"; std::ifstream in(path); if (!in.is_open()) { const auto alt_path = getInstallPathPrefix() + path; in.open(alt_path); if (!in.is_open()) { err_msg = std::string("event file ") + path + " or " + alt_path + " is not available."; throw std::invalid_argument(err_msg); } path = alt_path; } in.close(); break; } catch (std::invalid_argument & e) { std::cerr << "INFO: " << e.what() << "\n"; path.clear(); } } if (path.empty()) { throw std::invalid_argument(err_msg); } if (PMURegisterDeclarations.get() == nullptr) { // declaration not loaded yet try { JSONparsers.push_back(std::make_shared()); PMURegisterDeclarations = std::make_shared(); *PMURegisterDeclarations = JSONparsers.back()->load(path); } catch (std::exception& e) { cerr << "Error while opening and/or parsing " << path << " : " << e.what() << "\n"; return AddEventStatus::Failed; } } static std::map pmuNameMap = { {std::string("cbo"), std::string("cha")}, {std::string("b2cmi"), std::string("m2m")}, {std::string("upi"), std::string("xpi")}, {std::string("upi ll"), std::string("xpi")}, {std::string("b2upi"), std::string("m3upi")}, {std::string("qpi"), std::string("xpi")}, {std::string("qpi ll"), std::string("xpi")} }; if (!EventMap::isField(eventStr, "Unit")) { pmuName = "core"; config = initCoreConfig(); } else { std::string unit = EventMap::getField(eventStr, "Unit"); lowerCase(unit); // std::cout << eventStr << " is uncore event for unit " << unit << "\n"; pmuName = (pmuNameMap.find(unit) == pmuNameMap.end()) ? unit : pmuNameMap[unit]; } config.second = fullEventStr; if (1) { // cerr << "pmuName: " << pmuName << " full event "<< fullEventStr << " \n"; std::string CounterStr = EventMap::getField(eventStr, "Counter"); // cout << "Counter: " << CounterStr << "\n"; int fixedCounter = -1; fixed = (pcm_sscanf(CounterStr) >> s_expect("Fixed counter ") >> fixedCounter) ? true : false; if (!fixed){ bool counter_match=false; std::stringstream ss(CounterStr); // to get current event position int event_pos = curPMUConfigs[pmuName].programmable.size(); // loop through counter string and check if event pos matches any counter values for (int i = 0; ss >> i;) { if(event_pos == i) counter_match = true; if (ss.peek() == ',') ss.ignore(); } if(!counter_match) { std::cerr << "ERROR: position of " << fullEventStr << " event in the command is " << event_pos<<" but the supported counters are "<= config.first.size()) throw std::runtime_error("Config field value is out of bounds"); const auto width = uint64_t(fieldDescriptionObj["Width"]); assert(width <= 64); config.first[cfg] = insertBits(config.first[cfg], value, position, width); }; auto PMUObj = (*PMURegisterDeclarations)[pmuName]; if (PMUObj.error() == NO_SUCH_FIELD) { cerr << "ERROR: PMU \"" << pmuName << "\" not found for event " << fullEventStr << " in " << path << ", ignoring the event.\n"; return AddEventStatus::OK; } simdjson::dom::object PMUDeclObj; if (fixed) { PMUDeclObj = (*PMURegisterDeclarations)[pmuName][std::string("fixed") + std::to_string(fixedCounter)].get_object(); } else { PMUDeclObj = (*PMURegisterDeclarations)[pmuName]["programmable"].get_object(); } auto& myPMUConfigs = fixed ? curPMUConfigs[pmuName].fixed : curPMUConfigs[pmuName].programmable; simdjson::dom::object MSRObject; auto setMSRValue = [&setConfig,&MSRObject,&config,&myPMUConfigs](const string & valueStr) { const auto value = read_number(valueStr.c_str()); const auto position = int64_t(MSRObject["Position"]); // update the first event setConfig(myPMUConfigs.empty() ? config : myPMUConfigs.front(), MSRObject, value, position); // update the current as well for display setConfig(config, MSRObject, value, position); }; for (const auto & registerKeyValue : PMUDeclObj) { // cout << "Setting " << registerKeyValue.key << " : " << registerKeyValue.value << "\n"; simdjson::dom::object fieldDescriptionObj = registerKeyValue.value; // cout << " config: " << uint64_t(fieldDescriptionObj["Config"]) << "\n"; // cout << " Position: " << uint64_t(fieldDescriptionObj["Position"]) << "\n"; const std::string fieldNameStr{ registerKeyValue.key.begin(), registerKeyValue.key.end() }; if (fieldNameStr == "MSRIndex") { string fieldValueStr = EventMap::getField(eventStr, fieldNameStr); // cout << "MSR field " << fieldNameStr << " value is " << fieldValueStr << " (" << read_number(fieldValueStr.c_str()) << ") offcore=" << offcore << "\n"; lowerCase(fieldValueStr); if (fieldValueStr == "0" || fieldValueStr == "0x00") { continue; } auto MSRIndexStr = fieldValueStr; if (offcore) { const auto MSRIndexes = split(MSRIndexStr, ','); if (offcoreEventIndex >= MSRIndexes.size()) { std::cerr << "ERROR: too many offcore events specified (max is " << MSRIndexes.size() << "). Ignoring " << fullEventStr << " event\n"; return AddEventStatus::OK; } MSRIndexStr = MSRIndexes[offcoreEventIndex]; } // cout << " MSR field " << fieldNameStr << " value is " << MSRIndexStr << " (" << read_number(MSRIndexStr.c_str()) << ") offcore=" << offcore << "\n"; MSRObject = registerKeyValue.value[MSRIndexStr]; const string msrValueStr = EventMap::getField(eventStr, "MSRValue"); setMSRValue(msrValueStr); continue; } const int64_t position = int64_t(fieldDescriptionObj["Position"]); if (position == -1) { continue; // field ignored } if (!EventMap::isField(eventStr, fieldNameStr)) { // cerr << fieldNameStr << " not found\n"; if (fieldDescriptionObj["DefaultValue"].error() == NO_SUCH_FIELD) { cerr << "ERROR: DefaultValue not provided for field \"" << fieldNameStr << "\" in " << path << "\n"; return AddEventStatus::Failed; } else { const auto cfg = uint64_t(fieldDescriptionObj["Config"]); if (cfg >= config.first.size()) throw std::runtime_error("Config field value is out of bounds"); config.first[cfg] |= uint64_t(fieldDescriptionObj["DefaultValue"]) << position; } } else { std::string fieldValueStr = EventMap::getField(eventStr, fieldNameStr); fieldValueStr.erase(std::remove(fieldValueStr.begin(), fieldValueStr.end(), '\"'), fieldValueStr.end()); if (offcore && fieldNameStr == "EventCode") { const auto offcoreCodes = split(fieldValueStr,','); if (offcoreEventIndex >= offcoreCodes.size()) { std::cerr << "ERROR: too many offcore events specified (max is " << offcoreCodes.size() << "). Ignoring " << fullEventStr << " event\n"; return AddEventStatus::OK; } fieldValueStr = offcoreCodes[offcoreEventIndex]; } // cout << " field " << fieldNameStr << " value is " << fieldValueStr << " (" << read_number(fieldValueStr.c_str()) << ") offcore=" << offcore << "\n"; setConfig(config, fieldDescriptionObj, read_number(fieldValueStr.c_str()), position); } } auto setField = [&PMUDeclObj, &config, &setConfig](const char* field, const uint64 value) { const auto pos = int64_t(PMUDeclObj[field]["Position"]); setConfig(config, PMUDeclObj[field], value, pos); }; std::regex CounterMaskRegex("c(0x[0-9a-fA-F]+|[[:digit:]]+)"); std::regex UmaskRegex("u(0x[0-9a-fA-F]+|[[:digit:]]+)"); std::regex EdgeDetectRegex("e(0x[0-9a-fA-F]+|[[:digit:]]+)"); std::regex AnyThreadRegex("amt(0x[0-9a-fA-F]+|[[:digit:]]+)"); std::regex InvertRegex("i(0x[0-9a-fA-F]+|[[:digit:]]+)"); while (mod != EventTokens.end()) { const auto assignment = split(*mod, '='); if (*mod == "SUP") { setField("User", 0); setField("OS", 1); } else if (*mod == "USER") { setField("User", 1); setField("OS", 0); } else if (*mod == "tx") { setField("InTX", 1); } else if (*mod == "cp") { setField("InTXCheckpointed", 1); } else if (*mod == "percore") { unsupported(); return AddEventStatus::OK; } else if (*mod == "perf_metrics") { setField("PerfMetrics", 1); } else if (std::regex_match(mod->c_str(), CounterMaskRegex)) { // Counter Mask modifier const std::string CounterMaskStr{ mod->begin() + 1, mod->end() }; setField("CounterMask", read_number(CounterMaskStr.c_str())); } else if (std::regex_match(mod->c_str(), EdgeDetectRegex)) { // Edge Detect modifier const std::string Str{ mod->begin() + 1, mod->end() }; setField("EdgeDetect", read_number(Str.c_str())); } else if (std::regex_match(mod->c_str(), AnyThreadRegex)) { // AnyThread modifier const std::string Str{ mod->begin() + 1, mod->end() }; setField("AnyThread", read_number(Str.c_str())); } else if (std::regex_match(mod->c_str(), InvertRegex)) { // Invert modifier const std::string Str{ mod->begin() + 1, mod->end() }; setField("Invert", read_number(Str.c_str())); } else if (std::regex_match(mod->c_str(), UmaskRegex)) { // UMask modifier const std::string Str{ mod->begin() + 1, mod->end() }; setField("UMask", read_number(Str.c_str())); } else if (assignment.size() == 2 && assignment[0] == "request") { unsupported(); return AddEventStatus::OK; } else if (assignment.size() == 2 && assignment[0] == "response") { unsupported(); return AddEventStatus::OK; } else if (assignment.size() == 2 && assignment[0] == "filter0") { setField("Filter0", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "filter1") { setField("Filter1", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "opc") { setField("OPC", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "nc") { setField("NC", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "isoc") { setField("ISOC", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "state") { setField("State", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "t") { setField("Threshold", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "tid") { setField("TIDEnable", 1); setField("TID", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "umask_ext") { setField("UMaskExt", read_number(assignment[1].c_str())); } else if (assignment.size() == 2 && assignment[0] == "ocr_msr_val") { setMSRValue(assignment[1]); } else { unsupported(); return AddEventStatus::Failed; } ++mod; } if (offcore) { ++offcoreEventIndex; } myPMUConfigs.push_back(config); } catch (std::exception& e) { cerr << "Error while setting a register field for event " << fullEventStr << " : " << e.what() << "\n"; EventMap::print_event(eventStr); return AddEventStatus::Failed; } } /* for (const auto& keyValue : eventObj) { cout << keyValue.key << " : " << keyValue.value << "\n"; } */ printEvent(pmuName, fixed, config); return AddEventStatus::OK; } #endif AddEventStatus addEvent(PCM::RawPMUConfigs & curPMUConfigs, string eventStr) { if (eventStr.empty()) { return AddEventStatus::OK; } #ifdef PCM_SIMDJSON_AVAILABLE if (eventStr.find('/') == string::npos) { return addEventFromDB(curPMUConfigs, eventStr); } #endif PCM::RawEventConfig config = { {0,0,0,0,0}, "" }; const auto typeConfig = split(eventStr, '/'); if (typeConfig.size() < 2) { #ifndef PCM_SIMDJSON_AVAILABLE cerr << "WARNING: pcm-raw is compiled without simdjson library (check cmake output). Collecting events by names from json event lists is not supported.\n"; #endif cerr << "ERROR: wrong syntax in event description \"" << eventStr << "\"\n"; return AddEventStatus::Failed; } auto pmuName = typeConfig[0]; if (pmuName.empty()) { pmuName = "core"; } const auto configStr = typeConfig[1]; if (configStr.empty()) { cerr << "ERROR: empty config description in event description \"" << eventStr << "\"\n"; return AddEventStatus::Failed; } if (pmuName == "core" || pmuName == "atom") { config = initCoreConfig(); } const auto configArray = split(configStr, ','); bool fixed = false; std::string lookup; auto pmtAddRecord = [&lookup, &pmuName, &config](const std::vector & records) -> AddEventStatus { if (pmuName == "pmt") { if (records.empty()) { cerr << "ERROR: lookup \"" << lookup << "\" not found in PMT telemetry database\n"; return AddEventStatus::Failed; } if (records.size() > 1) { cerr << "ERROR: lookup \"" << lookup << "\" is ambiguous in PMT telemetry database\n\n"; for (const auto & record : records) { cerr << " "; record.print(cerr); cerr << "\n"; } return AddEventStatus::Failed; } config.second = records[0].fullName; assert(records.size() == 1); config.first[PCM::PMTEventPosition::UID] = records[0].uid; config.first[PCM::PMTEventPosition::offset] = records[0].qWordOffset; config.first[PCM::PMTEventPosition::type] = (records[0].sampleType == "Snapshot") ? PCM::MSRType::Static : PCM::MSRType::Freerun; config.first[PCM::PMTEventPosition::lsb] = records[0].lsb; config.first[PCM::PMTEventPosition::msb] = records[0].msb; } return AddEventStatus::OK; }; for (const auto & item : configArray) { if (match(item, "config=", &config.first[0])) { // matched and initialized config 0 } else if (match(item, "config1=", &config.first[1])) { // matched and initialized config 1 } else if (match(item, "config2=", &config.first[2])) { // matched and initialized config 2 } else if (match(item, "config3=", &config.first[3])) { // matched and initialized config 3 } else if (match(item, "config4=", &config.first[4])) { // matched and initialized config 4 } else if (match(item, "config5=", &config.first[5])) { // matched and initialized config 5 } else if (match(item, "width=", &config.first[PCM::PCICFGEventPosition::width])) { // matched and initialized config 5 (width) } else if (pcm_sscanf(item) >> s_expect("name=") >> setw(255) >> config.second) { // matched and initialized name if (check_for_injections(config.second)) return AddEventStatus::Failed; } else if (pcm_sscanf(item) >> s_expect("lookup=") >> setw(255) >> lookup) { if (pmtAddRecord(telemDB.lookup(lookup)) != AddEventStatus::OK) return AddEventStatus::Failed; } else if (pcm_sscanf(item) >> s_expect("ilookup=") >> setw(255) >> lookup) { if (pmtAddRecord(telemDB.ilookup(lookup)) != AddEventStatus::OK) return AddEventStatus::Failed; } else if (item == "fixed") { fixed = true; } else { cerr << "ERROR: unknown token " << item << " in event description \"" << eventStr << "\"\n"; return AddEventStatus::Failed; } } printEvent(pmuName, fixed, config); if (fixed == false && tooManyEvents(pmuName, curPMUConfigs[pmuName].programmable.size(), eventStr)) { return AddEventStatus::FailedTooManyEvents; } if (fixed) curPMUConfigs[pmuName].fixed.push_back(config); else curPMUConfigs[pmuName].programmable.push_back(config); return AddEventStatus::OK; } bool addEvents(std::vector& PMUConfigs, string fn) { std::ifstream in(fn); std::string line, item; if (!in.is_open()) { cerr << "ERROR: File " << fn << " can't be open. \n"; return false; } PCM::RawPMUConfigs curConfig; auto doFinishGroup = [&curConfig, &PMUConfigs]() { if (!curConfig.empty()) { cerr << "Adding new group \n"; PMUConfigs.push_back(curConfig); curConfig.clear(); } }; while (std::getline(in, line)) { if (line.empty() || line[0] == '#') { continue; } const auto last = line[line.size() - 1]; bool finishGroup = false; if (last == ',') { line.resize(line.size() - 1); } else if (last == ';') { line.resize(line.size() - 1); finishGroup = true; } const auto status = addEvent(curConfig, line); switch (status) { case AddEventStatus::Failed: return false; case AddEventStatus::FailedTooManyEvents: cerr << "Failed to add event due to a too large group. Trying to split the event group.\n"; doFinishGroup(); if (addEvent(curConfig, line) != AddEventStatus::OK) { return false; } break; case AddEventStatus::OK: // all is fine break; } if (finishGroup) { doFinishGroup(); } } in.close(); doFinishGroup(); return true; } bool show_partial_core_output = false; bitset ycores; bool flushLine = false; bool transpose = false; bool extendPrintout = false; bool singleHeader = false; std::string separator = ","; const std::string jsonSeparator = "\":"; bool sampleSeparator = false; bool outputToJson = false; struct PrintOffset { const std::string entry; int start; int end; }; std::vector printOffsets; std::vector printedBlocks; int getPrintOffsetIdx(const std::string &value) { for (size_t i = 0 ; i < printOffsets.size() ; i++) { if (printOffsets[i].entry == value) return (int)i; } return -1; } void printNewLine(const CsvOutputType outputType) { if (outputType == Data) cout << "\n"; else if (outputType == Json) cout << "}\n"; } void printRowBeginCSV(const std::string & EventName, const CoreCounterState & BeforeState, const CoreCounterState & AfterState, PCM* m) { printDateForCSV(CsvOutputType::Data, separator); cout << EventName << separator << (1000ULL * getInvariantTSC(BeforeState, AfterState)) / m->getNominalFrequency() << separator << getInvariantTSC(BeforeState, AfterState); } void printRowBeginJson(const std::string & EventName, const CoreCounterState & BeforeState, const CoreCounterState & AfterState, PCM* m) { cout << "{\""; printDateForJson(separator, jsonSeparator); cout << "Event" << jsonSeparator << "\"" << EventName << "\"" << separator << "ms" << jsonSeparator << (1000ULL * getInvariantTSC(BeforeState, AfterState)) / m->getNominalFrequency() << separator << "InvariantTSC" << jsonSeparator << getInvariantTSC(BeforeState, AfterState); } void printRowBegin(const std::string & EventName, const CoreCounterState & BeforeState, const CoreCounterState & AfterState, PCM* m, const CsvOutputType outputType, PrintOffset& printOffset) { if (outputType == Data) { printRowBeginCSV(EventName, BeforeState, AfterState, m); for (int i = 0 ; i < printOffset.start ; i++) std::cout << separator; } else if (outputType == Json) { printRowBeginJson(EventName, BeforeState, AfterState, m); } } template void printRow(const std::string & EventName, MetricFunc metricFunc, const std::vector& BeforeState, const std::vector& AfterState, PCM* m, const CsvOutputType outputType, PrintOffset& printOffset, const pcm::TopologyEntry::CoreType & coreType, const std::string & pmuType) { printRowBegin(EventName, BeforeState[0], AfterState[0], m, outputType, printOffset); for (uint32 core = 0; core < m->getNumCores(); ++core) { if (extendPrintout && m->isHybrid() && m->getCoreType(core) != coreType) { continue; } if (!(show_partial_core_output && ycores.test(core) == false)) { if (outputType == Header1) { cout << separator << "SKT" << m->getSocketId(core) << "CORE" << core; printOffset.end++; } else if (outputType == Header2) cout << separator << pmuType; else if (outputType == Data) { cout << separator; if (m->isHybrid() == false || m->getCoreType(core) == coreType) { cout << metricFunc(BeforeState[core], AfterState[core]); } } else if (outputType == Header21) { cout << separator << pmuType << "_SKT" << m->getSocketId(core) << "_CORE" << core; printOffset.end++; } else if (outputType == Json) { cout << separator << pmuType << "_SKT" << m->getSocketId(core) << "_CORE" << core << jsonSeparator; if (m->isHybrid() == false || m->getCoreType(core) == coreType) { cout << metricFunc(BeforeState[core], AfterState[core]); } } else assert(!"unknown output type"); } } printNewLine(outputType); }; typedef uint64 (*UncoreMetricFunc)(const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after); typedef uint64(*UncoreFixedMetricFunc)(const uint32 u, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after); uint64 nullFixedMetricFunc(const uint32, const ServerUncoreCounterState&, const ServerUncoreCounterState&) { return ~0ULL; } const char* fixedCoreEventNames[] = { "InstructionsRetired" , "Cycles", "RefCycles", "TopDownSlots" }; const char* topdownEventNames[] = { "PERF_METRICS.FRONTEND_BOUND" , "PERF_METRICS.BAD_SPECULATION", "PERF_METRICS.BACKEND_BOUND", "PERF_METRICS.RETIRING", "PERF_METRICS.HEAVY_OPERATIONS", "PERF_METRICS.BRANCH_MISPREDICTS", "PERF_METRICS.FETCH_LATENCY", "PERF_METRICS.MEMORY_BOUND"}; constexpr uint32 PerfMetricsConfig = 2; constexpr uint64 PerfMetricsMask = 1ULL; constexpr uint64 maxPerfMetricsValue = 255ULL; const char * getTypeString(uint64 typeID) { switch (typeID) { case PCM::MSRType::Freerun: return "freerun"; case PCM::MSRType::Static: return "static"; } return "unknownType"; } std::string getMSREventString(const uint64 & index, const std::string & type, const PCM::MSRType & msrType) { std::stringstream c; c << type << "/MSR 0x" << std::hex << index << "/" << getTypeString(msrType); return c.str(); } std::string getTPMIEventString(const PCM::RawEventEncoding & eventEnc, const std::string& type) { std::stringstream c; c << type << "/TPMI_ID 0x" << std::hex << eventEnc[PCM::TPMIEventPosition::ID] << "/offset 0x" << eventEnc[PCM::TPMIEventPosition::offset] << "/" << getTypeString(eventEnc[PCM::TPMIEventPosition::type]); return c.str(); } std::string getPCICFGEventString(const PCM::RawEventEncoding & eventEnc, const std::string& type) { std::stringstream c; c << type << "/deviceID 0x" << std::hex << eventEnc[PCM::PCICFGEventPosition::deviceID] << "/offset 0x" << eventEnc[PCM::PCICFGEventPosition::offset] << "/width 0x" << eventEnc[PCM::PCICFGEventPosition::width] << "/" << getTypeString(eventEnc[PCM::PCICFGEventPosition::type]); return c.str(); } std::string getMMIOEventString(const PCM::RawEventEncoding& eventEnc, const std::string& type) { std::stringstream c; c << type << "/deviceID 0x" << std::hex << eventEnc[PCM::MMIOEventPosition::deviceID] << "/offset 0x" << eventEnc[PCM::MMIOEventPosition::offset] << "/membar_bits1 0x" << eventEnc[PCM::MMIOEventPosition::membar_bits1] << "/membar_bits2 0x" << eventEnc[PCM::MMIOEventPosition::membar_bits2] << "/width 0x" << eventEnc[PCM::MMIOEventPosition::width] << "/" << getTypeString(eventEnc[PCM::MMIOEventPosition::type]); return c.str(); } std::string getPMTEventString(const PCM::RawEventEncoding& eventEnc, const std::string& type) { std::stringstream c; c << type << "/UID 0x" << std::hex << eventEnc[PCM::PMTEventPosition::UID] << "/offset 0x" << eventEnc[PCM::PMTEventPosition::offset] << "/lsb 0x" << eventEnc[PCM::PMTEventPosition::lsb] << "/msb 0x" << eventEnc[PCM::PMTEventPosition::msb] << "/" << getTypeString(eventEnc[PCM::PMTEventPosition::type]); return c.str(); } typedef std::string(*getEventStringFunc)(const PCM::RawEventEncoding& eventEnc, const std::string& type); typedef std::vector(getEventFunc)(const PCM::RawEventEncoding& eventEnc, const SystemCounterState& before, const SystemCounterState& after); enum MSRScope { Thread, Package }; uint32 numTMAEvents(PCM* m) { return (m->isHWTMAL2Supported() ? 8 : 4); } uint32 pmu_type = PCM::INVALID_PMU_ID; void printTransposed(const PCM::RawPMUConfigs& curPMUConfigs, PCM* m, SystemCounterState& SysBeforeState, SystemCounterState& SysAfterState, vector& BeforeState, vector& AfterState, vector& BeforeUncoreState, vector& AfterUncoreState, vector& BeforeSocketState, vector& AfterSocketState, const CsvOutputType outputType, const bool& isLastGroup) { const bool is_header = (outputType == Header1 || outputType == Header2 || outputType == Header21); for (const auto & typeEvents : curPMUConfigs) { bool is_header_printed = false; const auto& type = typeEvents.first; const auto& events = typeEvents.second.programmable; const auto& fixedEvents = typeEvents.second.fixed; PrintOffset printOffset{type, 0, 0}; const auto print_idx = getPrintOffsetIdx(type); if (outputType == Header1 || outputType == Header21) { if (print_idx != -1) continue; // header already printed else { printOffset.start = (printOffsets.empty()) ? 0 : printOffsets.back().end; printOffset.end = printOffset.start; } } else if (outputType == Header2) { if (std::find(printedBlocks.begin(), printedBlocks.end(), type) != printedBlocks.end()) continue; printedBlocks.push_back(type); } else if (outputType == Data) { assert(printOffsets.empty() || print_idx >= 0); printOffset.start = (printOffsets.empty()) ? 0 : printOffsets[print_idx].start; printOffset.end = (printOffsets.empty()) ? 0 : printOffsets[print_idx].end; } auto printUncoreRows = [&](UncoreMetricFunc metricFunc, const uint32 maxUnit, const std::string &miscName = std::string(""), UncoreFixedMetricFunc fixedMetricFunc = nullFixedMetricFunc) { if (fixedEvents.size()) { printRowBegin(miscName, BeforeState[0], AfterState[0], m, outputType, printOffset); for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 u = 0; u < maxUnit; ++u) { if (outputType == Header1) { cout << separator << "SKT" << s << miscName << u; printOffset.end++; } else if (outputType == Header2) cout << separator << miscName ; else if (outputType == Data) cout << separator << fixedMetricFunc(u, BeforeUncoreState[s], AfterUncoreState[s]); else if (outputType == Header21) { cout << separator << type << "_SKT" << s << "_" << miscName << u; printOffset.end++; } else if (outputType == Json) { cout << separator << type << "_SKT" << s << "_" << miscName << u << jsonSeparator << fixedMetricFunc(u, BeforeUncoreState[s], AfterUncoreState[s]); } else assert(!"unknown output type"); } } if (is_header) is_header_printed = true; printNewLine(outputType); } uint32 i = 0; for (auto& event : events) { const std::string name = (event.second.empty()) ? (type + "Event" + std::to_string(i)) : event.second; if (is_header && is_header_printed) break; printRowBegin(name, BeforeState[0], AfterState[0], m, outputType, printOffset); for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 u = 0; u < maxUnit; ++u) { if (outputType == Header1) { cout << separator << "SKT_" << s << miscName << u; printOffset.end++; } else if (outputType == Header2) { cout << separator << miscName ; } else if (outputType == Data) { assert(metricFunc); cout << separator << metricFunc(u, i, BeforeUncoreState[s], AfterUncoreState[s]); } else if (outputType == Header21) { cout << separator << type << "_SKT" << s << "_" << miscName << u; printOffset.end++; } else if (outputType == Json) { assert(metricFunc); cout << separator << type << "_SKT" << s << "_" << miscName << u << jsonSeparator << metricFunc(u, i, BeforeUncoreState[s], AfterUncoreState[s]); } else { assert(!"unknown output type"); } } } if (is_header) is_header_printed = true; ++i; printNewLine(outputType); } }; auto printMSRRows = [&](const MSRScope& scope) { auto printMSR = [&](const PCM::RawEventConfig& event) -> bool { const auto index = event.first[PCM::MSREventPosition::index]; const auto msrType = (PCM::MSRType)event.first[PCM::MSREventPosition::type]; const std::string name = (event.second.empty()) ? getMSREventString(index, type, msrType) : event.second; if (is_header && is_header_printed) return false; printRowBegin(name, BeforeState[0], AfterState[0], m, outputType, printOffset); switch (scope) { case MSRScope::Package: for (uint32 s = 0; s < m->getNumSockets(); ++s) { if (outputType == Header1) { cout << separator << "SKT" << s ; printOffset.end++; } else if (outputType == Header2) { cout << separator << type ; } else if (outputType == Data) { cout << separator << getMSREvent(index, msrType, BeforeSocketState[s], AfterSocketState[s]); } else if (outputType == Header21) { cout << separator << type << "_SKT" << s ; printOffset.end++; } else if (outputType == Json) { cout << separator << type << "_SKT" << s << jsonSeparator << getMSREvent(index, msrType, BeforeSocketState[s], AfterSocketState[s]); } else { assert(!"unknown output type"); } } break; case MSRScope::Thread: for (uint32 core = 0; core < m->getNumCores(); ++core) { if (outputType == Header1) { cout << separator << "SKT" << m->getSocketId(core) << "CORE" << core; printOffset.end++; } else if (outputType == Header2) { cout << separator << type ; } else if (outputType == Data) { cout << separator << getMSREvent(index, msrType, BeforeState[core], AfterState[core]); } else if (outputType == Header21) { cout << separator << type << "_SKT" << m->getSocketId(core) << "_CORE" << core; printOffset.end++; } else if (outputType == Json) { cout << separator << type << "_SKT" << m->getSocketId(core) << "_CORE" << core << jsonSeparator << getMSREvent(index, msrType, BeforeState[core], AfterState[core]); } else { assert(!"unknown output type"); } } break; } if (is_header) is_header_printed = true; printNewLine(outputType); return true; }; for (const auto& event : events) { if (!printMSR(event)) { break; } } for (const auto& event : fixedEvents) { if (!printMSR(event)) { break; } } }; auto printCores = [&](const pcm::TopologyEntry::CoreType & coreType) { typedef uint64(*FuncType) (const CoreCounterState& before, const CoreCounterState& after); static FuncType funcFixed[] = { [](const CoreCounterState& before, const CoreCounterState& after) { return getInstructionsRetired(before, after); }, [](const CoreCounterState& before, const CoreCounterState& after) { return getCycles(before, after); }, [](const CoreCounterState& before, const CoreCounterState& after) { return getRefCycles(before, after); }, [](const CoreCounterState& before, const CoreCounterState& after) { return getAllSlotsRaw(before, after); } }; static FuncType funcTopDown[] = { [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getFrontendBound(before, after) * maxPerfMetricsValue); }, [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getBadSpeculation(before, after) * maxPerfMetricsValue); }, [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getBackendBound(before, after) * maxPerfMetricsValue); }, [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getRetiring(before, after) * maxPerfMetricsValue); }, // "PERF_METRICS.HEAVY_OPERATIONS" : [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getHeavyOperationsBound(before, after) * maxPerfMetricsValue); }, // "PERF_METRICS.BRANCH_MISPREDICTS" : [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getBranchMispredictionBound(before, after) * maxPerfMetricsValue); }, // "PERF_METRICS.FETCH_LATENCY" : [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getFetchLatencyBound(before, after) * maxPerfMetricsValue); }, // "PERF_METRICS.MEMORY_BOUND" : [](const CoreCounterState& before, const CoreCounterState& after) { return uint64(getMemoryBound(before, after) * maxPerfMetricsValue); } }; for (const auto& event : fixedEvents) { for (uint32 cnt = 0; cnt < 4; ++cnt) { if (extract_bits(event.first[0], 4U * cnt, 1U + 4U * cnt)) { if (is_header && is_header_printed) break; printRow(event.second.empty() ? fixedCoreEventNames[cnt] : event.second, funcFixed[cnt], BeforeState, AfterState, m, outputType, printOffset, coreType, type); if (is_header) is_header_printed = true; if (cnt == 3 && (event.first[PerfMetricsConfig] & PerfMetricsMask)) { for (uint32 t = 0; t < numTMAEvents(m); ++t) { printRow(topdownEventNames[t], funcTopDown[t], BeforeState, AfterState, m, outputType, printOffset, coreType, type); } } } } } uint32 i = 0; for (const auto& event : events) { if (is_header && is_header_printed) break; const std::string name = (event.second.empty()) ? (type + "Event" + std::to_string(i)) : event.second; printRow(name, [&i](const CoreCounterState& before, const CoreCounterState& after) { return getNumberOfCustomEvents(i, before, after); }, BeforeState, AfterState, m, outputType, printOffset, coreType, type); ++i; if (is_header) is_header_printed = true; } }; auto printRegisterRows = [&](getEventStringFunc getEventString, getEventFunc getEvent) { auto printRegister = [&](const PCM::RawEventConfig& event) -> bool { const std::string name = (event.second.empty()) ? getEventString(event.first, type) : event.second; const auto values = getEvent(event.first, SysBeforeState, SysAfterState); if (is_header && is_header_printed) return false; printRowBegin(name, BeforeState[0], AfterState[0], m, outputType, printOffset); for (size_t r = 0; r < values.size(); ++r) { if (outputType == Header1) { cout << separator << "SYSTEM_" << r; printOffset.end++; } else if (outputType == Header2) { cout << separator << type; } else if (outputType == Data) { cout << separator << values[r]; } else if (outputType == Header21) { cout << separator << type << "_SYSTEM_" << r; printOffset.end++; } else if (outputType == Json) { cout << separator << type << "_SYSTEM_" << r << jsonSeparator << values[r]; } else { assert(!"unknown output type"); } } if (is_header) is_header_printed = true; printNewLine(outputType); return true; }; for (const auto& event : events) { if (!printRegister(event)) { break; } } for (const auto& event : fixedEvents) { if (!printRegister(event)) { break; } } }; if (type == "core") { printCores(pcm::TopologyEntry::Core); } else if (type == "atom") { printCores(pcm::TopologyEntry::Atom); } else if (type == "thread_msr") { printMSRRows(MSRScope::Thread); } else if (type == "package_msr") { printMSRRows(MSRScope::Package); } else if (type == "tpmi") { printRegisterRows(getTPMIEventString, getTPMIEvent); } else if (type == "pcicfg") { printRegisterRows(getPCICFGEventString, getPCICFGEvent); } else if (type == "mmio") { printRegisterRows(getMMIOEventString, getMMIOEvent); } else if (type == "pmt") { printRegisterRows(getPMTEventString, getPMTEvent); } else if (type == "m3upi") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getQPILinksPerSocket(), "LINK"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getQPILinksPerSocket(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getM3UPICounter(u, i, before, after); }, (uint32) m->getQPILinksPerSocket(), "LINK"); }); } else if (type == "xpi" || type == "upi" || type == "qpi") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getQPILinksPerSocket(), "LINK"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getQPILinksPerSocket(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getXPICounter(u, i, before, after); }, (uint32) m->getQPILinksPerSocket(), "LINK"); }); } else if (type == "imc") { const std::string fixedEventName = (fixedEvents.empty() == false && fixedEvents[0].second.empty() == false) ? fixedEvents[0].second : "DRAMClocks"; choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMCChannelsPerSocket(), "CHAN"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMCChannelsPerSocket(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getMCCounter(u, i, before, after); }, (uint32)m->getMCChannelsPerSocket(), fixedEventName, [](const uint32 u, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getDRAMClocks(u, before, after); }); }); } else if (type == "m2m") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMCPerSocket(), "MC"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMCPerSocket(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getM2MCounter(u, i, before, after); }, (uint32)m->getMCPerSocket(), "MC"); }); } else if (type == "ha") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMCPerSocket(), "HA"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMCPerSocket(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getHACounter(u, i, before, after); }, (uint32)m->getMCPerSocket(), "HA"); }); } else if (type == "pcu") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(PCM::PCU_PMU_ID), "P"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(PCM::PCU_PMU_ID), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getUncoreCounter(PCM::PCU_PMU_ID, u, i, before, after); }, 1U, ""); }); } else if (type == "ubox") { choose(outputType, [&]() { printUncoreRows(nullptr, 1U, ""); }, [&]() { printUncoreRows(nullptr, 1U, type); }, [&]() { printUncoreRows([](const uint32, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getUncoreCounter(PCM::UBOX_PMU_ID, 0, i, before, after); }, 1U, "UncoreClocks", [](const uint32, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getUncoreClocks(before, after); }); }); } else if (type == "cbo" || type == "cha") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(PCM::CBO_PMU_ID), "C"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(PCM::CBO_PMU_ID), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getUncoreCounter(PCM::CBO_PMU_ID, u, i, before, after); }, (uint32)m->getMaxNumOfUncorePMUs(PCM::CBO_PMU_ID), "C"); }); } else if (type == "mdf") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(PCM::MDF_PMU_ID), "MDF"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(PCM::MDF_PMU_ID), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getUncoreCounter(PCM::MDF_PMU_ID, u, i, before, after); }, (uint32)m->getMaxNumOfUncorePMUs(PCM::MDF_PMU_ID), "MDF"); }); } else if (type == "irp") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfIIOStacks(), "IRP"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfIIOStacks(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getIRPCounter(u, i, before, after); }, (uint32)m->getMaxNumOfIIOStacks(), "IRP"); }); } else if (type == "iio") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfIIOStacks(), "IIO"); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfIIOStacks(), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getIIOCounter(u, i, before, after); }, (uint32)m->getMaxNumOfIIOStacks(), "IIO"); }); } else if (type == "cxlcm") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) ServerUncoreCounterState::maxCXLPorts, "CXLCM"); }, [&]() { printUncoreRows(nullptr, (uint32) ServerUncoreCounterState::maxCXLPorts, type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getCXLCMCounter(u, i, before, after); }, ServerUncoreCounterState::maxCXLPorts, "CXLCM"); }); } else if (type == "cxldp") { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) ServerUncoreCounterState::maxCXLPorts, "CXLDP"); }, [&]() { printUncoreRows(nullptr, (uint32) ServerUncoreCounterState::maxCXLPorts, type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getCXLDPCounter(u, i, before, after); }, ServerUncoreCounterState::maxCXLPorts, "CXLDP"); }); } else if ((pmu_type = m->strToUncorePMUID(type)) != PCM::INVALID_PMU_ID) { choose(outputType, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(pmu_type), type); }, [&]() { printUncoreRows(nullptr, (uint32) m->getMaxNumOfUncorePMUs(pmu_type), type); }, [&]() { printUncoreRows([](const uint32 u, const uint32 i, const ServerUncoreCounterState& before, const ServerUncoreCounterState& after) { return getUncoreCounter(pmu_type, u, i, before, after); }, (uint32)m->getMaxNumOfUncorePMUs(pmu_type), type); }); } else { std::cerr << "ERROR: unrecognized PMU type \"" << type << "\"\n"; } if (outputType == Header1 || outputType == Header21) printOffsets.push_back(printOffset); } if (sampleSeparator) { cout << (isLastGroup? "==========\n" : "----------\n"); } if (flushLine) { cout.flush(); } } void print(const PCM::RawPMUConfigs& curPMUConfigs, PCM* m, SystemCounterState& SysBeforeState, SystemCounterState& SysAfterState, vector& BeforeState, vector& AfterState, vector& BeforeUncoreState, vector& AfterUncoreState, vector& BeforeSocketState, vector& AfterSocketState, const CsvOutputType outputType) { printDateForCSV(outputType, separator); if (BeforeState.size() > 0 && AfterState.size() > 0) { choose(outputType, []() { cout << separator; }, []() { cout << "ms" << separator; }, [&]() { cout << (1000ULL * getInvariantTSC(BeforeState[0], AfterState[0])) / m->getNominalFrequency() << separator; }); } for (auto& typeEvents : curPMUConfigs) { const auto & type = typeEvents.first; const auto & events = typeEvents.second.programmable; const auto & fixedEvents = typeEvents.second.fixed; auto printCores = [&m, &BeforeState, &AfterState, &type, &fixedEvents, &events, &outputType](const pcm::TopologyEntry::CoreType & coreType) { for (uint32 core = 0; core < m->getNumCores(); ++core) { if (show_partial_core_output && ycores.test(core) == false) continue; if (m->isHybrid() && m->getCoreType(core) != coreType) { continue; } const uint64 fixedCtrValues[] = { getInstructionsRetired(BeforeState[core], AfterState[core]), getCycles(BeforeState[core], AfterState[core]), getRefCycles(BeforeState[core], AfterState[core]), getAllSlotsRaw(BeforeState[core], AfterState[core]) }; const uint64 topdownCtrValues[] = { uint64(getFrontendBound(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getBadSpeculation(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getBackendBound(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getRetiring(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getHeavyOperationsBound(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getBranchMispredictionBound(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getFetchLatencyBound(BeforeState[core], AfterState[core]) * maxPerfMetricsValue), uint64(getMemoryBound(BeforeState[core], AfterState[core]) * maxPerfMetricsValue) }; for (const auto& event : fixedEvents) { auto print = [&](const std::string& metric, const uint64 value) { choose(outputType, [m, core]() { cout << "SKT" << m->getSocketId(core) << "CORE" << core << separator; }, [&metric]() { cout << metric << separator; }, [&value]() { cout << value << separator; }); }; for (uint32 cnt = 0; cnt < 4; ++cnt) { if (extract_bits(event.first[0], 4U * cnt, 1U + 4U * cnt)) { print(event.second.empty() ? fixedCoreEventNames[cnt] : event.second, fixedCtrValues[cnt]); if (cnt == 3 && (event.first[PerfMetricsConfig] & PerfMetricsMask)) { for (uint32 t = 0; t < numTMAEvents(m); ++t) { print(topdownEventNames[t], topdownCtrValues[t]); } } } } } int i = 0; for (auto& event : events) { choose(outputType, [m, core]() { cout << "SKT" << m->getSocketId(core) << "CORE" << core << separator; }, [&event, &i, &type]() { if (event.second.empty()) cout << type << "Event" << i << separator; else cout << event.second << separator; }, [&]() { cout << getNumberOfCustomEvents(i, BeforeState[core], AfterState[core]) << separator; }); ++i; } } }; auto printRegisters = [&](getEventStringFunc getEventString, getEventFunc getEvent) { auto printOneRegister = [&](const PCM::RawEventConfig& event) { const auto values = getEvent(event.first, SysBeforeState, SysAfterState); for (size_t r = 0; r < values.size(); ++r) { choose(outputType, [&r]() { cout << "SYSTEM_" << r << separator; }, [&]() { if (event.second.empty()) cout << getEventString(event.first, type) << separator; else cout << event.second << separator; }, [&]() { cout << values[r] << separator; }); } }; for (const auto& event : events) { printOneRegister(event); } for (const auto& event : fixedEvents) { printOneRegister(event); } }; if (type == "core") { printCores(pcm::TopologyEntry::Core); } else if (type == "atom") { printCores(pcm::TopologyEntry::Atom); } else if (type == "m3upi") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 l = 0; l < m->getQPILinksPerSocket(); ++l) { int i = 0; for (auto& event : events) { choose(outputType, [s, l]() { cout << "SKT" << s << "LINK" << l << separator; }, [&event, &i]() { if (event.second.empty()) cout << "M3UPIEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getM3UPICounter(l, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "xpi" || type == "upi" || type == "qpi") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 l = 0; l < m->getQPILinksPerSocket(); ++l) { int i = 0; for (auto& event : events) { choose(outputType, [s, l]() { cout << "SKT" << s << "LINK" << l << separator; }, [&event, &i]() { if (event.second.empty()) cout << "XPIEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getXPICounter(l, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "imc") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 ch = 0; ch < m->getMCChannelsPerSocket(); ++ch) { if (fixedEvents.size()) { choose(outputType, [s, ch]() { cout << "SKT" << s << "CHAN" << ch << separator; }, [&fixedEvents]() { cout << "DRAMClocks" << fixedEvents[0].second << separator; }, [&]() { cout << getDRAMClocks(ch, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); } int i = 0; for (auto& event : events) { choose(outputType, [s, ch]() { cout << "SKT" << s << "CHAN" << ch << separator; }, [&event, &i]() { if (event.second.empty()) cout << "IMCEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getMCCounter(ch, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "m2m") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 mc = 0; mc < m->getMCPerSocket(); ++mc) { int i = 0; for (auto& event : events) { choose(outputType, [s, mc]() { cout << "SKT" << s << "MC" << mc << separator; }, [&event, &i]() { if (event.second.empty()) cout << "M2MEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getM2MCounter(mc, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "ha") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 mc = 0; mc < m->getMCPerSocket(); ++mc) { int i = 0; for (auto& event : events) { choose(outputType, [s, mc]() { cout << "SKT" << s << "HA" << mc << separator; }, [&event, &i]() { if (event.second.empty()) cout << "HAEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getHACounter(mc, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "pcu") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 u = 0; u < m->getMaxNumOfUncorePMUs(PCM::PCU_PMU_ID); ++u) { int i = 0; for (auto& event : events) { choose(outputType, [s, u]() { cout << "SKT" << s << "P" << u << separator; }, [&event, &i]() { if (event.second.empty()) cout << "PCUEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getUncoreCounter(PCM::PCU_PMU_ID, u, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "package_msr") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { auto printMSR = [&](const PCM::RawEventConfig & event) { const auto index = event.first[PCM::MSREventPosition::index]; const auto msrType = (PCM::MSRType)event.first[PCM::MSREventPosition::type]; choose(outputType, [s]() { cout << "SKT" << s << separator; }, [&]() { if (event.second.empty()) cout << getMSREventString(index, type, msrType) << separator; else cout << event.second << separator; }, [&]() { cout << getMSREvent(index, msrType, BeforeSocketState[s], AfterSocketState[s]) << separator; }); }; for (const auto& event : events) { printMSR(event); } for (const auto& event : fixedEvents) { printMSR(event); } } } else if (type == "thread_msr") { for (uint32 core = 0; core < m->getNumCores(); ++core) { auto printMSR = [&](const PCM::RawEventConfig& event) { const auto index = event.first[PCM::MSREventPosition::index]; const auto msrType = (PCM::MSRType)event.first[PCM::MSREventPosition::type]; choose(outputType, [m, core]() { cout << "SKT" << m->getSocketId(core) << "CORE" << core << separator; }, [&]() { if (event.second.empty()) cout << getMSREventString(index, type, msrType) << separator; else cout << event.second << separator; }, [&]() { cout << getMSREvent(index, msrType, BeforeState[core], AfterState[core]) << separator; }); }; for (const auto& event : events) { printMSR(event); } for (const auto& event : fixedEvents) { printMSR(event); } } } else if (type == "pcicfg") { printRegisters(getPCICFGEventString, getPCICFGEvent); } else if (type == "tpmi") { printRegisters(getTPMIEventString, getTPMIEvent); } else if (type == "mmio") { printRegisters(getMMIOEventString, getMMIOEvent); } else if (type == "pmt") { printRegisters(getPMTEventString, getPMTEvent); } else if (type == "ubox") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { if (fixedEvents.size()) { choose(outputType, [s]() { cout << "SKT" << s << separator; }, [&fixedEvents]() { cout << "UncoreClocks" << fixedEvents[0].second << separator; }, [&]() { cout << getUncoreClocks(BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); } int i = 0; for (auto& event : events) { choose(outputType, [s]() { cout << "SKT" << s << separator; }, [&event, &i]() { if (event.second.empty()) cout << "UBOXEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getUncoreCounter(PCM::UBOX_PMU_ID, 0, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } else if (type == "cbo" || type == "cha") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 cbo = 0; cbo < m->getMaxNumOfUncorePMUs(PCM::CBO_PMU_ID); ++cbo) { int i = 0; for (auto& event : events) { choose(outputType, [s, cbo]() { cout << "SKT" << s << "C" << cbo << separator; }, [&event, &i]() { if (event.second.empty()) cout << "CBOEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getUncoreCounter(PCM::CBO_PMU_ID, cbo, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "mdf") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 mdf = 0; mdf < m->getMaxNumOfUncorePMUs(PCM::MDF_PMU_ID); ++mdf) { int i = 0; for (auto& event : events) { choose(outputType, [s, mdf]() { cout << "SKT" << s << "MDF" << mdf << separator; }, [&event, &i]() { if (event.second.empty()) cout << "MDFEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getUncoreCounter(PCM::MDF_PMU_ID, mdf, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "irp") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 stack = 0; stack < m->getMaxNumOfIIOStacks(); ++stack) { int i = 0; for (auto& event : events) { choose(outputType, [s, stack]() { cout << "SKT" << s << "IRP" << stack << separator; }, [&event, &i]() { if (event.second.empty()) cout << "IRPEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getIRPCounter(stack, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "iio") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 stack = 0; stack < m->getMaxNumOfIIOStacks(); ++stack) { int i = 0; for (auto& event : events) { choose(outputType, [s, stack]() { cout << "SKT" << s << "IIO" << stack << separator; }, [&event, &i]() { if (event.second.empty()) cout << "IIOEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getIIOCounter(stack, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "cxlcm") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 p = 0; p < ServerUncoreCounterState::maxCXLPorts; ++p) { int i = 0; for (auto& event : events) { choose(outputType, [s, p]() { cout << "SKT" << s << "CXLCM" << p << separator; }, [&event, &i]() { if (event.second.empty()) cout << "CXLCMEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getCXLCMCounter(p, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if (type == "cxldp") { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 p = 0; p < ServerUncoreCounterState::maxCXLPorts; ++p) { int i = 0; for (auto& event : events) { choose(outputType, [s, p]() { cout << "SKT" << s << "CXLDP" << p << separator; }, [&event, &i]() { if (event.second.empty()) cout << "CXLDPEvent" << i << separator; else cout << event.second << separator; }, [&]() { cout << getCXLDPCounter(p, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else if ((pmu_type = m->strToUncorePMUID(type)) != PCM::INVALID_PMU_ID) { for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 unit = 0; unit < m->getMaxNumOfUncorePMUs(pmu_type); ++unit) { int i = 0; for (auto& event : events) { choose(outputType, [s, unit, &type]() { cout << "SKT" << s << type << unit << separator; }, [&event, &i, &type]() { if (event.second.empty()) cout << type << "Event" << i << separator; else cout << event.second << separator; }, [&]() { cout << getUncoreCounter(pmu_type, unit, i, BeforeUncoreState[s], AfterUncoreState[s]) << separator; }); ++i; } } } } else { std::cerr << "ERROR: unrecognized PMU type \"" << type << "\"\n"; } } if (flushLine) { cout << endl; } else { cout << "\n"; } } void printAll(const PCM::RawPMUConfigs& curPMUConfigs, PCM * m, SystemCounterState & SysBeforeState, SystemCounterState& SysAfterState, vector& BeforeState, vector& AfterState, vector& BeforeUncoreState, vector& AfterUncoreState, vector& BeforeSocketState, vector& AfterSocketState, std::vector& PMUConfigs, const bool & isLastGroup) { if (outputToJson) { printTransposed(curPMUConfigs, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Json, isLastGroup); return; } static bool displayHeader = true; if (!extendPrintout && transpose) displayHeader = false; if (transpose) { if (displayHeader) { // Need to go through all possible print on first run to form header. if (singleHeader) { // merge header 2 and 1, print and get all offsets cout << "Date" << separator << "Time" << separator << "Event" << separator; cout << "ms" << separator << "InvariantTSC"; for (auto &config : PMUConfigs) printTransposed(config, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Header21, isLastGroup); } else { // print 2 headers in 2 rows for (int i = 0 ; i < 4 ; i++) cout << separator; // print header_1 and get all offsets for (auto &config : PMUConfigs) printTransposed(config, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Header1, isLastGroup); cout << endl; // print header_2 cout << "Date" << separator << "Time" << separator << "Event" << separator; cout << "ms" << separator << "InvariantTSC"; for (auto &config : PMUConfigs) printTransposed(config, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Header2, isLastGroup); } cout << endl; } printTransposed(curPMUConfigs, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Data, isLastGroup); } else { if (displayHeader) { print(curPMUConfigs, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Header1); print(curPMUConfigs, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Header2); } print(curPMUConfigs, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, Data); } displayHeader = false; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); parseParam(argc, argv, "out", [](const char* p) { const string filename{ p }; if (!filename.empty()) { PCM::setOutput(filename, true); } }); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; std::cout.rdbuf(&nullStream1); std::cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); set_real_time_priority(true); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor: Raw Event Monitoring Utility \n"; cerr << "\n"; std::vector PMUConfigs(1); double delay = -1.0; int pid{-1}; char* sysCmd = NULL; char** sysArgv = NULL; MainLoop mainLoop; string program = string(argv[0]); bool forceRTMAbortMode = false; bool reset_pmu = false; PCM* m = PCM::getInstance(); telemDB.loadFromXML("Intel-PMT"); parsePID(argc, argv, pid); #ifdef PCM_SIMDJSON_AVAILABLE parseParam(argc, argv, "ep", [](const char* p) { eventFileLocationPrefix = p;}); #endif if (argc > 1) do { argv++; argc--; string arg_value; if (*argv == nullptr) { continue; } else if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { if (!arg_value.empty()) { m->setOutput(arg_value); } } else if (check_argument_equals(*argv, {"-json", "/json"})) { separator = ",\""; outputToJson = true; } else if (extract_argument_value(*argv, {"-json", "/json"}, arg_value)) { separator = ",\""; outputToJson = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (isPIDOption(argv)) { argv++; argc--; continue; } else if (check_argument_equals(*argv, {"-reset", "/reset", "-r"})) { reset_pmu = true; continue; } else if (check_argument_equals(*argv, {"-tr", "/tr"})) { transpose = true; continue; } else if (check_argument_equals(*argv, {"-ext", "/ext"})) { extendPrintout = true; continue; } else if (check_argument_equals(*argv, {"-single-header", "/single-header"})) { singleHeader = true; continue; } else if (check_argument_equals(*argv, {"-l"})) { std::cout.imbue(std::locale("")); separator = "\t"; continue; } else if (check_argument_equals(*argv, {"-tab"})) { separator = "\t"; continue; } else if (check_argument_equals(*argv, {"--yescores", "-yc", "/yc"})) { argv++; argc--; show_partial_core_output = true; if (*argv == NULL) { cerr << "Error: --yescores requires additional argument.\n"; exit(EXIT_FAILURE); } std::stringstream ss(*argv); while (ss.good()) { string s; int core_id; std::getline(ss, s, ','); if (s.empty()) continue; core_id = atoi(s.c_str()); if (core_id > MAX_CORES) { cerr << "Core ID:" << core_id << " exceed maximum range " << MAX_CORES << ", program abort\n"; exit(EXIT_FAILURE); } ycores.set(atoi(s.c_str()), true); } if (m->getNumCores() > MAX_CORES) { cerr << "Error: --yescores option is enabled, but #define MAX_CORES " << MAX_CORES << " is less than m->getNumCores() = " << m->getNumCores() << "\n"; cerr << "There is a potential to crash the system. Please increase MAX_CORES to at least " << m->getNumCores() << " and re-enable this option.\n"; exit(EXIT_FAILURE); } continue; } else if (check_argument_equals(*argv, {"-out", "/out"})) { argv++; argc--; continue; } else if (check_argument_equals(*argv, {"-ep", "/ep"})) { argv++; argc--; continue; } else if (check_argument_equals(*argv, {"-edp", "/edp"})) { sampleSeparator = true; defaultDelay = 0.2; transpose = true; m->printDetailedSystemTopology(); continue; } else if (check_argument_equals(*argv, {"-el", "/el"})) { argv++; argc--; const auto p = *argv; if (p == nullptr) { cerr << "ERROR: no parameter value provided for 'el' option\n"; exit(EXIT_FAILURE); } else if (addEvents(PMUConfigs, p) == false) { exit(EXIT_FAILURE); } continue; } else if (check_argument_equals(*argv, {"-e"})) { argv++; argc--; const auto p = *argv; if (p == nullptr) { cerr << "ERROR: no parameter value provided for 'e' option\n"; exit(EXIT_FAILURE); } else if (addEvent(PMUConfigs[0], p) != AddEventStatus::OK) { exit(EXIT_FAILURE); } continue; } else if (CheckAndForceRTMAbortMode(*argv, m)) { forceRTMAbortMode = true; continue; } else if (check_argument_equals(*argv, {"-f", "/f"})) { flushLine = true; continue; } else if (check_argument_equals(*argv, {"-s", "/s"})) { sampleSeparator = true; continue; } else if (check_argument_equals(*argv, {"-v", "/v"})) { verbose = true; continue; } else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } while (argc > 1); // end of command line parsing loop if (reset_pmu) { cerr << "\n Resetting PMU configuration\n"; m->resetPMU(); } print_cpu_details(); size_t nGroups = 0; for (const auto& group : PMUConfigs) { if (!group.empty()) ++nGroups; } for (size_t i = 0; i < PMUConfigs.size(); ++i) { if (PMUConfigs[i].empty()) { // erase empty groups PMUConfigs.erase(PMUConfigs.begin() + i); --i; } } assert(PMUConfigs.size() == nGroups); if (nGroups == 0) { cerr << "No events specified. Exiting.\n"; exit(EXIT_FAILURE); } cerr << "Collecting " << nGroups << " event group(s)\n"; if (nGroups > 1) { transpose = true; cerr << "Enforcing transposed event output because the number of event groups > 1\n"; } print_pid_collection_message(pid); auto programPMUs = [&m, &pid](const PCM::RawPMUConfigs & config) { if (verbose) { for (const auto & pmuConfig: config) { for (const auto & e : pmuConfig.second.fixed) { cerr << "Programming " << pmuConfig.first << " fixed event: " << e.second << "\n"; } for (const auto & e : pmuConfig.second.programmable) { cerr << "Programming " << pmuConfig.first << " programmable event: " << e.second << "\n"; } } } PCM::ErrorCode status = m->program(config, !verbose, pid); m->checkError(status); }; SystemCounterState SysBeforeState, SysAfterState; vector BeforeState, AfterState; vector BeforeSocketState, AfterSocketState; vector BeforeUncoreState, AfterUncoreState; BeforeUncoreState.resize(m->getNumSockets()); AfterUncoreState.resize(m->getNumSockets()); if ((sysCmd != NULL) && (delay <= 0.0)) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (delay <= 0.0) delay = defaultDelay; cerr << "Update every " << delay << " seconds\n"; std::cout.precision(2); std::cout << std::fixed; if (sysCmd != NULL) { MySystem(sysCmd, sysArgv); } auto programAndReadGroup = [&](const PCM::RawPMUConfigs & group) { if (forceRTMAbortMode) { m->enableForceRTMAbortMode(true); } programPMUs(group); m->globalFreezeUncoreCounters(); m->getAllCounterStates(SysBeforeState, BeforeSocketState, BeforeState); for (uint32 s = 0; s < m->getNumSockets(); ++s) { BeforeUncoreState[s] = m->getServerUncoreCounterState(s); } m->globalUnfreezeUncoreCounters(); }; if (nGroups == 1) { programAndReadGroup(PMUConfigs[0]); } mainLoop([&]() { size_t groupNr = 0; for (const auto & group : PMUConfigs) { ++groupNr; if (nGroups > 1) { programAndReadGroup(group); } calibratedSleep(delay, sysCmd, mainLoop, m); m->globalFreezeUncoreCounters(); m->getAllCounterStates(SysAfterState, AfterSocketState, AfterState); for (uint32 s = 0; s < m->getNumSockets(); ++s) { AfterUncoreState[s] = m->getServerUncoreCounterState(s); } m->globalUnfreezeUncoreCounters(); //cout << "Time elapsed: " << dec << fixed << AfterTime - BeforeTime << " ms\n"; //cout << "Called sleep function for " << dec << fixed << delay_ms << " ms\n"; printAll(group, m, SysBeforeState, SysAfterState, BeforeState, AfterState, BeforeUncoreState, AfterUncoreState, BeforeSocketState, AfterSocketState, PMUConfigs, groupNr == nGroups); if (nGroups == 1) { std::swap(BeforeState, AfterState); std::swap(BeforeSocketState, AfterSocketState); std::swap(BeforeUncoreState, AfterUncoreState); std::swap(SysBeforeState, SysAfterState); } } if (m->isBlocked()) { // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } pcm-202502/src/pcm-sensor-server.cpp000066400000000000000000005124721475730356400173030ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2016-2022, Intel Corporation // Use port allocated for PCM in prometheus: // https://github.com/prometheus/prometheus/wiki/Default-port-allocations constexpr unsigned int DEFAULT_HTTP_PORT = 9738; #if defined (USE_SSL) constexpr unsigned int DEFAULT_HTTPS_PORT = DEFAULT_HTTP_PORT; #endif #include "pcm-accel-common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpucounters.h" #include "debug.h" #include "topology.h" #include "dashboard.h" #define PCMWebServerVersion "0.1" #if defined (USE_SSL) # include # include # define CERT_FILE_NAME "./server.pem" # define KEY_FILE_NAME "./server.pem" #endif // USE_SSL #include #include #include "threadpool.h" using namespace pcm; std::string const HTTP_EOL( "\r\n" ); std::string const PROM_EOL( "\n" ); class Indent { public: explicit Indent( std::string const & is = std::string(" ") ) : indstr_(is), indent_(""), len_(0), indstrlen_(is.length()) { } Indent() = delete; Indent(Indent const &) = default; Indent & operator = (Indent const &) = delete; ~Indent() = default; friend std::stringstream& operator <<( std::stringstream& stream, Indent in ); void printIndentationString(std::stringstream& s) { s << indent_; } // We only need post inc und pre dec Indent& operator--() { if ( len_ > 0 ) --len_; else throw std::runtime_error("Indent: Decremented len_ too often!"); indent_.erase( len_ * indstrlen_ ); return *this; } Indent operator++(int) { Indent copy( *this ); ++len_; indent_ += indstr_; // add one more indstr_ return copy; } private: std::string indstr_; std::string indent_; size_t len_; size_t const indstrlen_; }; std::stringstream& operator <<( std::stringstream& stream, Indent in ) { in.printIndentationString( stream ); return stream; } class datetime { public: datetime() { std::time_t t = std::time( nullptr ); const auto gt = std::gmtime( &t ); if (gt == nullptr) throw std::runtime_error("std::gmtime returned nullptr"); now = *gt; } datetime( std::tm t ) : now( t ) {} ~datetime() = default; datetime( datetime const& ) = default; datetime & operator = ( datetime const& ) = default; public: void printDateTimeString( std::ostream& os ) const { std::stringstream str(""); char timeBuffer[64]; std::fill(timeBuffer, timeBuffer + 64, 0); str.imbue( std::locale::classic() ); if ( strftime( timeBuffer, 63, "%a, %d %b %Y %T GMT", &now ) ) str << timeBuffer; else throw std::runtime_error("Error writing to timeBuffer, too small?"); os << str.str(); } std::string toString() const { std::stringstream str(""); char timeBuffer[64]; std::fill(timeBuffer, timeBuffer + 64, 0); str.imbue( std::locale::classic() ); if ( strftime( timeBuffer, 63, "%a, %d %b %Y %T GMT", &now ) ) str << timeBuffer; else throw std::runtime_error("Error writing to timeBuffer, too small?"); return str.str(); } private: std::tm now; }; std::ostream& operator<<( std::ostream& os, datetime const & dt ) { dt.printDateTimeString(os); return os; } class date { public: date() { now = std::time(nullptr); } ~date() = default; date( date const& ) = default; date & operator = ( date const& ) = default; public: void printDate( std::ostream& os ) const { char buf[64]; const auto t = std::localtime(&now); assert(t); std::strftime( buf, 64, "%F", t); os << buf; } private: std::time_t now; }; std::ostream& operator<<( std::ostream& os, date const & d ) { d.printDate(os); return os; } /* Not used right now std::string read_ndctl_info( std::ofstream& logfile ) { int pipes[2]; if ( pipe( pipes ) == -1 ) { logfile << date() << ": ERROR Cannot create pipe, errno = " << errno << ", strerror: " << strerror(errno) << ". Exit 50.\n"; exit(50); } std::stringstream ndctl; if ( fork() == 0 ) { // child, writes to pipe, close read-end close( pipes[0] ); dup2( pipes[1], fileno(stdout) ); execl( "/usr/bin/ndctl", "ndctl", "list", (char*)NULL ); } else { // parent, reads from pipe, close write-end close( pipes[1] ); char buf[2049]; std::fill(buf, buf + 2049, 0); ssize_t len = 0; while( (len = read( pipes[0], buf, 2048 )) > 0 ) { buf[len] = '\0'; ndctl << buf; } close( pipes[0] ); if ( len < 0 ) { logfile << ": ERROR Read from ndctl pipe failed. errno = " << errno << ". strerror(errno) = " << strerror(errno) << ". Exit 52.\n"; exit(52); } logfile << datetime() << ": INFO Read JSON from ndctl pipe: " << ndctl.str() << ".\n"; } return ndctl.str(); } */ class HTTPServer; class SignalHandler { public: static SignalHandler* getInstance() { static SignalHandler instance; return &instance; } static void handleSignal( int signum ); void setSocket( int s ) { networkSocket_ = s; } void setHTTPServer( HTTPServer* hs ) { httpServer_ = hs; } void ignoreSignal( int signum ) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigaction( signum, &sa, 0 ); } void installHandler( void (*handler)(int), int signum ) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = handler; sa.sa_flags = 0; sigaction( signum, &sa, 0 ); } SignalHandler( SignalHandler const & ) = delete; void operator=( SignalHandler const & ) = delete; ~SignalHandler() = default; private: SignalHandler() = default; private: static int networkSocket_; static HTTPServer* httpServer_; }; int SignalHandler::networkSocket_ = 0; HTTPServer* SignalHandler::httpServer_ = nullptr; class JSONPrinter : Visitor { public: enum LineEndAction { NewLineOnly = 0, DelimiterOnly, DelimiterAndNewLine, LineEndAction_Spare = 255 }; JSONPrinter( std::pair,std::shared_ptr> aggregatorPair ) : indentation(" "), aggPair_( aggregatorPair ) { if ( nullptr == aggPair_.second.get() ) throw std::runtime_error("BUG: second Aggregator == nullptr!"); DBG( 2, "Constructor: before=", std::hex, aggPair_.first.get(), ", after=", std::hex, aggPair_.second.get() ); } JSONPrinter( JSONPrinter const & ) = delete; JSONPrinter & operator = ( JSONPrinter const & ) = delete; JSONPrinter() = delete; CoreCounterState const getCoreCounter( std::shared_ptr ag, uint32 tid ) const { CoreCounterState ccs; if ( nullptr == ag.get() ) return ccs; return std::move( ag->coreCounterStates()[tid] ); } SocketCounterState const getSocketCounter( std::shared_ptr ag, uint32 sid ) const { SocketCounterState socs; if ( nullptr == ag.get() ) return socs; return std::move( ag->socketCounterStates()[sid] ); } SystemCounterState getSystemCounter( std::shared_ptr ag ) const { SystemCounterState sycs; if ( nullptr == ag.get() ) return sycs; return std::move( ag->systemCounterState() ); } virtual void dispatch( HyperThread* ht ) override { printCounter( "Object", "HyperThread" ); printCounter( "Thread ID", ht->threadID() ); printCounter( "OS ID", ht->osID() ); CoreCounterState before = getCoreCounter( aggPair_.first, ht->osID() ); CoreCounterState after = getCoreCounter( aggPair_.second, ht->osID() ); printBasicCounterState( before, after ); } virtual void dispatch( ServerUncore* su ) override { printCounter( "Object", "ServerUncore" ); SocketCounterState before = getSocketCounter( aggPair_.first, su->socketID() ); SocketCounterState after = getSocketCounter( aggPair_.second, su->socketID() ); printUncoreCounterState( before, after ); } virtual void dispatch( ClientUncore* cu) override { printCounter( "Object", "ClientUncore" ); SocketCounterState before = getSocketCounter( aggPair_.first, cu->socketID() ); SocketCounterState after = getSocketCounter( aggPair_.second, cu->socketID() ); printUncoreCounterState( before, after ); } virtual void dispatch( Core* c ) override { printCounter( "Object", "Core" ); auto vec = c->threads(); printCounter( "Number of threads", vec.size() ); startObject( "Threads", BEGIN_LIST ); iterateVectorAndCallAccept( vec ); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_LIST ); // For backward compatibility we use socketUniqueCoreID to create a unique number inside the socket for a core // and introduce HW Core ID as the physical core id inside a module, keep in mind this core id is not unique inside a socket printCounter( "Core ID", c->socketUniqueCoreID() ); printCounter( "HW Core ID", c->coreID() ); printCounter( "Module ID", c->moduleID() ); printCounter( "Tile ID", c->tileID() ); printCounter( "Die ID", c->dieID() ); printCounter( "Die Group ID", c->dieGroupID() ); printCounter( "Socket ID", c->socketID() ); } virtual void dispatch( SystemRoot const & s ) override { using namespace std::chrono; auto interval = duration_cast( aggPair_.second->dispatchedAt() - aggPair_.first->dispatchedAt() ).count(); startObject( "", BEGIN_OBJECT ); printCounter( "Interval us", interval ); printCounter( "Object", "SystemRoot" ); auto vec = s.sockets(); printCounter( "Number of sockets", vec.size() ); startObject( "Sockets", BEGIN_LIST ); iterateVectorAndCallAccept( vec ); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_LIST ); SystemCounterState before = getSystemCounter( aggPair_.first ); SystemCounterState after = getSystemCounter( aggPair_.second ); PCM * pcm = PCM::getInstance(); if (pcm->getAccel()!=ACCEL_NOCONFIG){ startObject ("Accelerators",BEGIN_OBJECT); printAccelCounterState(before,after); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_OBJECT ); } startObject( "QPI/UPI Links", BEGIN_OBJECT ); printSystemCounterState( before, after ); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_OBJECT ); startObject( "Core Aggregate", BEGIN_OBJECT ); printBasicCounterState( before, after ); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_OBJECT ); startObject( "Uncore Aggregate", BEGIN_OBJECT ); printUncoreCounterState( before, after ); endObject( JSONPrinter::LineEndAction::NewLineOnly, END_OBJECT ); endObject( JSONPrinter::LineEndAction::NewLineOnly, END_OBJECT ); } virtual void dispatch( Socket* s ) override { printCounter( "Object", "Socket" ); printCounter( "Socket ID", s->socketID() ); auto vec = s->cores(); printCounter( "Number of cores", vec.size() ); startObject( "Cores", BEGIN_LIST ); iterateVectorAndCallAccept( vec ); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_LIST ); startObject( "Uncore", BEGIN_OBJECT ); s->uncore()->accept( *this ); endObject( JSONPrinter::LineEndAction::DelimiterAndNewLine, END_OBJECT ); startObject( "Core Aggregate", BEGIN_OBJECT ); SocketCounterState before = getSocketCounter( aggPair_.first, s->socketID() ); SocketCounterState after = getSocketCounter( aggPair_.second, s->socketID() ); printBasicCounterState( before, after ); endObject( JSONPrinter::LineEndAction::NewLineOnly, END_OBJECT ); } std::string str( void ) { return ss.str(); } private: void printBasicCounterState( BasicCounterState const& before, BasicCounterState const& after ) { startObject( "Core Counters", BEGIN_OBJECT ); printCounter( "Instructions Retired Any", getInstructionsRetired( before, after ) ); printCounter( "Clock Unhalted Thread", getCycles ( before, after ) ); printCounter( "Clock Unhalted Ref", getRefCycles ( before, after ) ); printCounter( "L3 Cache Misses", getL3CacheMisses ( before, after ) ); printCounter( "L3 Cache Hits", getL3CacheHits ( before, after ) ); printCounter( "L2 Cache Misses", getL2CacheMisses ( before, after ) ); printCounter( "L2 Cache Hits", getL2CacheHits ( before, after ) ); printCounter( "L3 Cache Occupancy", getL3CacheOccupancy ( after ) ); printCounter( "Invariant TSC", getInvariantTSC ( before, after ) ); printCounter( "SMI Count", getSMICount ( before, after ) ); printCounter( "Core Frequency", getActiveAverageFrequency ( before, after ) ); printCounter( "Frontend Bound", int(100. * getFrontendBound(before, after)) ); printCounter( "Bad Speculation", int(100. * getBadSpeculation(before, after)) ); printCounter( "Backend Bound", int(100. * getBackendBound(before, after)) ); printCounter( "Retiring", int(100. * getRetiring(before, after)) ); printCounter( "Fetch Latency Bound", int(100. * getFetchLatencyBound(before, after)) ); printCounter( "Fetch Bandwidth Bound", int(100. * getFetchBandwidthBound(before, after)) ); printCounter( "Branch Misprediction Bound", int(100. * getBranchMispredictionBound(before, after)) ); printCounter( "Machine Clears Bound", int(100. * getMachineClearsBound(before, after)) ); printCounter( "Memory Bound", int(100. * getMemoryBound(before, after)) ); printCounter( "Core Bound", int(100. * getCoreBound(before, after)) ); printCounter( "Heavy Operations Bound", int(100. * getHeavyOperationsBound(before, after)) ); printCounter( "Light Operations Bound", int(100. * getLightOperationsBound(before, after)) ); endObject( JSONPrinter::DelimiterAndNewLine, END_OBJECT ); //DBG( 2, "Invariant TSC before=", before.InvariantTSC, ", after=", after.InvariantTSC, ", difference=", after.InvariantTSC-before.InvariantTSC ); startObject( "Energy Counters", BEGIN_OBJECT ); printCounter( "Thermal Headroom", after.getThermalHeadroom() ); uint32 i = 0; for ( ; i < ( PCM::MAX_C_STATE ); ++i ) { std::stringstream s; s << "CStateResidency[" << i << "]"; printCounter( s.str(), getCoreCStateResidency( i, before, after ) ); } // Here i == PCM::MAX_STATE so no need to type so many characters ;-) std::stringstream s; s << "CStateResidency[" << i << "]"; printCounter( s.str(), getCoreCStateResidency( i, before, after ) ); endObject( JSONPrinter::DelimiterAndNewLine, END_OBJECT ); startObject( "Core Memory Bandwidth Counters", BEGIN_OBJECT ); printCounter( "Local Memory Bandwidth", getLocalMemoryBW( before, after ) ); printCounter( "Remote Memory Bandwidth", getRemoteMemoryBW( before, after ) ); endObject( JSONPrinter::NewLineOnly, END_OBJECT ); } void printUncoreCounterState( SocketCounterState const& before, SocketCounterState const& after ) { startObject( "Uncore Counters", BEGIN_OBJECT ); PCM* pcm = PCM::getInstance(); printCounter( "DRAM Writes", getBytesWrittenToMC ( before, after ) ); printCounter( "DRAM Reads", getBytesReadFromMC ( before, after ) ); if(pcm->nearMemoryMetricsAvailable()){ printCounter( "NM HitRate", getNMHitRate ( before, after ) ); printCounter( "NM Hits", getNMHits ( before, after ) ); printCounter( "NM Misses", getNMMisses ( before, after ) ); printCounter( "NM Miss Bw", getNMMissBW ( before, after ) ); } printCounter( "Persistent Memory Writes", getBytesWrittenToPMM ( before, after ) ); printCounter( "Persistent Memory Reads", getBytesReadFromPMM ( before, after ) ); printCounter( "Embedded DRAM Writes", getBytesWrittenToEDC ( before, after ) ); printCounter( "Embedded DRAM Reads", getBytesReadFromEDC ( before, after ) ); printCounter( "Memory Controller IA Requests", getIARequestBytesFromMC( before, after ) ); printCounter( "Memory Controller GT Requests", getGTRequestBytesFromMC( before, after ) ); printCounter( "Memory Controller IO Requests", getIORequestBytesFromMC( before, after ) ); printCounter( "Package Joules Consumed", getConsumedJoules ( before, after ) ); printCounter( "PP0 Joules Consumed", getConsumedJoules ( 0, before, after ) ); printCounter( "PP1 Joules Consumed", getConsumedJoules ( 1, before, after ) ); printCounter( "DRAM Joules Consumed", getDRAMConsumedJoules ( before, after ) ); auto uncoreFrequencies = getUncoreFrequencies( before, after ); for (size_t i = 0; i < uncoreFrequencies.size(); ++i) { printCounter( std::string("Uncore Frequency Die ") + std::to_string(i), uncoreFrequencies[i]); } const auto localRatio = int(100.* getLocalMemoryRequestRatio(before, after)); printCounter( "Local Memory Request Ratio", int(100.* getLocalMemoryRequestRatio(before, after)) ); printCounter( "Remote Memory Request Ratio", 100 - localRatio); uint32 i = 0; for ( ; i < ( PCM::MAX_C_STATE ); ++i ) { std::stringstream s; s << "CStateResidency[" << i << "]"; printCounter( s.str(), getPackageCStateResidency( i, before, after ) ); } // Here i == PCM::MAX_STATE so no need to type so many characters ;-) std::stringstream s; s << "CStateResidency[" << i << "]"; printCounter( s.str(), getPackageCStateResidency( i, before, after ) ); endObject( JSONPrinter::NewLineOnly, END_OBJECT ); } void printAccelCounterState( SystemCounterState const& before, SystemCounterState const& after ) { AcceleratorCounterState* accs_ = AcceleratorCounterState::getInstance(); uint32 devs = accs_->getNumOfAccelDevs(); for ( uint32 i=0; i < devs; ++i ) { startObject( std::string( accs_->getAccelCounterName() + " Counters Device " ) + std::to_string( i ), BEGIN_OBJECT ); for(int j=0;jgetNumberOfCounters();j++){ printCounter( accs_->getAccelIndexCounterName(j), accs_->getAccelIndexCounter(i, before, after,j) ); } // debug prints //for(uint32 j=0;jgetNumberOfCounters();j++){ // std::cout<getAccelIndexCounterName(j) << " "<getAccelIndexCounter(i, before, after,j)<getAccelIndexCounterName()<< accs_->getAccelInboundBW (i, before, after ) << " "<< accs_->getAccelOutboundBW (i, before, after ) << " "<getAccelShareWQ_ReqNb (i, before, after ) << " "<getAccelDedicateWQ_ReqNb (i, before, after ) << std::endl; endObject( JSONPrinter::DelimiterAndNewLine, END_OBJECT ); } } void printSystemCounterState( SystemCounterState const& before, SystemCounterState const& after ) { PCM* pcm = PCM::getInstance(); uint32 sockets = pcm->getNumSockets(); uint32 links = pcm->getQPILinksPerSocket(); for ( uint32 i=0; i < sockets; ++i ) { startObject( std::string( "QPI Counters Socket " ) + std::to_string( i ), BEGIN_OBJECT ); printCounter( std::string( "CXL Write Cache" ), getCXLWriteCacheBytes (i, before, after ) ); printCounter( std::string( "CXL Write Mem" ), getCXLWriteMemBytes (i, before, after ) ); for ( uint32 j=0; j < links; ++j ) { printCounter( std::string( "Incoming Data Traffic On Link " ) + std::to_string( j ), getIncomingQPILinkBytes ( i, j, before, after ) ); printCounter( std::string( "Outgoing Data And Non-Data Traffic On Link " ) + std::to_string( j ), getOutgoingQPILinkBytes ( i, j, before, after ) ); printCounter( std::string( "Utilization Incoming Data Traffic On Link " ) + std::to_string( j ), getIncomingQPILinkUtilization( i, j, before, after ) ); printCounter( std::string( "Utilization Outgoing Data And Non-Data Traffic On Link " ) + std::to_string( j ), getOutgoingQPILinkUtilization( i, j, before, after ) ); } endObject( JSONPrinter::DelimiterAndNewLine, END_OBJECT ); } } template void printCounter( std::string const & name, Counter c ); template void iterateVectorAndCallAccept( Vector const& v ); void startObject(std::string const& s, char const ch ) { std::string name; if ( s.size() != 0 ) name = "\"" + s + "\" : "; ss << (indentation++) << name << ch << HTTP_EOL; } void endObject( enum JSONPrinter::LineEndAction lea, char const ch ) { // look 3 chars back, if it is a ',' then delete it. // make read same as write position - 3 std::stringstream::pos_type oldReadPos = ss.tellg(); ss.seekg( -3, std::ios_base::end ); if ( ss.peek() == ',' ) { ss.seekp( ss.tellg() ); // Make write same as read position ss << HTTP_EOL; } ss.seekg( oldReadPos );// Just making sure the readpointer is set back to where it was ss << (--indentation) << ch; if ( lea == LineEndAction::NewLineOnly ) ss << HTTP_EOL; else if ( lea == LineEndAction::DelimiterAndNewLine ) ss << "," << HTTP_EOL; else if ( lea == LineEndAction::DelimiterOnly ) ss << ","; else throw std::runtime_error( "Unknown LineEndAction enum" ); } void insertListDelimiter() { ss << "," << HTTP_EOL; } private: Indent indentation; std::pair,std::shared_ptr> aggPair_; const char BEGIN_OBJECT = '{'; const char END_OBJECT = '}'; const char BEGIN_LIST = '['; const char END_LIST = ']'; }; template void JSONPrinter::printCounter( std::string const & name, Counter c ) { if ( std::is_same::value || std::is_same::value ) ss << indentation << "\"" << name << "\" : \"" << c << "\"," << HTTP_EOL; else ss << indentation << "\"" << name << "\" : " << c << "," << HTTP_EOL; } template void JSONPrinter::iterateVectorAndCallAccept(Vector const& v) { for ( auto* vecElem: v ) { // Inside a list objects are not named startObject( "", BEGIN_OBJECT ); vecElem->accept( *this ); endObject( JSONPrinter::DelimiterAndNewLine, END_OBJECT ); } }; class PrometheusPrinter : Visitor { public: PrometheusPrinter( std::pair,std::shared_ptr> aggregatorPair ) : aggPair_( aggregatorPair ) { if ( nullptr == aggPair_.second.get() ) throw std::runtime_error("BUG: second Aggregator == nullptr!"); DBG( 2, "Constructor: before=", std::hex, aggPair_.first.get(), ", after=", std::hex, aggPair_.second.get() ); } PrometheusPrinter( PrometheusPrinter const & ) = delete; PrometheusPrinter & operator = ( PrometheusPrinter const & ) = delete; PrometheusPrinter() = delete; CoreCounterState const getCoreCounter( std::shared_ptr ag, uint32 tid ) const { CoreCounterState ccs; if ( nullptr == ag.get() ) return ccs; return std::move( ag->coreCounterStates()[tid] ); } SocketCounterState const getSocketCounter( std::shared_ptr ag, uint32 sid ) const { SocketCounterState socs; if ( nullptr == ag.get() ) return socs; return std::move( ag->socketCounterStates()[sid] ); } SystemCounterState getSystemCounter( std::shared_ptr ag ) const { SystemCounterState sycs; if ( nullptr == ag.get() ) return sycs; return std::move( ag->systemCounterState() ); } virtual void dispatch( HyperThread* ht ) override { addToHierarchy( "thread=\"" + std::to_string( ht->threadID() ) + "\"" ); printCounter( "OS ID", ht->osID() ); CoreCounterState before = getCoreCounter( aggPair_.first, ht->osID() ); CoreCounterState after = getCoreCounter( aggPair_.second, ht->osID() ); printBasicCounterState( before, after ); removeFromHierarchy(); } virtual void dispatch( ServerUncore* su ) override { printComment( std::string( "Uncore Counters Socket " ) + std::to_string( su->socketID() ) ); SocketCounterState before = getSocketCounter( aggPair_.first, su->socketID() ); SocketCounterState after = getSocketCounter( aggPair_.second, su->socketID() ); printUncoreCounterState( before, after ); } virtual void dispatch( ClientUncore* cu) override { printComment( std::string( "Uncore Counters Socket " ) + std::to_string( cu->socketID() ) ); SocketCounterState before = getSocketCounter( aggPair_.first, cu->socketID() ); SocketCounterState after = getSocketCounter( aggPair_.second, cu->socketID() ); printUncoreCounterState( before, after ); } virtual void dispatch( Core* c ) override { addToHierarchy( std::string( "core=\"" ) + std::to_string( c->socketUniqueCoreID() ) + "\"" ); auto vec = c->threads(); iterateVectorAndCallAccept( vec ); removeFromHierarchy(); } virtual void dispatch( SystemRoot const & s ) override { using namespace std::chrono; auto interval = duration_cast( aggPair_.second->dispatchedAt() - aggPair_.first->dispatchedAt() ).count(); printCounter( "Measurement Interval in us", interval ); auto vec = s.sockets(); printCounter( "Number of sockets", vec.size() ); iterateVectorAndCallAccept( vec ); SystemCounterState before = getSystemCounter( aggPair_.first ); SystemCounterState after = getSystemCounter( aggPair_.second ); addToHierarchy( "aggregate=\"system\"" ); PCM* pcm = PCM::getInstance(); if (pcm->getAccel()!=ACCEL_NOCONFIG){ printComment( "Accelerator Counters" ); printAccelCounterState(before,after); } if ( pcm->isServerCPU() && pcm->getNumSockets() >= 2 ) { printComment( "UPI/QPI Counters" ); printSystemCounterState( before, after ); } printComment( "Core Counters Aggregate System" ); printBasicCounterState ( before, after ); printComment( "Uncore Counters Aggregate System" ); printUncoreCounterState( before, after ); removeFromHierarchy(); // aggregate=system } virtual void dispatch( Socket* s ) override { addToHierarchy( std::string( "socket=\"" ) + std::to_string( s->socketID() ) + "\"" ); printComment( std::string( "Core Counters Socket " ) + std::to_string( s->socketID() ) ); auto vec = s->cores(); iterateVectorAndCallAccept( vec ); // Uncore writes the comment for the socket uncore counters s->uncore()->accept( *this ); addToHierarchy( "aggregate=\"socket\"" ); printComment( std::string( "Core Counters Aggregate Socket " ) + std::to_string( s->socketID() ) ); SocketCounterState before = getSocketCounter( aggPair_.first, s->socketID() ); SocketCounterState after = getSocketCounter( aggPair_.second, s->socketID() ); printBasicCounterState( before, after ); removeFromHierarchy(); // aggregate=socket removeFromHierarchy(); // socket=x } std::string str( void ) { return ss.str(); } private: void printBasicCounterState( BasicCounterState const& before, BasicCounterState const& after ) { addToHierarchy( "source=\"core\"" ); printCounter( "Instructions Retired Any", getInstructionsRetired( before, after ) ); printCounter( "Clock Unhalted Thread", getCycles ( before, after ) ); printCounter( "Clock Unhalted Ref", getRefCycles ( before, after ) ); printCounter( "L3 Cache Misses", getL3CacheMisses ( before, after ) ); printCounter( "L3 Cache Hits", getL3CacheHits ( before, after ) ); printCounter( "L2 Cache Misses", getL2CacheMisses ( before, after ) ); printCounter( "L2 Cache Hits", getL2CacheHits ( before, after ) ); printCounter( "L3 Cache Occupancy", getL3CacheOccupancy ( after ) ); printCounter( "Invariant TSC", getInvariantTSC ( before, after ) ); printCounter( "SMI Count", getSMICount ( before, after ) ); #if 0 // disabling this metric for a moment due to https://github.com/intel/pcm/issues/789 printCounter( "Core Frequency", getActiveAverageFrequency ( before, after ) ); #endif //DBG( 2, "Invariant TSC before=", before.InvariantTSC, ", after=", after.InvariantTSC, ", difference=", after.InvariantTSC-before.InvariantTSC ); printCounter( "Thermal Headroom", after.getThermalHeadroom() ); uint32 i = 0; for ( ; i <= ( PCM::MAX_C_STATE ); ++i ) { std::stringstream s; s << "index=\"" << i << "\""; addToHierarchy( s.str() ); printCounter( "CStateResidency", getCoreCStateResidency( i, before, after ) ); // need a raw CStateResidency metric because the precision is lost to unacceptable levels when trying // to compute CStateResidency for the last second using the existing CStateResidency metric printCounter( "RawCStateResidency", getCoreCStateResidency( i, after ) ); removeFromHierarchy(); } printCounter( "Local Memory Bandwidth", getLocalMemoryBW( before, after ) ); printCounter( "Remote Memory Bandwidth", getRemoteMemoryBW( before, after ) ); removeFromHierarchy(); } void printUncoreCounterState( SocketCounterState const& before, SocketCounterState const& after ) { PCM* pcm = PCM::getInstance(); addToHierarchy( "source=\"uncore\"" ); printCounter( "DRAM Writes", getBytesWrittenToMC ( before, after ) ); printCounter( "DRAM Reads", getBytesReadFromMC ( before, after ) ); if(pcm->nearMemoryMetricsAvailable()){ printCounter( "NM Hits", getNMHits ( before, after ) ); printCounter( "NM Misses", getNMMisses ( before, after ) ); printCounter( "NM Miss Bw", getNMMissBW ( before, after ) ); printCounter( "NM HitRate", getNMHitRate ( before, after ) ); } printCounter( "Persistent Memory Writes", getBytesWrittenToPMM ( before, after ) ); printCounter( "Persistent Memory Reads", getBytesReadFromPMM ( before, after ) ); printCounter( "Embedded DRAM Writes", getBytesWrittenToEDC ( before, after ) ); printCounter( "Embedded DRAM Reads", getBytesReadFromEDC ( before, after ) ); printCounter( "Memory Controller IA Requests", getIARequestBytesFromMC( before, after ) ); printCounter( "Memory Controller GT Requests", getGTRequestBytesFromMC( before, after ) ); printCounter( "Memory Controller IO Requests", getIORequestBytesFromMC( before, after ) ); printCounter( "Package Joules Consumed", getConsumedJoules ( before, after ) ); printCounter( "PP0 Joules Consumed", getConsumedJoules ( 0, before, after ) ); printCounter( "PP1 Joules Consumed", getConsumedJoules ( 1, before, after ) ); printCounter( "DRAM Joules Consumed", getDRAMConsumedJoules ( before, after ) ); #if 0 // disabling these metrics for a moment due to https://github.com/intel/pcm/issues/789 auto uncoreFrequencies = getUncoreFrequencies( before, after ); for (size_t i = 0; i < uncoreFrequencies.size(); ++i) { printCounter( std::string("Uncore Frequency Die ") + std::to_string(i), uncoreFrequencies[i]); } #endif uint32 i = 0; for ( ; i <= ( PCM::MAX_C_STATE ); ++i ) { std::stringstream s; s << "index=\"" << i << "\""; addToHierarchy( s.str() ); printCounter( "CStateResidency", getPackageCStateResidency( i, before, after ) ); // need a CStateResidency raw metric because the precision is lost to unacceptable levels when trying // to compute CStateResidency for the last second using the existing CStateResidency metric printCounter( "RawCStateResidency", getPackageCStateResidency( i, after ) ); removeFromHierarchy(); } removeFromHierarchy(); } void printAccelCounterState( SystemCounterState const& before, SystemCounterState const& after ) { addToHierarchy( "source=\"accel\"" ); AcceleratorCounterState* accs_ = AcceleratorCounterState::getInstance(); uint32 devs = accs_->getNumOfAccelDevs(); for ( uint32 i=0; i < devs; ++i ) { addToHierarchy( std::string( accs_->getAccelCounterName() + "device=\"" ) + std::to_string( i ) + "\"" ); for(int j=0;jgetNumberOfCounters();j++) { printCounter( accs_->remove_string_inside_use(accs_->getAccelIndexCounterName(j)), accs_->getAccelIndexCounter(i, before, after,j) ); } removeFromHierarchy(); } removeFromHierarchy(); } void printSystemCounterState( SystemCounterState const& before, SystemCounterState const& after ) { addToHierarchy( "source=\"uncore\"" ); PCM* pcm = PCM::getInstance(); uint32 sockets = pcm->getNumSockets(); uint32 links = pcm->getQPILinksPerSocket(); for ( uint32 i=0; i < sockets; ++i ) { addToHierarchy( std::string( "socket=\"" ) + std::to_string( i ) + "\"" ); printCounter( std::string( "CXL Write Cache" ), getCXLWriteCacheBytes (i, before, after ) ); printCounter( std::string( "CXL Write Mem" ), getCXLWriteMemBytes (i, before, after ) ); for ( uint32 j=0; j < links; ++j ) { printCounter( std::string( "Incoming Data Traffic On Link " ) + std::to_string( j ), getIncomingQPILinkBytes ( i, j, before, after ) ); printCounter( std::string( "Outgoing Data And Non-Data Traffic On Link " ) + std::to_string( j ), getOutgoingQPILinkBytes ( i, j, before, after ) ); printCounter( std::string( "Utilization Incoming Data Traffic On Link " ) + std::to_string( j ), getIncomingQPILinkUtilization( i, j, before, after ) ); printCounter( std::string( "Utilization Outgoing Data And Non-Data Traffic On Link " ) + std::to_string( j ), getOutgoingQPILinkUtilization( i, j, before, after ) ); } removeFromHierarchy(); } removeFromHierarchy(); } std::string replaceIllegalCharsWithUnderbar( std::string const& s ) { size_t pos = 0; std::string str(s); while ( ( pos = str.find( '-', pos ) ) != std::string::npos ) { str.replace( pos, 1, "_" ); } pos = 0; while ( ( pos = str.find( ' ', pos ) ) != std::string::npos ) { str.replace( pos, 1, "_" ); } return str; } void addToHierarchy( std::string const& s ) { hierarchy_.push_back( s ); } void removeFromHierarchy() { hierarchy_.pop_back(); } std::string printHierarchy() { std::string s(" "); if (hierarchy_.size() == 0 ) return s; s = "{"; for(const auto & level : hierarchy_ ) { s += level + ','; } s.pop_back(); s += "} "; return s; } template void printCounter( std::string const & name, Counter c ); void printComment( std::string const &comment ) { ss << "# " << comment << PROM_EOL; } template void iterateVectorAndCallAccept( Vector const& v ); private: std::pair,std::shared_ptr> aggPair_; std::vector hierarchy_; }; template void PrometheusPrinter::printCounter( std::string const & name, Counter c ) { ss << replaceIllegalCharsWithUnderbar(name) << printHierarchy() << c << PROM_EOL; } template void PrometheusPrinter::iterateVectorAndCallAccept(Vector const& v) { for ( auto* vecElem: v ) { vecElem->accept( *this ); } }; #if defined (USE_SSL) void closeSSLConnectionAndFD( int fd, SSL* ssl ) { int ret; if ( (ret = SSL_shutdown( ssl )) == 0 ) { DBG( 3, "first shutdown returned: ", ret ); // Call it again when it returns 0, it has sent the notification but not received it back yet if ( (ret = SSL_shutdown( ssl )) != 1 ) // Big trouble but we did all we could. DBG( 3, "Could not shutdown the SSL connection the second time... ret: ", ret ); } ERR_clear_error(); SSL_free( ssl ); // Free the SSL structure to prevent memory leaks // cppcheck-suppress uselessAssignmentPtrArg ssl = nullptr; DBG( 3, "close fd" ); ::close( fd ); } #endif template > class basic_socketbuf : public std::basic_streambuf { public: basic_socketbuf(const basic_socketbuf&) = delete; basic_socketbuf & operator = (const basic_socketbuf&) = delete; using Base = std::basic_streambuf; using char_type = typename Base::char_type; using int_type = typename Base::int_type; using traits_type = typename Base::traits_type; basic_socketbuf( std::string dbg_ = std::string("Server: ") ): socketFD_(0), dbg(dbg_) { // According to http://en.cppreference.com/w/cpp/io/basic_streambuf // epptr and egptr point beyond the buffer, so start + SIZE Base::setp( outputBuffer_, outputBuffer_ + SIZE ); Base::setg( inputBuffer_, inputBuffer_, inputBuffer_ ); // Default timeout of 10 seconds and 0 microseconds timeout_ = { 10, 0 }; #if defined (USE_SSL) // I guess one could say that the instantiation of the ptr in this object will always be 0, i just want this to be explicit for now // cppcheck-suppress uselessAssignmentPtrArg ssl_ = nullptr; #endif } virtual ~basic_socketbuf() { close(); DBG( 3, dbg, "socketbuf destructor finished" ); } int socket() { return socketFD_; } void setSocket( int socketFD ) { socketFD_ = socketFD; if( 0 == socketFD ) // avoid work with 0 socket after closure socket and set value to 0 return; // When receiving the socket descriptor, set the timeout const auto res = setsockopt( socketFD_, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_, sizeof(struct timeval) ); if (res != 0) { std::cerr << "setsockopt failed while setting timeout value, " << strerror( errno ) << "\n"; } } void setTimeout( struct timeval t ) { timeout_ = t; const auto res = setsockopt( socketFD_, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_, sizeof(struct timeval) ); if (res != 0) { std::cerr << "setsockopt failed while setting timeout value, " << strerror( errno ) << "\n"; } } #if defined (USE_SSL) SSL* ssl() { return ssl_; } void setSSL( SSL* ssl ) { if ( nullptr != ssl_ ) throw std::runtime_error( "BUG: You can set the SSL pointer only once" ); if ( nullptr == ssl ) throw std::runtime_error( "BUG: Trying to set a nullptr as ssl" ); ssl_ = ssl; } #endif void close() { basic_socketbuf::sync(); #if defined (USE_SSL) if ( nullptr != ssl_ ) { SSL_shutdown( ssl_ ); ERR_clear_error(); SSL_free( ssl_ ); ssl_ = nullptr; } #endif if ( 0 != socketFD_ ) { DBG( 3, dbg, "close clientsocketFD" ); ::close( socketFD_ ); } } protected: int_type writeToSocket() { size_t bytesToSend; ssize_t bytesSent; bytesToSend = (char*)Base::pptr() - (char*)Base::pbase(); DBG( 3, dbg, "wts: Bytes to send: ", bytesToSend ); #if defined (USE_SSL) if ( nullptr == ssl_ ) { #endif bytesSent= ::send( socketFD_, (void*)outputBuffer_, bytesToSend, MSG_NOSIGNAL ); if ( -1 == bytesSent ) { DBG( 3, "bytesSent == -1: strerror( ", errno, " ): ", strerror( errno ), ", returning eof..." ); return traits_type::eof(); } #if defined (USE_SSL) } else { while( 1 ) { // openSSL has no support for setting the MSG_NOSIGNAL during send // but we ignore sigpipe so we should be fine bytesSent = SSL_write( ssl_, (void*)outputBuffer_, bytesToSend ); DBG( 3, dbg, "wts: SSL_write returned for bytesSent: ", bytesSent ); if ( 0 >= bytesSent ) { int sslError = SSL_get_error( ssl_, bytesSent ); if ( sslError == SSL_ERROR_ZERO_RETURN ) { // TSL/SSL Connection has been closed, the underlying socket may not though return traits_type::eof(); } else { DBG( 3, dbg, "wts: SSL_get_error returned: ", sslError ); ERR_clear_error(); // Clear error because SSL_get_error does not do so switch ( sslError ) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: DBG( 3, dbg, "wts: Want read or write or error none. Trying SSL_write again..."); // retry continue; // Should continue in the while loop and attempt to write again // break; case SSL_ERROR_SYSCALL: DBG( 3, dbg, "wts: errno is: ", errno, " strerror(errno): ", strerror(errno) ); if ( errno == 0 ) return 0; /* fall-through */ case SSL_ERROR_SSL: default: DBG( 3, dbg, "wts: SSL_write, syscall, ssl or default. Returning eof" ); return traits_type::eof(); } } } else { // Valid write break; // out of the while loop } } } #endif Base::pbump( -bytesSent ); return bytesSent; } int sync() override { DBG( 3, dbg, "sync socketFD_: ", socketFD_ ); if ( 0 == socketFD_ ) // Socket is closed already return 0; DBG( 3, dbg, "sync: Calling writeToSocket()" ); int_type ret = writeToSocket(); DBG( 3, dbg, "sync: writeToSocket returned: ", ret ); if ( traits_type::eof() == ret ) return -1; return 0; } virtual int_type overflow( int_type ch ) override { // send data in buffer and reset it if ( traits_type::eof() != ch ) { *Base::pptr() = ch; Base::pbump(1); } int_type bytesWritten = 0; if ( traits_type::eof() == (bytesWritten = writeToSocket()) ) { return traits_type::eof(); } return bytesWritten; // Anything but traits_type::eof() to signal ok. } virtual int_type underflow() override { std::fill(inputBuffer_, inputBuffer_ + SIZE, 0); ssize_t bytesReceived; #if defined (USE_SSL) if ( nullptr == ssl_ ) { #endif DBG( 3, dbg, "Socketbuf: Read from socket:" ); bytesReceived = ::read( socketFD_, static_cast(inputBuffer_), SIZE * sizeof( char_type ) ); if ( 0 == bytesReceived ) { // Client closed the socket normally, we will do the same close(); return traits_type::eof(); } if ( -1 == bytesReceived ) { if ( errno ) DBG( 3, dbg, "Errno: ", errno, ", (", strerror( errno ) , ")" ); close(); Base::setg( nullptr, nullptr, nullptr ); return traits_type::eof(); } DBG( 3, dbg, "Bytes received: ", bytesReceived ); debug::dyn_hex_table_output( 3, std::cout, bytesReceived, inputBuffer_ ); DBG( 3, dbg, "End", std::dec ); #if defined (USE_SSL) } else { bool loopAgain = true; while (loopAgain) { bytesReceived = SSL_read( ssl_, static_cast(inputBuffer_), SIZE * sizeof( char_type ) ); DBG( 3, dbg, "SSL_read: bytesReceived: ", bytesReceived ); if ( 0 >= bytesReceived ) { int sslError = SSL_get_error( ssl_, bytesReceived ); if ( sslError == SSL_ERROR_ZERO_RETURN ) { // TSL/SSL Connection has been closed, the underlying socket may not though throw std::runtime_error( "SSL_read returned SSL_ERROR_ZERO_RETURN, connection was closed" ); } else { DBG( 3, dbg, "SSL_read: sslError: ", sslError ); int err = 0; char buf[256]; err = ERR_get_error(); DBG( 3, dbg, "ERR_get_error(): ", err ); ERR_error_string( err, buf ); DBG( 3, dbg, "ERR_error_string(): ", buf ); ERR_clear_error(); // Clear error because SSL_get_error does not do so //ERR_print_errors_fp(stderr); switch ( sslError ) { case SSL_ERROR_WANT_READ: DBG( 3, "SSL_ERROR_WANT_READ: Errno = ", errno, ", strerror(errno): ", strerror(errno) ); if ( errno == EAGAIN || errno == EWOULDBLOCK ) { DBG( 3, dbg, "Most likely the set timeout, so aborting..." ); close(); Base::setg( nullptr, nullptr, nullptr ); DBG( 3, dbg, "return eof" ); return traits_type::eof(); } /* fall-through */ case SSL_ERROR_WANT_WRITE: // retry loopAgain = true; // Should continue in the while loop and attempt to read again break; case SSL_ERROR_SYSCALL: DBG( 3, "SSL_ERROR_SYSCALL: Errno = ", errno ); if ( errno == EAGAIN || errno == EWOULDBLOCK ) { DBG( 3, dbg, "Most likely the set timeout, so aborting..." ); close(); Base::setg( nullptr, nullptr, nullptr ); DBG( 3, dbg, "return eof" ); return traits_type::eof(); } /* fall-through */ case SSL_ERROR_SSL: default: close(); Base::setg( nullptr, nullptr, nullptr ); DBG( 3, dbg, "return eof" ); return traits_type::eof(); } } } else { // Valid read ERR_get_error(); ERR_clear_error(); loopAgain = false; // out of the while loop } } } #endif // In case the number of bytes read is not the size of the buffer, we have to set // egptr to start plus the number of bytes received Base::setg( inputBuffer_, inputBuffer_, inputBuffer_ + bytesReceived ); return *inputBuffer_; } protected: CharT outputBuffer_[SIZE]; CharT inputBuffer_[SIZE]; int socketFD_; struct timeval timeout_; std::string dbg; #if defined (USE_SSL) SSL* ssl_; #endif }; template > class basic_socketstream : public std::basic_iostream { public: using Base = std::basic_iostream; using stream_type = typename std::basic_iostream; using buf_type = basic_socketbuf<16385, CharT, Traits>; using traits_type = typename Base::traits_type; public: basic_socketstream(const basic_socketstream &) = delete; virtual ~basic_socketstream() = default; basic_socketstream & operator = (const basic_socketstream &) = delete; basic_socketstream() : stream_type( &socketBuffer_ ) {} #if defined (USE_SSL) basic_socketstream( int socketFD, SSL* ssl, std::string dbg_ = "Server: " ) : stream_type( &socketBuffer_ ), dbg( dbg_ ), socketBuffer_( dbg_ ) { DBG( 3, dbg, "socketFD = ", socketFD ); if ( 0 == socketFD ) { DBG( 3, dbg, "Trying to set socketFD to 0 which is not allowed!" ); throw std::runtime_error( "Trying to set socketFD to 0 on basic_socketstream level which is not allowed." ); } socketBuffer_.setSocket( socketFD ); if ( nullptr != ssl ) socketBuffer_.setSSL( ssl ); } #endif basic_socketstream( int socketFD ) : stream_type( &socketBuffer_ ) { DBG( 3, dbg, "socketFD = ", socketFD ); if ( 0 == socketFD ) { DBG( 3, dbg, "Trying to set socketFD to 0 which is not allowed!" ); throw std::runtime_error( "Trying to set socketFD to 0 on basic_socketstream level which is not allowed." ); } socketBuffer_.setSocket( socketFD ); } public: // For clients only, servers will have to create a socketstream // by providing a socket descriptor in the constructor int open( std::string& hostname, uint16_t port ) { if ( hostname.empty() ) return -1; if ( port == 0 ) return -2; struct addrinfo* address; int retval = 0; retval = getaddrinfo( hostname.c_str(), nullptr, nullptr, &address ); if ( 0 != retval ) { perror( "getaddrinfo" ); return -3; } int sockfd = socket( address->ai_family, address->ai_socktype, address->ai_protocol ); if ( -1 == sockfd ) { freeaddrinfo( address ); return -4; } retval = connect( sockfd, address->ai_addr, address->ai_addrlen ); if ( -1 == retval ) { DBG( 3, dbg, "close clientsocketFD" ); ::close( sockfd ); freeaddrinfo( address ); return -5; } freeaddrinfo( address ); socketBuffer_.setSocket( sockfd ); } // might be useful in the future so leaving it in // std::string getLine() { // if ( !socketBuffer_.socket() ) // throw std::runtime_error( "The socket is not or no longer open!" ); // std::string result; // CharT chr; // while( '\n' != ( chr = Base::get()) ) { // result += chr; // } // result += chr; // return result; // } bool usesSSL() { #ifdef USE_SSL return ( socketBuffer_.ssl() != nullptr ); #else return false; #endif } void putLine( std::string& line ) { if ( !socketBuffer_.socket() ) throw std::runtime_error( "The socket is not or no longer open!" ); DBG( 3, dbg, "socketstream::putLine: putting \"", line, "\" into the socket." ); Base::write( line.c_str(), line.size() ); } void close() { DBG( 3, dbg, "close clientsocketFD" ); socketBuffer_.close(); } protected: std::string dbg; buf_type socketBuffer_; }; typedef basic_socketstream socketstream; typedef basic_socketstream wsocketstream; class Server { public: Server() = delete; Server( const std::string & listenIP, uint16_t port ) noexcept( false ) : listenIP_(listenIP), wq_( WorkQueue::getInstance() ), port_( port ) { DBG( 3, "Initializing Server" ); serverSocket_ = initializeServerSocket(); SignalHandler* shi = SignalHandler::getInstance(); shi->setSocket( serverSocket_ ); shi->ignoreSignal( SIGPIPE ); // Sorry Dennis Ritchie, we do not care about this, we always check return codes #ifndef UNIT_TEST // libFuzzer installs own signal handlers shi->installHandler( SignalHandler::handleSignal, SIGTERM ); shi->installHandler( SignalHandler::handleSignal, SIGINT ); #endif } Server( Server const & ) = delete; Server & operator = ( Server const & ) = delete; virtual ~Server() { wq_ = nullptr; } public: virtual void run() = 0; private: int initializeServerSocket() { if ( port_ == 0 ) throw std::runtime_error( "Server Constructor: No port specified." ); int sockfd = ::socket( AF_INET6, SOCK_STREAM, 0 ); if ( -1 == sockfd ) throw std::runtime_error( "Server Constructor: Can´t create socket" ); int retval = 0; struct sockaddr_in6 serv; serv.sin6_family = AF_INET6; serv.sin6_port = htons( port_ ); if ( listenIP_.empty() ) serv.sin6_addr = in6addr_any; else { if ( 1 != ::inet_pton( AF_INET6, listenIP_.c_str(), &(serv.sin6_addr) ) ) { DBG( 3, "close clientsocketFD" ); ::close(sockfd); throw std::runtime_error( "Server Constructor: Cannot convert IP string" ); } } socklen_t len = sizeof( struct sockaddr_in6 ); retval = ::bind( sockfd, reinterpret_cast(&serv), len ); if ( 0 != retval ) { DBG( 3, "close clientsocketFD" ); ::close( sockfd ); throw std::runtime_error( std::string("Server Constructor: Cannot bind to port ") + std::to_string(port_) ); } retval = listen( sockfd, 64 ); if ( 0 != retval ) { DBG( 3, "close clientsocketFD" ); ::close( sockfd ); throw std::runtime_error( "Server Constructor: Cannot listen on socket" ); } // Here everything should be fine, return socket fd return sockfd; } protected: std::string listenIP_; WorkQueue* wq_; int serverSocket_; uint16_t port_; }; enum HTTPRequestMethod { GET = 1, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, HTTPRequestMethod_Spare = 255 // To save some space for future methods }; enum HTTPProtocol { InvalidProtocol = 0, HTTP_0_9, HTTP_1_0, HTTP_1_1, HTTP_2_0, HTTPProtocol_Spare = 255 }; enum HTTPResponseCode { RC_100_Continue = 100, RC_101_SwitchingProtocols, RC_102_Processing, RC_200_OK = 200, RC_201_Created, RC_202_Accepted, RC_203_NonAuthorativeInformation, RC_204_NoContent, RC_205_ResetContent, RC_206_PartialContent, RC_207_MultiStatus, RC_208_AlreadyReported, RC_226_IMUsed = 226, RC_300_MultipleChoices = 300, RC_301_MovedPermanently, RC_302_Found, RC_303_SeeOther, RC_304_NotModified, RC_305_UseProxy, RC_307_TemporaryRedirect = 307, RC_308_PermanentRedirect, RC_400_BadRequest = 400, RC_401_Unauthorized, RC_402_PaymentRequired, RC_403_Forbidden, RC_404_NotFound, RC_405_MethodNotAllowed, RC_406_NotAcceptable, RC_407_ProxyAuthenticationRequired, RC_408_RequestTimeout, RC_409_Conflict, RC_410_Gone, RC_411_LengthRequired, RC_412_PreconditionFailed, RC_413_PayloadTooLarge, RC_414_RequestURITooLong, RC_415_UnsupportedMediaType, RC_416_RequestRangeNotSatisfiable, RC_417_ExpectationFailed, RC_418_ImATeapot, RC_421_MisdirectedRequest = 421, RC_422_UnprocessableEntity, RC_423_Locked, RC_424_FailedDependency, RC_426_UpgradeRequired = 426, RC_428_PreconditionRequired = 428, RC_429_TooManyRequests, RC_431_RequestHeaderFieldsTooLarge = 431, RC_444_ConnectionClosedWithoutResponse = 444, RC_451_UnavailableForLegalReasons = 451, RC_499_ClientClosedRequest = 499, RC_500_InternalServerError, RC_501_NotImplemented, RC_502_BadGateway, RC_503_ServiceUnavailable, RC_504_GatewayTimeout, RC_505_HTTPVersionNotSupported, RC_506_VariantAlsoNegotiates, RC_507_InsufficientStorage, RC_508_LoopDetected, RC_510_NotExtended = 510, RC_511_NetworkAuthenticationRequired, RC_599_NetworkConnectTimeoutError = 599, HTTPReponseCode_Spare = 1000 // Filler }; enum HTTPRequestHasBody { No = 0, Optional = 1, Required = 2 }; class HTTPMethodProperties { private: // Embedded declaration, no need for this info outside of this container class struct HTTPMethodProperty { enum HTTPRequestMethod method_; std::string methodName_; enum HTTPRequestHasBody requestHasBody_; bool responseHasBody_; }; public: static enum HTTPRequestMethod getMethodAsEnum( std::string const& rms ) { static HTTPMethodProperties props_; struct HTTPMethodProperty const& prop = props_.findProperty( rms ); return prop.method_; } static std::string const& getMethodAsString( enum HTTPRequestMethod rme ) { static HTTPMethodProperties props_; struct HTTPMethodProperty const& prop = props_.findProperty( rme ); return prop.methodName_; } static enum HTTPRequestHasBody requestHasBody( enum HTTPRequestMethod rme ) { static HTTPMethodProperties props_; struct HTTPMethodProperty const& prop = props_.findProperty( rme ); return prop.requestHasBody_; } static bool responseHasBody( enum HTTPRequestMethod rme ) { static HTTPMethodProperties props_; struct HTTPMethodProperty const& prop = props_.findProperty( rme ); return prop.responseHasBody_; } private: struct HTTPMethodProperty const& findProperty( std::string rm ) { for( auto& prop : httpMethodProperties ) if ( prop.methodName_ == rm ) return prop; throw std::runtime_error( "HTTPMethodProperties::findProperty: HTTPRequestMethod as string not found." ); } struct HTTPMethodProperty const& findProperty( enum HTTPRequestMethod rm ) { for( auto& prop : httpMethodProperties ) if ( prop.method_ == rm ) return prop; throw std::runtime_error( "HTTPMethodProperties::findProperty: HTTPRequestMethod as enum not found." ); } std::vector const httpMethodProperties = { { GET, "GET", HTTPRequestHasBody::No, true }, { HEAD, "HEAD", HTTPRequestHasBody::No, false }, { POST, "POST", HTTPRequestHasBody::Required, true }, { PUT, "PUT", HTTPRequestHasBody::Required, true }, { DELETE, "DELETE", HTTPRequestHasBody::No, true }, { CONNECT, "CONNECT", HTTPRequestHasBody::Required, true }, { OPTIONS, "OPTIONS", HTTPRequestHasBody::Optional, true }, { TRACE, "TRACE", HTTPRequestHasBody::No, true }, { PATCH, "PATCH", HTTPRequestHasBody::Required, true } }; }; enum HeaderType { ServerSet = -2, Invalid = -1, Unspecified = 0, String = 1, Integer = 2, Float = 3, Date = 4, Range = 5, True = 7, // Only allowed value is "true", all lowercase Email = 8, ETag = 9, DateOrETag = 10, Parameters = 11, Url = 12, HostPort = 13, ProtoHostPort = 14, DateOrSeconds = 15, NoCache = 16, IP = 17, Character = 18, OnOff = 19, ContainsOtherHeaders = 20, StarOrFQURL = 21, CustomHeader = 22, HeaderType_Spare = 127 // Reserving some values }; class HTTPHeaderProperties { private: struct HTTPHeaderProperty { HTTPHeaderProperty( std::string name, enum HeaderType ht, bool w = false, bool l = false, char lsc = ',' ) : name_( name ), type_( ht ), canBeWeighted_( w ), canBeAList_( l ), listSeparatorChar_( lsc ) {} std::string name_; enum HeaderType type_; bool canBeWeighted_; bool canBeAList_; char listSeparatorChar_; }; std::unordered_map> const headerTypeToString_ = { { ServerSet, "ServerSet" }, { Invalid, "Invalid" }, { Unspecified, "Unspecified" }, { String, "String" }, { Integer, "Integer" }, { Float, "Float" }, { Date, "Date" }, { Range, "Range" }, { True, "True" }, { Email, "Email" }, { ETag, "ETag" }, { DateOrETag, "DateOrETag" }, { Parameters, "Parameters" }, { Url, "Url" }, { HostPort, "HostPort" }, { ProtoHostPort, "ProtoHostPort" }, { DateOrSeconds, "DateOrSeconds" }, { NoCache, "NoCache" }, { IP, "IP" }, { Character, "Character" }, { OnOff, "OnOff" }, { ContainsOtherHeaders, "ContainsOtherHeaders" }, { StarOrFQURL, "StarOrFQURL" }, { CustomHeader, "CustomHeader" } }; public: static enum HeaderType headerType( std::string const & str ) { static HTTPHeaderProperties props; for ( auto& prop : props.httpHeaderProperties ) { if ( prop.name_ == str ) return prop.type_; } return CustomHeader; } static char listSeparatorChar( std::string const & headerName ) { static HTTPHeaderProperties props; for ( auto& prop : props.httpHeaderProperties ) { if ( prop.name_ == headerName ) return prop.listSeparatorChar_; } return ','; } static std::string const& headerTypeAsString( enum HeaderType ht ) { static HTTPHeaderProperties props; return props.headerTypeToString_.at( ht ); } private: // Contains most if not all headers from RFC2616 RFC7230 and RFC7231 // This is a mix of request and response headers! // Please add if you find that headers are missing std::vector const httpHeaderProperties = { { "Accept", HeaderType::String, true, true }, { "Accept-Charset", HeaderType::String, true, true }, { "Accept-Encoding", HeaderType::String, true, true }, { "Accept-Language", HeaderType::String, true, true }, { "Accept-Ranges", HeaderType::String, false, false }, { "Access-Control-Allow-Credentials", HeaderType::True, false, false }, { "Access-Control-Allow-Headers", HeaderType::String, false, true }, { "Access-Control-Allow-Methods", HeaderType::String, false, true }, { "Access-Control-Allow-Origin", HeaderType::StarOrFQURL, false, false }, { "Access-Control-Expose-Headers", HeaderType::String, false, true }, { "Access-Control-Max-Age", HeaderType::Integer, false, false }, { "Access-Control-Request-Headers", HeaderType::String, false, true }, { "Access-Control-Request-Method", HeaderType::String, false, false }, { "Age", HeaderType::Integer, false ,false }, { "Allow", HeaderType::String, false, true }, { "Authorization", HeaderType::String, false, false }, { "Cache-Control", HeaderType::String, false, true }, { "Connection", HeaderType::String, false, false }, { "Content-Disposition", HeaderType::String, false, false }, { "Content-Encoding", HeaderType::String, false, true }, { "Content-Language", HeaderType::String, false, true }, { "Content-Length", HeaderType::Integer, false, false }, { "Content-Location", HeaderType::Url, false, false }, { "Content-Range", HeaderType::Range, false, true }, { "Content-Security-Policy", HeaderType::String, false, false }, { "Content-Security-Policy-Report-Only", HeaderType::String, false, false }, { "Content-Type", HeaderType::String, false, false }, { "Cookie", HeaderType::Parameters, false, false }, { "Cookie2", HeaderType::String, false, false }, // Obsolete by RFC 6265 { "DNT", HeaderType::Integer, false, false }, { "Date", HeaderType::Date, false, false }, { "ETag", HeaderType::ETag, false, false }, { "Expect", HeaderType::String, false, false }, { "Expires", HeaderType::Date, false, false }, { "Forwarded", HeaderType::String, false, false }, { "From", HeaderType::Email, false, false }, { "Host", HeaderType::HostPort, false, false }, { "If-Match", HeaderType::ETag, false, true }, { "If-Modified-Since", HeaderType::Date, false, false }, { "If-None-Match", HeaderType::ETag, false, true }, { "If-Range", HeaderType::DateOrETag, false ,false }, { "If-Unmodified-Since", HeaderType::Date, false, false }, { "Keep-Alive", HeaderType::Parameters, false, true }, { "Large-Allocation", HeaderType::Integer, false, false }, // Not Standard yet { "Last-Modified", HeaderType::Date,false ,false }, { "Location", HeaderType::Url, false ,false }, { "Origin", HeaderType::ProtoHostPort, false, false }, { "Pragma", HeaderType::NoCache, false, false }, { "Proxy-Authenticate", HeaderType::String, false ,false }, { "Proxy-Authorization", HeaderType::String, false, false }, { "Public-Key-Pins", HeaderType::Parameters, false, false }, { "Public-Key-Pins-Report-Only", HeaderType::Parameters, false, false }, { "Range", HeaderType::Range, false, true }, { "Referer", HeaderType::Url, false, false }, { "Referrer-Policy", HeaderType::String, false, false }, { "Retry-After", HeaderType::DateOrSeconds, false, false }, { "Server", HeaderType::String, false, false }, { "Set-Cookie", HeaderType::Parameters, false, false }, { "Set-Cookie2", HeaderType::Parameters, false, false }, // Obsolete { "SourceMap", HeaderType::Url, false, false }, { "Strict-Transport-Security", HeaderType::Parameters, false, false }, { "TE", HeaderType::String, true, true }, { "Tk", HeaderType::Character, false, false }, { "Trailer", HeaderType::ContainsOtherHeaders, false, false }, { "Transfer-Encoding", HeaderType::String, false, true }, { "Upgrade-Insecure-Requests", HeaderType::Integer }, { "User-Agent", HeaderType::String, false, false }, { "Vary", HeaderType::String, false, true }, { "Via", HeaderType::String, false, true }, { "WWW-Authenticate", HeaderType::String, false, false }, { "Warning", HeaderType::String, false, false }, { "X-Content-Type-Options", HeaderType::String, false, false }, { "X-DNS-Prefetch-Control", HeaderType::OnOff, false, false }, { "X-Forwarded-For", HeaderType::IP, false, true }, { "X-Forwarded-Host", HeaderType::String, false, false }, { "X-Forwarded-Proto", HeaderType::String, false, false }, { "X-Frame-Options", HeaderType::String, false, false }, { "X-XSS-Protection", HeaderType::String, false, false } // { "", HeaderType:: , , }, // Default LCS is ',', no need to add it }; }; // This URL class tries to follow RFC 3986 // the updates in 6874 and 8820 are not taken into account struct URL { public: URL() : scheme_( "" ), user_( "" ), passwd_( "" ), host_( "" ), path_( "" ), fragment_( "" ), port_( 0 ), hasScheme_( false ), hasUser_ ( false ), hasPasswd_( false ), hasHost_( false ), hasPort_( false ), hasQuery_( false ), hasFragment_( false ), pathIsStar_( false ) {} URL( URL const & ) = default; ~URL() = default; URL& operator=( URL const & ) = default; private: int charToNumber( std::string::value_type c ) const { if ( 'A' <= c && 'F' >= c ) return (int)(c - 'A') + 10; if ( 'a' <= c && 'f' >= c ) return (int)(c - 'a') + 10; if ( '0' <= c && '9' >= c ) return (int)(c - '0'); std::stringstream s; s << "'" << c << "' is not a hexadecimal digit!"; throw std::runtime_error( s.str() ); } public: // Following https://en.wikipedia.org/wiki/Percent-encoding std::string percentEncode( const std::string& s ) const { std::stringstream r; for ( std::string::value_type c : s ) { // skip alpha and unreserved characters if ( isalnum( c ) || '-' == c || '_' == c || '.' == c || '~' == c ) { r << c; continue; } r << '%' << std::setw(2) << std::uppercase << std::hex << int( (unsigned char)c ) << std::nouppercase; } return r.str(); } std::string percentDecode( const std::string& s ) const { std::stringstream r; // cppcheck-suppress StlMissingComparison for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci ) { std::string::value_type c = *ci; int n = 0; if ( '%' == c ) { if ( ++ci == s.end() ) throw std::runtime_error( "Malformed URL, percent found but no next char" ); n += charToNumber(*ci); n *= 16; if ( ++ci == s.end() ) throw std::runtime_error( "Malformed URL, percent found but no next next char" ); // better error message needed :-) n += charToNumber(*ci); r << (unsigned char)n; continue; } r << c; } return r.str(); } static URL parse( std::string fullURL ) { DBG( 3, "fullURL: '", fullURL, "'" ); URL url; size_t pathBeginPos = 0; size_t pathEndPos = std::string::npos; size_t questionMarkPos = 0; size_t numberPos = 0; if ( fullURL.empty() ) { url.path_ = '/'; return url; } if ( fullURL.size() == 1 && fullURL[0] == '*' ) { url.path_ = fullURL; url.pathIsStar_ = true; return url; } questionMarkPos = fullURL.find( '?' ); numberPos = fullURL.find( '#' ); if ( fullURL[0] == '/' ) { pathBeginPos = 0; } else { // If first character is not a / then the first colon is end of scheme size_t schemeColonPos = fullURL.find( ':' ); if ( std::string::npos != schemeColonPos && 0 != schemeColonPos ) { std::string scheme; scheme = fullURL.substr( 0, schemeColonPos ); std::string validSchemeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-."; DBG( 3, "scheme: '", scheme, "'" ); if ( scheme.find_first_not_of( validSchemeChars ) != std::string::npos ) throw std::runtime_error( "Scheme contains invalid characters" ); url.scheme_ = scheme; url.hasScheme_ = true; } else throw std::runtime_error( "URL does not start with / and has no scheme" ); size_t authorityPos = fullURL.find( "//", schemeColonPos+1 ); size_t authorityEndPos; std::string authority; if ( std::string::npos != authorityPos ) { if ( (schemeColonPos+1) != authorityPos ) throw std::runtime_error( "Something between : and //" ); pathBeginPos = fullURL.find( '/', authorityPos+2 ); authorityEndPos = std::min( { pathBeginPos, questionMarkPos, numberPos } ); authority = fullURL.substr( authorityPos+2, authorityEndPos - (authorityPos + 2) ); DBG( 3, "authority: '", authority, "'" ); const size_t atPos = authority.find( '@' ); bool atFound = (atPos != std::string::npos); if ( atFound ) { if ( atPos == 0 ) throw std::runtime_error( "'@' found in the first column, username would be empty" ); // User (+passwd) found user : passwd @ host size_t passwdColonPos = authority.rfind( ':', atPos ); size_t userEndPos = std::string::npos; DBG( 3, "1 userEndPos '", userEndPos, "'" ); if ( passwdColonPos != std::string::npos ) { std::string passwd = authority.substr( passwdColonPos+1, atPos-(passwdColonPos+1) ); DBG( 3, "passwd: '", passwd, "', passwdColonPos: ", passwdColonPos ); userEndPos = passwdColonPos; DBG( 3, "2a userEndPos '", userEndPos, "'" ); // passwd is possibly percent encoded FIXME url.passwd_ = url.percentDecode( passwd ); url.hasPasswd_ = true; } else { userEndPos = atPos; DBG( 3, "2b userEndPos '", userEndPos, "'" ); } DBG( 3, "3 userEndPos '", userEndPos, "'" ); std::string user = authority.substr( 0, userEndPos ); DBG( 3, "user: '", user, "'" ); if ( !user.empty() ) { // user is possibly percent encoded FIXME url.user_ = url.percentDecode( user ); url.hasUser_ = true; // delete user/pass including the at authority.erase( 0, atPos+1 ); } else { throw std::runtime_error( "User not found before @ sign" ); } } // Instead of all the logic it is easier to work on substrings // authority now at most contains hostname possibly in ipv6 notation plus port bool angleBracketOpenFound = (authority[0] == '['); size_t angleBracketClosePos; bool angleBracketCloseFound = false; if ( angleBracketOpenFound ) { angleBracketClosePos = authority.find( ']', 0 ); angleBracketCloseFound = (angleBracketClosePos != std::string::npos); if ( !angleBracketCloseFound ) throw std::runtime_error( "No matching IPv6 ']' found." ); url.host_ = authority.substr( 0, angleBracketClosePos ); url.hasHost_ = true; DBG( 3, "angleBracketCloseFound: host: '", url.host_, "'" ); authority.erase( 0, angleBracketClosePos+1 ); } if ( !authority.empty() ) { // authority now at most has host and port, port is now the definitive separator // (can't be part of the ipv6 address anymore) size_t portColonPos = authority.rfind( ':' ); bool portColonFound = (portColonPos != std::string::npos); if ( portColonFound ) { if ( portColonPos == 0 && !url.hasHost_ ) throw std::runtime_error( "No hostname found" ); if ( portColonPos != 0 ) { url.host_ = authority.substr( 0, portColonPos ); DBG( 3, "portColonFound: host: '", url.host_, "'" ); url.hasHost_ = true; } size_t port = 0; std::string portString = authority.substr( portColonPos+1 ); DBG( 3, "portString: '", portString, "'" ); if ( portString.empty() ) // Use the default port number, use scheme and the /etc/services file port = 0; // FIXME else { size_t pos = 0; try { port = std::stoull( portString, &pos ); } catch ( std::invalid_argument& e ) { DBG( 3, "invalid_argument exception caught in stoull: ", e.what() ); DBG( 3, "number of characters processed: ", pos ); } catch ( std::out_of_range& e ) { DBG( 3, "out_of_range exception caught in stoull: ", e.what() ); DBG( 3, "errno: ", errno, ", strerror(errno): ", strerror(errno) ); } } if ( port >= 65536 ) throw std::runtime_error( "URL::parse: port too large" ); url.port_ = (unsigned short)port; url.hasPort_ = true; DBG( 3, "port: ", port ); } else { url.host_ = authority; url.hasHost_ = true; DBG( 3, "portColonNotFound: host: '", url.host_, "'" ); } } else if ( !url.hasHost_ ) throw std::runtime_error( "No hostname found" ); } else { throw std::runtime_error( "// not found" ); } } pathEndPos = std::min( {questionMarkPos, numberPos} ); if ( std::string::npos != pathBeginPos ) { url.path_ = fullURL.substr( pathBeginPos, pathEndPos - pathBeginPos ); } else { url.path_ = ""; } DBG( 3, "path: '", url.path_, "'" ); if ( std::string::npos != questionMarkPos ) { // Why am i not checking numberPos for validity? std::string queryString = fullURL.substr( questionMarkPos+1, numberPos-(questionMarkPos+1) ); DBG( 3, "queryString: '", queryString, "'" ); if ( queryString.empty() ) { url.hasQuery_ = false; throw std::runtime_error( "Invalid URL: query not found after question mark" ); } else { url.hasQuery_ = true; size_t ampPos = 0; while ( !queryString.empty() ) { ampPos = queryString.find( '&' ); std::string query = queryString.substr( 0, ampPos ); DBG( 3, "query: '", query, "'" ); size_t equalsPos = query.find( '=' ); if ( std::string::npos == equalsPos ) throw std::runtime_error( "Did not find a '=' in the query" ); std::string one, two; one = url.percentDecode( query.substr( 0, equalsPos ) ); DBG( 3, "one: '", one, "'" ); two = url.percentDecode( query.substr( equalsPos+1 ) ); DBG( 3, "two: '", two, "'" ); url.arguments_.push_back( std::make_pair( one ,two ) ); // npos + 1 == 0... ouch if ( std::string::npos == ampPos ) queryString.erase( 0, ampPos ); else queryString.erase( 0, ampPos+1 ); } } } if ( std::string::npos != numberPos ) { url.hasFragment_ = true; url.fragment_ = fullURL.substr( numberPos+1 ); DBG( 3, "path: '", url.path_, "'" ); } // Now make sure the URL does not contain %xx values size_t percentPos = url.path_.find( '%' ); if ( std::string::npos != percentPos ) { // throwing an error mentioning a dev issue throw std::runtime_error( std::string("DEV: Some URL component still contains percent encoded values, please report the URL: ") + url.path_ ); } // Done! return url; } void printURL( std::ostream& os ) const { DBG( 3, "URL::printURL: debug level 3 to see more" ); std::stringstream ss; DBG( 3, " hasScheme_: ", hasScheme_, ", scheme_: ", scheme_ ); if ( hasScheme_ ) { ss << scheme_ << ':'; } DBG( 3, " hasHost_: ", hasHost_, ", host_: ", host_ ); if ( hasHost_ ) { ss << "//"; DBG( 3, " hasUser_: ", hasUser_, ", user_: ", user_ ); if ( hasUser_ ) { ss << percentEncode( user_ ); } DBG( 3, " hasPasswd_: ", hasPasswd_, ", passwd_: ", passwd_ ); if ( hasPasswd_ ) { ss << ':' << percentEncode( passwd_ ); } DBG( 3, " hasUser_: ", hasUser_, ", user_: ", user_ ); if ( hasUser_ ) { ss << '@'; } DBG( 3, " hasHost_: ", hasHost_, ", host_: ", host_ ); if ( hasHost_ ) { ss << host_; } DBG( 3, " hasPort_: ", hasPort_, ", port_: ", port_ ); if ( hasPort_ ) { ss << ':' << port_; } } DBG( 3, " path_: '", path_, "'" ); ss << (path_.empty() ? "/" : path_); if ( hasQuery_ ) { DBG( 3, " hasQuery_: ", hasQuery_ ); ss << '?'; size_t i; for ( i = 0; i < (arguments_.size()-1); ++i ) { DBG( 3, " query[", i, "]: ", arguments_[i].first, " ==> ", arguments_[i].second ); ss << percentEncode( arguments_[i].first ) << '=' << percentEncode( arguments_[i].second ) << "&"; } DBG( 3, " query[", i, "]: ", arguments_[i].first, " ==> ", arguments_[i].second ); ss << percentEncode( arguments_[i].first ) << '=' << percentEncode( arguments_[i].second ); } if ( hasFragment_ ) { DBG( 3, " hasFragment_: ", hasFragment_, ", fragment_: ", fragment_ ); ss << '#' << fragment_; } os << ss.str() << "\n"; DBG( 3, "URL::printURL: done" ); } public: std::string scheme_; std::string user_; std::string passwd_; std::string host_; std::string path_; std::string fragment_; std::vector> arguments_; unsigned short port_; bool hasScheme_; bool hasUser_; bool hasPasswd_; bool hasHost_; bool hasPort_; bool hasQuery_; bool hasFragment_; bool pathIsStar_; }; std::ostream& operator<<( std::ostream& os, URL const & url ) { url.printURL( os ); return os; } enum MimeType { CatchAll = 0, TextHTML, TextXML, TextPlain, TextPlainProm_0_0_4, ApplicationJSON, ImageXIcon, MimeType_spare = 255 }; std::unordered_map> mimeTypeMap = { { CatchAll, "*/*" }, { TextHTML, "text/html" }, { TextPlain, "text/plain" }, { TextPlainProm_0_0_4, "text/plain; version=0.0.4" }, { ImageXIcon, "image/x-icon" }, { ApplicationJSON, "application/json" } }; class HTTPHeader { public: HTTPHeader() { type_ = HeaderType::Invalid; } HTTPHeader( std::string n, std::string v ) : name_( n ), value_( v ) { type_ = HeaderType::ServerSet; } HTTPHeader( char const * n, char const * v ) : name_( n ), value_( v ) { type_ = HeaderType::ServerSet; } HTTPHeader( HTTPHeader const & ) = default; HTTPHeader( HTTPHeader&& ) = default; HTTPHeader& operator=( HTTPHeader const& ) = default; ~HTTPHeader() = default; public: static HTTPHeader parse( std::string& header ) { HTTPHeader hh; hh.type_ = HeaderType::Invalid; DBG( 3, "Raw Header : '", header, "'" ); std::string::size_type colonPos = header.find( ':' ); if ( std::string::npos == colonPos ) { hh.invalidReason_ = "Not a valid header, no : found"; return hh; } std::string headerName = header.substr( 0, colonPos ); std::string headerValue = header.substr( colonPos+1 ); // FIXME: possible whitespace before, between and after // Spaces in header names are illegal but be lenient und just remove them headerName.erase( std::remove ( headerName.begin(), headerName.begin() + colonPos, ' ' ), headerName.end() ); hh.name_ = headerName; hh.value_ = headerValue; hh.type_ = HTTPHeaderProperties::headerType( hh.name_ ); DBG( 3, "Headername : '", headerName, "'" ); DBG( 3, "Headervalue: '", headerValue, "'" ); DBG( 3, "HeaderType : '", HTTPHeaderProperties::headerTypeAsString(hh.type_), "'" ); if ( hh.type_ == HeaderType::Invalid ) { hh.invalidReason_ = "parse header: found an Invalid HeaderType"; return hh; } std::string::size_type quotes = std::count( headerValue.begin(), headerValue.end(), '"' ); bool properlyQuoted = (quotes % 2 == 0); if ( !properlyQuoted ) { DBG( 3, "Parse: header not properly quoted: uneven number of quotes (", quotes, ") found" ); hh.type_ = HeaderType::Invalid; hh.invalidReason_ = "parse header: header improperly quoted"; } return hh; } std::string headerName() const { return name_; } // Not sure what I needed it for but leaving it for now // std::string headerValue() const { // std::cout << "Calling headerValue for HeaderName: " << name_ << "\n"; // std::cout.flush(); // std::string value; // switch ( type_ ) { // case ServerSet: // return value_; // case String: // if ( valueList_.size() > 0 ) // return valueList_[0]; // throw std::runtime_error( "headerValue(): Empty valuelist" ); // break; // case Integer: // if ( integers_.size() > 0 ) // return std::to_string( integers_[0] ); // throw std::runtime_error( "headerValue(): Empty valuelist" ); // break; // case Float: // if ( floats_.size() > 0 ) // return std::to_string( floats_[0] ); // throw std::runtime_error( "headerValue(): Empty valuelist" ); // break; // case Date: // return date_.toString(); // break; // case Range: // return ""; // break; // default: // return std::string("Not implemented yet for '") + name_ + "', type is '" + std::to_string((int)type_) + "'"; // } // } std::vector const headerValueAsList() const { return splitHeaderValue(); } void debugPrint() const { if ( type_ == HeaderType::Invalid ) { DBG( 3, "HeaderType::Invalid, invalidReason: ", invalidReason_ ); } else { DBG( 3, "Headername: '", name_, "', Headervalue: '", value_, "'" ); } } size_t headerValueAsNumber() const { size_t number = std::stoll( value_ ); return number; } double headerValueAsDouble() const { double number = std::stod( value_ ); return number; } HeaderType type() const { return type_; } std::string const & headerValueAsString() const { return value_; } enum MimeType headerValueAsMimeType() const { auto list = headerValueAsList(); for ( auto& item : list ) { DBG( 3, "item: '", item, "'" ); for( auto& mt : mimeTypeMap ) { DBG( 3, "comparing item: '", item, "' to '", mt.second, "'" ); if ( mt.second.compare( item ) == 0 ) { DBG( 3, "MimeType ", mt.second, " found." ); return mt.first; } } } // If we did not recognize the mimetype we will return TextHTML so the client can see the HTML page return TextHTML; } const std::string& invalidReason() const { return invalidReason_; } private: std::vector splitHeaderValue() const { std::vector elementList; std::stringstream ss( value_ ); std::string s; char listSeparatorChar = HTTPHeaderProperties::listSeparatorChar( name_ ); while ( ss.good() ) { std::getline( ss, s, listSeparatorChar ); // Remove leading whitespace s.erase( s.begin(), std::find_if( s.begin(), s.end(), std::bind1st( std::not_equal_to(), ' ' ) ) ); // Remove trailing whitespace s.erase( std::find_if( s.rbegin(), s.rend(), std::bind1st( std::not_equal_to(), ' ') ).base(), s.end() ); elementList.push_back( s ); } return elementList; } private: std::string name_; std::string value_; enum HeaderType type_; std::string invalidReason_; std::vector valueList_; std::vector floats_; std::vector integers_; std::vector> ranges_; std::vector> parameters_; datetime date_; }; class HTTPMessage { protected: HTTPMessage() { initialized_ = false; protocol_ = HTTPProtocol::InvalidProtocol; } HTTPMessage( HTTPMessage const & ) = default; HTTPMessage & operator = ( HTTPMessage const & ) = default; ~HTTPMessage() = default; public: // Data manipulators/extractors std::string const & body() const { return body_; } void addBody( std::string const& body ) { body_ = body; } void addHeader( std::string const & name, std::string const & value ) { if ( headers_.insert( std::make_pair( name, HTTPHeader( name, value ) ) ).second == false ) { throw std::runtime_error( "Header already exists in the headerlist" ); } } void addHeader( const HTTPHeader & hh ) { if ( headers_.insert( std::make_pair( hh.headerName(), hh ) ).second == false ) { throw std::runtime_error( "Header already exists in the headerlist" ); } } bool hasHeader( std::string const & header ) const { auto pos = headers_.find( header ); if ( pos == headers_.end() ) return false; return true; } HTTPHeader const & getHeader( std::string const & header ) const { auto pos = headers_.find( header ); if ( pos == headers_.end() ) { std::stringstream ss; ss << "HTTPMessage::getHeader: Header '" << header << "' not found."; throw std::runtime_error( ss.str() ); } return (*pos).second; } std::string const & protocolAsString() const { // will throw if key not found, it is a bug anyway return protocol_map_.at(protocol_); } enum HTTPProtocol protocol() const { return protocol_; } void setProtocol( enum HTTPProtocol protocol ) { if ( protocol < HTTPProtocol::HTTP_0_9 || protocol > HTTPProtocol::HTTP_2_0 ) throw std::runtime_error( std::string("Protocol enum value out of bounds: ") + std::to_string(protocol) ); protocol_ = protocol; } void setProtocol( std::string const & protocolString ) { auto it = protocol_map_.begin(); while( it != protocol_map_.end() ) { if ( (*it).second == protocolString ) { protocol_ = (*it).first; break; } ++it; } if ( it == protocol_map_.end() ) { DBG( 3, "Protocol string '", protocolString, "' not found in map, protocol unsupported!" ); throw std::runtime_error( std::string("Protocol is not supported: ") + protocolString ); } } std::string const host() const { std::string host; if ( hasHeader( "Host" ) ) { HTTPHeader host = getHeader( "Host" ); } else { DBG( 3, "HTTPMessage::host: header Host not found." ); host = ""; } return host; } bool isInitialized() const { return initialized_; } void setInitialized() { initialized_ = true; } protected: std::string readData( socketstream& in, size_t length ) { std::string data( length, '\0' ); in.read( &data[0], length ); return data; } std::string readChunkedData( socketstream& in ) { std::string chunkHeader; std::string data; std::getline( in, chunkHeader, '\n' ); // Final header starts with 0, rest of the line is not important while ( '0' != chunkHeader[0] ) { // chunkheader: hexadecimal numbers followed by an optional semi-colon with a comment and a \r // stoll should filter all that crap out for us and return just the hexadecimal digits DBG( 3, "chunkHeader (ater check for 0): '", chunkHeader, "'" ); size_t length = std::stoll( chunkHeader, nullptr, 16 ); DBG( 3, "length: '", length, "'" ); // Initialize chunk to all zeros std::string chunk( length, '\0' ); in.read( &chunk[0], length ); DBG( 3, "chunk: '", chunk, "'" ); data += chunk; // Reads trailing \r\n from the chunk std::getline( in, chunkHeader, '\n' ); // Reads the empty line following the chunk std::getline( in, chunkHeader, '\n' ); DBG( 3, "chunkHeader (should be empty line): '", chunkHeader, "'" ); // Read a new line to check for 0\r header std::getline( in, chunkHeader, '\n' ); DBG( 3, "chunkHeader (should be next chunk header): '", chunkHeader, "'" ); } return data; } protected: enum HTTPProtocol protocol_; std::unordered_map headers_; std::string body_; std::unordered_map> protocol_map_ = { { HTTPProtocol::HTTP_0_9, "HTTP/0.9" }, { HTTPProtocol::HTTP_1_0, "HTTP/1.0" }, { HTTPProtocol::HTTP_1_1, "HTTP/1.1" }, { HTTPProtocol::HTTP_2_0, "HTTP/2.0" } }; bool initialized_; }; class HTTPRequest : public HTTPMessage { public: HTTPRequest() : method_( HTTPRequestMethod::GET ) {} HTTPRequest( HTTPRequest const & ) = default; HTTPRequest & operator = ( HTTPRequest const & ) = default; ~HTTPRequest() = default; template friend basic_socketstream& operator>>(basic_socketstream&, HTTPRequest& ); public: enum HTTPRequestMethod method() const { return method_; } URL const & url() const { return url_; } void debugPrint() { DBG( 3, "HTTPRequest::debugPrint:" ); DBG( 3, "Method : \"", method_, "\"" ); DBG( 3, "URL : \"", url_, "\"" ); DBG( 3, "Protocol: \"", protocol_, "\"" ); for ( auto& header: headers_ ) DBG( 3, "Header : \"", header.first, "\" ==> \"", header.second.headerValueAsString(), "\"" ); DBG( 3, "Body : \"", body_, "\"" ); } private: enum HTTPRequestMethod method_; URL url_; }; class HTTPResponse : public HTTPMessage { public: HTTPResponse( bool bodyExpected = true ) : responseCode_( HTTPResponseCode::RC_200_OK ), bodyExpected_( bodyExpected ) {} HTTPResponse( HTTPResponse const & ) = default; HTTPResponse & operator = ( HTTPResponse const & ) = default; virtual ~HTTPResponse() = default; template friend basic_socketstream& operator<<(basic_socketstream&, HTTPResponse& ); template friend basic_socketstream& operator>>(basic_socketstream&, HTTPResponse& ); public: enum HTTPResponseCode responseCode() const { return responseCode_; } std::string reasonPhrase() const { return reasonPhrase_; } std::string responseCodeAsString() const { return response_map_.at( responseCode_ ); } bool bodyExpected() const { return bodyExpected_; } void setResponseCode( enum HTTPResponseCode rc ) { DBG( 3, "Setting response code to: '", std::dec, (int)rc, "'" ); responseCode_ = rc; } void setResponseCode( std::string& rc ) { int anInt = std::stoi( rc ); if ( anInt < 0 || anInt > HTTPResponseCode::HTTPReponseCode_Spare ) throw std::runtime_error( "Responsecode is out of bounds!" ); responseCode_ = static_cast( anInt ); } void setReasonPhrase( std::string& reason ) { reasonPhrase_ = reason; } void debugPrint() { DBG( 3, "HTTPReponse::debugPrint:" ); DBG( 3, "Response Code: \"", (int)responseCode_, "\"" ); for ( auto& header: headers_ ) DBG( 3, "Header: \"", header.first, "\" ==> \"", header.second.headerValueAsString(), "\"" ); // Leaving body at 3, too large and spams the output DBG( 3, "Body: \"", body_, "\"" ); } void createResponse( enum MimeType mimeType, std::string body, enum HTTPResponseCode rc ) { // mimetype validity checking? addHeader( HTTPHeader( "Content-Type", mimeTypeMap[mimeType] ) ); addHeader( HTTPHeader( "Content-Length", std::to_string( body.size() ) ) ); addBody( body ); setResponseCode( rc ); } private: enum HTTPResponseCode responseCode_; bool bodyExpected_; std::string reasonPhrase_; std::unordered_map> response_map_ = { { RC_100_Continue, "Continue" }, { RC_101_SwitchingProtocols, "Switching Protocols" }, { RC_102_Processing, "Processing" }, { RC_200_OK, "OK" }, { RC_201_Created, "Created" }, { RC_202_Accepted, "Accepted" }, { RC_203_NonAuthorativeInformation, "Non-authorative Information" }, { RC_204_NoContent, "No Content" }, { RC_205_ResetContent, "Reset Content" }, { RC_206_PartialContent, "Partial Content" }, { RC_207_MultiStatus, "Multi-Status" }, { RC_208_AlreadyReported, "Already Reported" }, { RC_226_IMUsed, "IM Used" }, { RC_300_MultipleChoices, "Multiple Choices" }, { RC_301_MovedPermanently, "Moved Permanently" }, { RC_302_Found, "Found" }, { RC_303_SeeOther, "See Other" }, { RC_304_NotModified, "Not Modified" }, { RC_305_UseProxy, "Use Proxy" }, { RC_307_TemporaryRedirect, "Temporary Redirect" }, { RC_308_PermanentRedirect, "Permanent Redirect" }, { RC_400_BadRequest, "Bad Request" }, { RC_401_Unauthorized, "Unauthorized" }, { RC_402_PaymentRequired, "Payment Required" }, { RC_403_Forbidden, "Forbidden" }, { RC_404_NotFound, "Not Found" }, { RC_405_MethodNotAllowed, "Method Not Allowed" }, { RC_406_NotAcceptable, "Not Acceptable" }, { RC_407_ProxyAuthenticationRequired, "Proxy Authentication Required" }, { RC_408_RequestTimeout, "Request Timeout" }, { RC_409_Conflict, "Conflict" }, { RC_410_Gone, "Gone" }, { RC_411_LengthRequired, "Length Required" }, { RC_412_PreconditionFailed, "Precondition Failed" }, { RC_413_PayloadTooLarge, "Payload Too Large" }, { RC_414_RequestURITooLong, "Request-URI Too Long" }, { RC_415_UnsupportedMediaType, "Unsupported Media Type" }, { RC_416_RequestRangeNotSatisfiable, "Request Range Not Satisfiable" }, { RC_417_ExpectationFailed, "Expectation Failed" }, { RC_418_ImATeapot, "I'm a teapot" }, { RC_421_MisdirectedRequest, "Misdirected Request" }, { RC_422_UnprocessableEntity, "Unprocessable Entity" }, { RC_423_Locked, "Locked" }, { RC_424_FailedDependency, "Failed Dependency" }, { RC_426_UpgradeRequired, "Upgrade Required" }, { RC_428_PreconditionRequired, "Precondition Required" }, { RC_429_TooManyRequests, "Too Many Requests" }, { RC_431_RequestHeaderFieldsTooLarge, "Request Header Fields Too Large" }, { RC_444_ConnectionClosedWithoutResponse, "Connection Closed Without Response" }, { RC_451_UnavailableForLegalReasons, "Unavailable For Legal Reasons" }, { RC_499_ClientClosedRequest, "Client Closed Request" }, { RC_500_InternalServerError, "Internal Server Error" }, { RC_501_NotImplemented, "Not Implemented" }, { RC_502_BadGateway, "Bad Gateway" }, { RC_503_ServiceUnavailable, "Service Unavailable" }, { RC_504_GatewayTimeout, "Gateway Timeout" }, { RC_505_HTTPVersionNotSupported, "HTTP Version Not Supported" }, { RC_506_VariantAlsoNegotiates, "Variant Also Negotiates" }, { RC_507_InsufficientStorage, "Insufficient Storage" }, { RC_508_LoopDetected, "Loop Detected" }, { RC_510_NotExtended, "Not Extended" }, { RC_511_NetworkAuthenticationRequired, "Network Authentication Required" }, { RC_599_NetworkConnectTimeoutError, "Network Connect Timeout Error" } }; }; // Compress linear white space and remove carriage return, not new line, this one is gone already std::string& compressLWSAndRemoveCR( std::string& line ) { std::string::size_type pos = 0, end = line.size(), start = 0; for ( pos = 0; pos < end; ++pos ) { start = pos; if ( ::isspace( line[pos] ) ) { while ( (pos+1) < line.size() && ::isspace( line[++pos] ) ) { } if ( (pos - start) > 1 ) { line.erase( start+1, pos-start-1 ); end -= pos-start-1; pos = start+1; } } } // Remove trailing '\r' if (!line.empty() && line.back() == '\r') { line.pop_back(); } return line; } // This method is for a server reading a request from the client template basic_socketstream& operator>>( basic_socketstream& rs, HTTPRequest& m ) { DBG( 3, "Reading from the socket" ); // Read something like: GET /persecond/10 HTTP/1.1\r\n std::string requestLine, method, url, protocol; // We need to read a line and check if the request is valid // Fuzzers like to remove spaces so there are not enough elements // on the line and then we're in trouble with the old method std::getline( rs, requestLine ); if ( rs.fail() ) { DBG( 3, "Could not read from socket, might have been closed due to e.g. timeout" ); throw std::runtime_error( "Could not read from socket, might have been closed due to e.g. timeout" ); } size_t nlPos = requestLine.find( '\n', 0 ); if ( nlPos != std::string::npos ) requestLine.erase( nlPos, 1 ); size_t crPos = requestLine.find( '\r', 0 ); if ( crPos != std::string::npos ) requestLine.erase( crPos, 1 ); DBG( 3, "RequestLine: \"", requestLine, "\"" ); // Method does not have spaces, url has %20, protocol does not have spaces, so exactly 2 if ( std::count( requestLine.begin(), requestLine.end(), ' ' ) == 2 ) { // No need to check for npos, we determined there are enough spaces in the string size_t firstSpace = requestLine.find( ' ', 0 ); // Bogus check, we checked for the existence of 2 spaces... // A simple assert is not enough to silence cppcheck and coverity. if ( firstSpace == std::string::npos ) throw std::runtime_error("No first space found in request line"); DBG( 3, "firstSpace: ", firstSpace ); method = requestLine.substr( 0, firstSpace ); DBG( 3, "method: ", method ); if ( method.size() == 0 ) throw std::runtime_error( "Not a valid request string: Method is empty" ); size_t secondSpace = requestLine.find( ' ', firstSpace+1 ); // Bogus check, we checked for the existence of 2 spaces... // A simple assert is not enough to silence cppcheck and coverity. if ( secondSpace == std::string::npos ) throw std::runtime_error("No second space found in request line"); DBG( 3, "secondSpace: ", secondSpace ); url = requestLine.substr( firstSpace+1, secondSpace-firstSpace-1 ); DBG( 3, "url: ", url ); if ( url.size() == 0 ) throw std::runtime_error( "Not a valid request string: URL is empty" ); protocol = requestLine.substr( secondSpace+1, std::string::npos ); DBG( 3, "protocol: ", protocol ); if ( protocol.size() == 0 ) throw std::runtime_error( "Not a valid request string: Protocol is empty" ); } else throw std::runtime_error( std::string( "Not a valid request string: Not exactly 3 space separated tokens: " ) + requestLine ); m.setProtocol( protocol ); m.method_ = HTTPMethodProperties::getMethodAsEnum( method ); m.url_ = URL::parse( url ); m.setInitialized(); // m.debugPrint(); std::string line; std::string concatLine; while ( true ) { std::getline( rs, line ); DBG( 3, "Line with whitespace: '", line, "'" ); concatLine += compressLWSAndRemoveCR( line ); DBG( 3, "Line without whitespace: '", line, "'" ); DBG( 3, "ConcatLine: '", concatLine, "'" ); // empty line is separator between headers and body if ( concatLine.empty() ) { break; } // Header spans multiple lines if a line starts with SP or HTAB, fetch another line and append to concatLine if ( rs.peek() == ' ' || rs.peek() == '\t' ) continue; HTTPHeader hh; hh = HTTPHeader::parse( concatLine ); hh.debugPrint(); if ( hh.type() == HeaderType::Invalid ) { // Bad request, throw exception, catch in httpconnection, create response there throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); } m.addHeader( hh ); // Parsing of header done, clear concatLine to start fresh concatLine.clear(); } DBG( 3, "Done parsing headers" ); enum HTTPRequestHasBody hasBody = HTTPMethodProperties::requestHasBody( m.method_ ); DBG( 3, "Request has Body (0 No, 1 Optional, 2 Yes): ", (int)hasBody ); if ( hasBody != HTTPRequestHasBody::No ) { // this mess of code checks if the body will arrive in pieces (chunked) or in one piece and tests the pre-conditions // that belong with them either content-length header or transfer-encoding header, both // means bad request, in case neither is there we need to check if body is optional bool validCL = false; size_t contentLength = 0; bool chunkedTE = false; std::string body( "" ); // cl = Content Length if ( m.hasHeader( "Content-Length" ) ) { HTTPHeader const h = m.getHeader( "Content-Length" ); contentLength = h.headerValueAsNumber(); validCL = true; DBG( 3, "Content-Length: clValue: ", contentLength, ", validCL: ", validCL ); } else { validCL = false; DBG( 3, "Content-Length: header not found." ); } // te = Transfer Encoding if ( m.hasHeader( "Transfer-Encoding" ) ) { HTTPHeader const h = m.getHeader( "Transfer-Encoding" ); std::string teString = h.headerValueAsString(); // Validate header if ( teString.find( "chunked" ) != std::string::npos ) { chunkedTE = true; } else { chunkedTE = false; } DBG( 3, "Transfer-Encoding: teString: ", teString, ", chunkedTE: ", chunkedTE ); } else { DBG( 3, "Transfer-Encoding: header not found " ); chunkedTE = false; } size_t trailerLength = 0; if ( m.hasHeader( "Trailer" ) ) { HTTPHeader const trailer = m.getHeader( "Trailer" ); trailerLength = trailer.headerValueAsList().size(); } else { DBG( 3, "Trailer: header not found " ); } if ( ( chunkedTE && !validCL ) || ( !chunkedTE && validCL ) ) { DBG( 3, "Good request" ); // Good request, get body // but first check if the client sent the Expect header, if so we // need to respond with 100 Continue so it starts transmitting the body std::string expect( "" ); if ( m.hasHeader( "Expect" ) ) { HTTPHeader const h = m.getHeader( "Expect" ); expect = h.headerValueAsString(); } else { expect = ""; } if ( expect == "100-continue" ) { // We have to send a HTTP/1.1 100 Continue response followed by an empty line HTTPResponse resp; resp.setProtocol( HTTPProtocol::HTTP_1_1 ); resp.setResponseCode( HTTPResponseCode::RC_100_Continue ); rs << resp; } else if ( expect != "" ) throw std::runtime_error( "Not a valid Expect header" ); // now load the body if ( chunkedTE ) { m.body_ = m.readChunkedData( rs ); // There is now either a \r\n pair in the stream, or footers/trailers, lets see: std::string remainder; size_t numHeadersAdded = 0; std::getline( rs, remainder, '\n' ); DBG( 3, "Parsing remainder '", remainder, "'" ); while ( remainder[0] != '\r' ) { HTTPHeader hh = HTTPHeader::parse( remainder ); if ( hh.type() == HeaderType::Invalid ) { // Bad request, throw exception, catch in httpconnection, create response there throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); } m.addHeader( hh ); ++numHeadersAdded; } // If trailer contains 3 headers then 3 headers should be added if ( numHeadersAdded != trailerLength ) throw std::runtime_error( "Trailing headers does not match Trailer header content" ); } else { body = m.readData( rs, contentLength ); } } else if ( hasBody == HTTPRequestHasBody::Optional && ! validCL && !chunkedTE ){ // Good request, no body, done return rs; } else { // Bad request, throw exception, catch in connection, create response there throw std::runtime_error( "Bad Request received" ); } } return rs; } // This method is for a client reading a response from the server template basic_socketstream& operator>>( basic_socketstream& rs, HTTPResponse& m ) { DBG( 3, "Reading from the socket" ); // Read something like: HTTP/1.1 403 OK\r\n std::string protocol, statuscode, reasonphrase; rs >> protocol >> statuscode; std::getline( rs, reasonphrase ); if ( rs.fail() ) { DBG( 3, "Could not read from socket, might have been closed due to e.g. timeout" ); throw std::runtime_error( "Could not read from socket, might have been closed due to e.g. timeout" ); } m.setProtocol( protocol ); m.setResponseCode( statuscode ); m.setReasonPhrase( reasonphrase ); //m.debugPrint(); // ignore the '\n' after the protocol //rs.ignore( std::numeric_limits::max(), '\n' ); std::string line; std::string concatLine; while ( true ) { std::getline( rs, line ); DBG( 3, "Line with whitespace: '", line, "'" ); concatLine += compressLWSAndRemoveCR( line ); DBG( 3, "Line without whitespace: '", line, "'" ); DBG( 3, "ConcatLine: '", concatLine, "'" ); // empty line is separator between headers and body if ( concatLine.empty() ) { break; } // Header spans multiple lines if a line starts with SP or HTAB, fetch another line and append to concatLine if ( rs.peek() == ' ' || rs.peek() == '\t' ) continue; HTTPHeader hh; hh = HTTPHeader::parse( concatLine ); if ( hh.type() == HeaderType::Invalid ) { // Bad request, throw exception, catch in httpconnection, create response there throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); } hh.debugPrint(); m.addHeader( hh ); // Parsing of header done, clear concatLine to start fresh concatLine.clear(); } DBG( 3, "Done parsing headers" ); DBG( 3, "Body expected: ", (int)m.bodyExpected() ); if ( m.bodyExpected() ) { bool validCL = false; size_t contentLength = 0; std::string body( "" ); // cl = Content Length if ( m.hasHeader( "Content-Length" ) ) { HTTPHeader const h = m.getHeader( "Content-Length" ); contentLength = h.headerValueAsNumber(); if ( contentLength == 0 ) throw std::runtime_error( "Client: Server did not send a body (cl=0) but we expected one." ); validCL = true; DBG( 3, "Content-Length: clValue: ", contentLength, ", validCL: ", validCL ); } else { validCL = false; DBG( 3, "Content-Length: header not found." ); throw std::runtime_error( "Could not find a Content-Length header so we're not sure how much data is coming, this is a protocol error on the server." ); } body = m.readData( rs, contentLength ); m.addBody( body ); } return rs; } // This method is for a server writing a response to the client template basic_socketstream& operator<<( basic_socketstream& ws, HTTPResponse& m ) { DBG( 3, "Writing the HTTPResponse to the socket" ); m.debugPrint(); DBG( 3, m.protocolAsString(), " ", (int)m.responseCode(), " ", m.responseCodeAsString() ); ws << m.protocolAsString() << " " << (int)m.responseCode() << " " << m.responseCodeAsString() << HTTP_EOL; DBG( 3, "Headers:" ); // write headers for( auto& header : m.headers_ ) { DBG( 3, header.first, ": ", header.second.headerValueAsString() ); if ( header.first == "Content-Type" ) ws << header.first << ": " << header.second.headerValueAsString() << "; charset=UTF-8" << HTTP_EOL; else ws << header.first << ": " << header.second.headerValueAsString() << HTTP_EOL; } ws << HTTP_EOL; DBG( 3, "Body:", m.body() ); ws << m.body(); ws.flush(); DBG( 3, "Written the response to the socket and flushed it" ); return ws; } typedef void (*http_callback)( HTTPServer *, HTTPRequest const &, HTTPResponse & ); class HTTPConnection : public Work { public: HTTPConnection() = delete; #if defined (USE_SSL) HTTPConnection( HTTPServer* hs, int socketFD, struct sockaddr_in /* clientAddr */, std::vector const & cl, SSL* ssl = nullptr ) : hs_( hs ), socketStream_( socketFD, ssl ), /* clientAddress_( clientAddr ), */ callbackList_( cl ) { DBG( 3, "HTTPConnection Constructor called..." ); } #else HTTPConnection( HTTPServer* hs, int socketFD, struct sockaddr_in /* clientAddr */, std::vector const & cl ) : hs_( hs ), socketStream_( socketFD ), /* clientAddress_( clientAddr ), */ callbackList_( cl ) {} #endif HTTPConnection( HTTPConnection const & ) = delete; void operator=( HTTPConnection const & ) = delete; ~HTTPConnection() = default; public: virtual void execute() override { bool keepListening = false; int numRequests = 0; do { HTTPRequest request; HTTPResponse response; try { DBG( 3, "Starting a HTTPConnection read from socket" ); socketStream_ >> request; } catch( std::exception& e ) { DBG( 3, "Reading request from socket: Exception caught: ", e.what(), "\n" ); // Use the protocol that the client used or simply respond with HTTP/1.1 if it could not be determined if ( request.isInitialized() ) { // No need to catch here, if request isInitialized is true then the protocol // is set and there was no throw at that point response.setProtocol( request.protocol() ); } else { response.setProtocol( HTTPProtocol::HTTP_1_1 ); } // Always send a response response.createResponse( TextPlain, std::string( "400 Bad Request " ) + e.what(), RC_400_BadRequest ); socketStream_ << response; break; } DBG( 3, "Request read from socket, processing..." ); ++numRequests; // Debug: // request.debugPrint(); response.setProtocol( request.protocol() ); // Check for protocol conformity if ( request.protocol() == HTTPProtocol::HTTP_1_1 ) { if ( ! request.hasHeader( "Host" ) ) { DBG( 3, "Mandatory Host header not found." ); std::string body( "400 Bad Request. HTTP 1.1: Mandatory Host header is missing." ); response.createResponse( TextPlain, body, RC_400_BadRequest ); socketStream_ << response; break; } } // Do processing of the request here if (*callbackList_[request.method()]) (*callbackList_[request.method()])( hs_, request, response ); else { std::string body( "501 Not Implemented." ); body += " Method \"" + HTTPMethodProperties::getMethodAsString(request.method()) + "\" is not implemented (yet)."; response.createResponse( TextPlain, body, RC_501_NotImplemented ); } // Post-processing, adding some server specific response headers int const requestLimit = 100; int const connectionTimeout = 10; response.addHeader( HTTPHeader( "Server", std::string( "PCMWebServer " ) + PCMWebServerVersion ) ); response.addHeader( HTTPHeader( "Date", datetime().toString() ) ); if ( numRequests < requestLimit ) { std::string connection; if ( request.hasHeader( "Connection" ) ) { HTTPHeader const h = request.getHeader( "Connection" ); connection = h.headerValueAsString(); } else { DBG( 3, "Connection: header not found, this is not an error" ); connection = ""; } // FIXME: case insensitive compare if ( connection == "keep-alive" ) { DBG( 3, "HTTPConnection::execute: keep-alive header found" ); response.addHeader( HTTPHeader( "Connection", "keep-alive" ) ); std::string tmp = "timeout=" + std::to_string(connectionTimeout) + ", max=" + std::to_string( requestLimit ); HTTPHeader header2( "Keep-Alive", tmp ); response.addHeader( header2 ); keepListening = true; } } else { DBG( 3, "Keep-Alive connection request limit (", requestLimit, ") reached" ); // Now respond with the answer response.addHeader( HTTPHeader( "Connection", "close" ) ); keepListening = false; } // Remove body if method is HEAD, it is using the same callback as GET but does not need the body if ( request.method() == HEAD ) { DBG( 1, "Method HEAD, removing body" ); response.addBody( "" ); } response.debugPrint(); DBG( 3, "Writing back the response to the client" ); socketStream_ << response; DBG( 3, "Now flushing the socket" ); socketStream_.flush(); DBG( 3, "Flushed, keep listening: ", keepListening ); } while ( keepListening ); DBG( 3, "Stopped listening and ending this HTTPConnection" ); } private: HTTPServer* hs_; socketstream socketStream_; // struct sockaddr_in clientAddress_; // Not used yet std::vector const & callbackList_; std::vector responseHeader_; std::string responseBody_; std::string protocol_; }; class PeriodicCounterFetcher : public Work { public: PeriodicCounterFetcher( HTTPServer* hs ) : hs_(hs), run_(false), exit_(false) {} virtual ~PeriodicCounterFetcher() override { hs_ = nullptr; } void start( void ) { DBG( 4, "PeriodicCounterFetcher::start() called" ); run_ = true; } void pause( void ) { DBG( 4, "PeriodicCounterFetcher::pause() called" ); run_ = false; } void stop( void ) { DBG( 4, "PeriodicCounterFetcher::stop() called" ); exit_ = true; } virtual void execute() override; private: HTTPServer* hs_; std::atomic run_; std::atomic exit_; }; class HTTPServer : public Server { public: HTTPServer() : Server( "", 80 ), stopped_( false ){ DBG( 3, "HTTPServer::HTTPServer()" ); callbackList_.resize( 256 ); createPeriodicCounterFetcher(); pcf_->start(); SignalHandler::getInstance()->setHTTPServer( this ); } HTTPServer( std::string const & ip, uint16_t port ) : Server( ip, port ), stopped_( false ) { DBG( 3, "HTTPServer::HTTPServer( ip=", ip, ", port=", port, " )" ); callbackList_.resize( 256 ); createPeriodicCounterFetcher(); pcf_->start(); SignalHandler::getInstance()->setHTTPServer( this ); } HTTPServer( HTTPServer const & ) = delete; HTTPServer & operator = ( HTTPServer const & ) = delete; virtual ~HTTPServer() { if ( ! stopped_ ) { DBG( 0, "BUG: HTTPServer or derived class not explicitly stopped before destruction!" ); stop(); } SignalHandler::getInstance()->setHTTPServer( nullptr ); } public: virtual void run() override; void stop() { stopped_ = true; pcf_->stop(); // pcf is a Work object in the threadpool, calling stop makes // it leave the loop and then automatically gets deleted, // we just set it to nullptr here pcf_ = nullptr; // It takes up to one second for a pcf to leave the loop std::this_thread::sleep_for( std::chrono::seconds(1) ); ThreadPool::getInstance().emptyThreadPool(); } // Register Callbacks void registerCallback( HTTPRequestMethod rm, http_callback hc ) { callbackList_[rm] = hc; } void unregisterCallback( HTTPRequestMethod rm ) { callbackList_[rm] = nullptr; } void addAggregator( std::shared_ptr agp ) { DBG( 4, "HTTPServer::addAggregator( agp=", std::hex, agp.get(), " ) called" ); agVectorMutex_.lock(); agVector_.insert( agVector_.begin(), agp ); if ( agVector_.size() > 30 ) { DBG( 4, "HTTPServer::addAggregator(): Removing last Aggegator" ); agVector_.pop_back(); } agVectorMutex_.unlock(); } std::pair,std::shared_ptr> getAggregators( size_t index, size_t index2 ) { if ( index == index2 ) throw std::runtime_error("BUG: getAggregator: both indices are equal. Fix the code!" ); // simply wait until we have enough samples to return while( agVector_.size() < ( std::max( index, index2 ) + 1 ) ) std::this_thread::sleep_for(std::chrono::seconds(1)); agVectorMutex_.lock(); auto ret = std::make_pair( agVector_[ index ], agVector_[ index2 ] ); agVectorMutex_.unlock(); return ret; } bool checkForIncomingSSLConnection( int fd ) { char ch = ' '; ssize_t bytes = ::recv( fd, &ch, 1, MSG_PEEK ); if ( bytes == -1 ) { DBG( 1, "recv call to peek for the first incoming character failed, errno = ", errno, ", strerror: ", strerror(errno) ); throw std::runtime_error( "recv to peek first char failed" ); } else if ( bytes == 0 ) { DBG( 0, "Connection was properly closed by the client, no bytes to read" ); throw std::runtime_error( "No error but the connecton is closed so we should just wait for a new connection again" ); } DBG( 1, "SSL: Peeked Char: ", (EOF == ch) ? std::string("EOF") : std::string(1, ch) ); if ( ch == EOF ) throw std::runtime_error( "Peeking for SSL resulted in EOF" ); // for SSLv2 bit 7 is set and for SSLv3 and up the first ClientHello Message is 0x16 if ( ( ch & 0x80 ) || ( ch == 0x16 ) ) { DBG( 3, "SSL detected" ); return true; } return false; } private: void createPeriodicCounterFetcher() { // We keep a pointer to pcf to start and stop execution // not to delete it when done with it, that is up to threadpool/workqueue pcf_ = new PeriodicCounterFetcher( this ); wq_->addWork( pcf_ ); pcf_->start(); } protected: std::vector callbackList_; std::vector> agVector_; std::mutex agVectorMutex_; PeriodicCounterFetcher* pcf_; bool stopped_; }; // Here to break dependency on HTTPServer void SignalHandler::handleSignal( int signum ) { // Clean up, close socket and such std::cerr << "handleSignal: signal " << signum << " caught.\n"; std::cerr << "handleSignal: closing socket " << networkSocket_ << "\n"; ::close( networkSocket_ ); std::cerr << "Stopping HTTPServer\n"; httpServer_->stop(); std::cerr << "Cleaning up PMU:\n"; PCM::getInstance()->cleanup(); std::cerr << "handleSignal: exiting with exit code 1...\n"; exit(1); } void PeriodicCounterFetcher::execute() { using namespace std::chrono; system_clock::time_point now = system_clock::now(); now = now + std::chrono::seconds(1); std::this_thread::sleep_until( now ); while( 1 ) { if ( exit_ ) break; if ( run_ ) { auto before = steady_clock::now(); // create an aggregator std::shared_ptr sagp = std::make_shared(); assert(sagp.get()); DBG( 4, "PCF::execute(): AGP=", sagp.get(), " )" ); // dispatch it sagp->dispatch( PCM::getInstance()->getSystemTopology() ); // add it to the vector hs_->addAggregator( sagp ); auto after = steady_clock::now(); auto elapsed = duration_cast(after - before); DBG( 4, "Aggregation Duration: ", elapsed.count(), "ms." ); } now = now + std::chrono::seconds(1); std::this_thread::sleep_until( now ); } } void HTTPServer::run() { struct sockaddr_in clientAddress; clientAddress.sin_family = AF_INET; int clientSocketFD = 0; while ( ! stopped_ ) { // Listen on socket for incoming requests socklen_t sa_len = sizeof( struct sockaddr_in ); int retval = ::accept( serverSocket_, (struct sockaddr*)&clientAddress, &sa_len ); if ( -1 == retval ) { DBG( 3, "Accept returned -1, errno: ", strerror( errno ) ); continue; } clientSocketFD = retval; bool clientWantsSSL = false; try { clientWantsSSL = checkForIncomingSSLConnection( clientSocketFD ); } catch( std::exception& e ) { DBG( 3, "Exception during checkForIncomingConnection: ", e.what(), ", closing clientsocketFD" ); ::close( clientSocketFD ); continue; } // HTTPServer so we cannot do SSL if ( clientWantsSSL ) { DBG( 0, "Client wants SSL but we can't speak SSL ourselves" ); // TODO: return a 403 response, then close the connection DBG( 3, "close clientsocketFD" ); ::close( clientSocketFD ); continue; } // Client connected, let's determine the client ip as string. char ipbuf[INET_ADDRSTRLEN]; std::fill(ipbuf, ipbuf + INET_ADDRSTRLEN, 0); char const * resbuf = ::inet_ntop( AF_INET, &(clientAddress.sin_addr), ipbuf, INET_ADDRSTRLEN ); if ( nullptr == resbuf ) { DBG( 3, "inet_ntop returned -1, strerror: ", strerror( errno ) ); DBG( 3, "close clientsocketFD" ); ::close( clientSocketFD ); continue; } int port = ntohs( clientAddress.sin_port ); DBG( 3, "Client IP is: ", ipbuf, ", and the port it uses is : ", port ); HTTPConnection* connection = nullptr; try { connection = new HTTPConnection( this, clientSocketFD, clientAddress, callbackList_ ); } catch ( std::exception& e ) { DBG( 3, "Exception caught while creating a HTTPConnection: " ); deleteAndNullify( connection ); DBG( 3, "close clientsocketFD" ); ::close( clientSocketFD ); continue; } if ( stopped_ ) { // Overkill if you know the program flow but we want to be overly cautious... deleteAndNullify( connection ); break; } wq_->addWork( connection ); } } #if defined (USE_SSL) class HTTPSServer : public HTTPServer { public: HTTPSServer() : HTTPServer( "", 443 ) {} HTTPSServer( std::string const & ip, uint16_t port ) : HTTPServer( ip, port ), sslCTX_( nullptr ) {} HTTPSServer( HTTPSServer const & ) = delete; HTTPSServer & operator = ( HTTPSServer const & ) = delete; virtual ~HTTPSServer() { if ( ! stopped_ ) { DBG( 0, "BUG: HTTPServer or derived class not explicitly stopped before destruction!" ); stop(); } // Program ends after this, no need to set it to nullptr SSL_CTX_free( sslCTX_ ); sslCTX_ = nullptr; // a reuse of sslCTX_ can never happen but we want to be overly cautious. } public: virtual void run() final; public: void setPrivateKeyFile ( std::string const & privateKeyFile ) { privateKeyFile_ = privateKeyFile; } void setCertificateFile( std::string const & certificateFile ) { certificateFile_ = certificateFile; } void initialiseSSL() { if ( nullptr != sslCTX_ ) throw std::runtime_error( "HTTPSServer SSL already initialised" ); if ( privateKeyFile_.empty() ) throw std::runtime_error( "No private key file given" ); if ( certificateFile_.empty() ) throw std::runtime_error( "No certificate file given" ); SSL_library_init(); SSL_load_error_strings(); // SSL too old on development machine, not available yet FIXME //OPENSSL_config(nullptr); // We require 1.1.1 now so TLS_method is available but still // make sure minimum protocol is TSL1_VERSION below sslCTX_ = SSL_CTX_new( TLS_method() ); if ( nullptr == sslCTX_ ) throw std::runtime_error( "Cannot create an SSL context" ); DBG( 3, "SSLCTX set up" ); if( SSL_CTX_set_min_proto_version( sslCTX_, TLS1_VERSION ) != 1 ) throw std::runtime_error( "Cannot set minimum protocol to TSL1_VERSION" ); DBG( 3, "Min TLS Version set" ); if ( SSL_CTX_use_certificate_file( sslCTX_, certificateFile_.c_str(), SSL_FILETYPE_PEM ) <= 0 ) throw std::runtime_error( "Cannot use certificate file" ); DBG( 3, "Certificate file set up" ); if ( SSL_CTX_use_PrivateKey_file( sslCTX_, privateKeyFile_.c_str(), SSL_FILETYPE_PEM ) <= 0 ) throw std::runtime_error( "Cannot use private key file" ); DBG( 3, "Private key set up" ); } private: SSL_CTX* sslCTX_ = nullptr; std::string certificateFile_; std::string privateKeyFile_; }; void HTTPSServer::run() { struct sockaddr_in clientAddress; clientAddress.sin_family = AF_INET; int clientSocketFD = 0; // Check SSL CTX for validity if ( nullptr == sslCTX_ ) throw std::runtime_error( "No SSL_CTX created" ); while ( ! stopped_ ) { // Listen on socket for incoming requests, same as for regular connection socklen_t sa_len = sizeof( struct sockaddr_in ); int retval = ::accept( serverSocket_, (struct sockaddr*)&clientAddress, &sa_len ); DBG( 3, "RegularAccept: (if not -1 it is client socket descriptor) ", retval ); if ( -1 == retval ) { DBG( 3, "Accept failed: strerror( ", errno, " ): ", strerror( errno ) ); continue; } clientSocketFD = retval; bool clientWantsSSL = false; try { clientWantsSSL = checkForIncomingSSLConnection( clientSocketFD ); } catch( std::exception& e ) { DBG( 3, "Exception during checkForIncomingConnection: ", e.what(), ", closing clientsocketFD" ); ::close( clientSocketFD ); continue; } // HTTPSServer so we want to do SSL if ( ! clientWantsSSL ) { DBG( 0, "Client wants Plain HTTP but we want to speak SSL ourselves" ); // TODO: return a 403 response, then close the connection DBG( 3, "close clientsocketFD" ); ::close( clientSocketFD ); continue; } // Create and setup SSL on the socket SSL* ssl = SSL_new( sslCTX_ ); if (ssl == nullptr ) { DBG( 3, "We're in big trouble, we could not create an SSL object with the SSL_CTX..." ); throw std::runtime_error( "Could not create SSL object" ); } int ret = SSL_set_fd( ssl, clientSocketFD ); DBG( 3, "set_fd: ret = ", ret ); if (ret == 0 ) { DBG( 3, "SSL_set_fd returned 0, oops...", ret ); throw std::runtime_error("SSL_set_fd returned 0, oops..."); } bool cleanupAndRestartListening = false; while (1) { bool leaveLoop = true; // Check if the SSL handshake worked int accept = SSL_accept( ssl ); DBG( 3, "SSL_accept: ", accept ); if ( 0 >= accept ) { int errorCode = SSL_get_error( ssl, accept ); if ( errorCode == SSL_ERROR_ZERO_RETURN ) { // TLS/SSL Connection has been closed, socket may not though cleanupAndRestartListening = true; break; } int err = 0; char buf[256]; DBG( 3, "errorCode: ", errorCode ); switch ( errorCode ) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: // All good, just try again leaveLoop = false; break; case SSL_ERROR_SSL: case SSL_ERROR_SYSCALL: err = ERR_get_error(); DBG( 3, "ERR_get_error(): ", err ); ERR_error_string( err, buf ); DBG( 3, "ERR_error_string(): ", buf ); cleanupAndRestartListening = true; break; default: DBG( 3, "Unhandled SSL Error: ", errorCode ); ERR_print_errors_fp( stderr); cleanupAndRestartListening = true; } } ERR_clear_error(); // Clear error because SSL_get_error does not do so if ( leaveLoop ) break; } if ( cleanupAndRestartListening ) { // Here we still have not passed it to socket_buffer so we need to deal with shutdown properly. DBG( 3, "SSL Accept: error accepting incoming connection, closing the FD and continuing: " ); closeSSLConnectionAndFD( clientSocketFD, ssl ); continue; } DBG( 1, "Server: client connected successfully, starting a new HTTPConnection" ); // Client connected, let's determine the client ip as string. char ipbuf[INET_ADDRSTRLEN]; memset( ipbuf, 0, 16 ); char const * resbuf = ::inet_ntop( AF_INET, &(clientAddress.sin_addr), ipbuf, INET_ADDRSTRLEN ); if ( nullptr == resbuf ) { DBG( 3, "inet_ntop returned an error: ", errno, ", error string: ", strerror( errno ), "\n"); ERR_clear_error(); SSL_free( ssl ); // Free the SSL structure to prevent memory leaks ssl = nullptr; DBG( 3, "close clientsocketFD" ); ::close( clientSocketFD ); continue; } int port = ntohs( clientAddress.sin_port ); DBG( 3, "Client IP is: ", ipbuf, ", and the port it uses is : ", port ); DBG( 3, "SSL info: version: ", SSL_get_version( ssl ), ", stuff" ); // Ownership of ssl is now passed to HTTPConnection, it will delete ssl when done HTTPConnection* connection = new HTTPConnection( this, clientSocketFD, clientAddress, callbackList_, ssl ); ssl = nullptr; if ( stopped_ ) { // Overkill if you know the program flow but we want to be overly cautious... deleteAndNullify( connection ); break; } wq_->addWork( connection ); } } #endif // USE_SSL // Hack needed to convert from unsigned char to signed char, favicon.h is changed to have _uc for each char inline constexpr signed char operator "" _uc( unsigned long long arg ) noexcept { return static_cast(arg); } #include "favicon.ico.h" std::pair,std::shared_ptr> getNullAndCurrentAggregator() { std::shared_ptr current = std::make_shared(); std::shared_ptr null = std::make_shared(); assert(current.get()); current->dispatch( PCM::getInstance()->getSystemTopology() ); return std::make_pair( null, current ); } enum OutputFormat { Prometheus_0_0_4 = 1, JSON, HTML, XML, PlainText, OutputFormat_Spare = 255 }; std::unordered_map> mimeTypeToOutputFormat = { { TextHTML, HTML }, { TextXML, XML }, { ApplicationJSON, JSON }, { TextPlainProm_0_0_4, Prometheus_0_0_4 }, { CatchAll, HTML } }; std::unordered_map> supportedOutputMimeTypes = { { TextPlainProm_0_0_4, "text/plain;version=0.0.4" }, { ApplicationJSON, "application/json" } }; enum MimeType matchSupportedWithAcceptedMimeTypes( HTTPHeader const& h ) { auto list = h.headerValueAsList(); // TODO: We should actually build up a list of accepted mimetypes and their preference, sort // the list and then compare against it. We now use the inherent order as preference which // is not entirely accurate but is good enough. for ( auto& item : list ) { DBG( 2, "Item: \"", item, "\"" ); // Search for preference and remove it auto copy = item; size_t pos; // Using erase with npos as second parameter to be explicit about the intent: delete until end if ( std::string::npos != ( pos = item.find( "q=", 0 ) ) ){ // found it, remove q=... copy.erase( pos, std::string::npos ); DBG( 2, "q= found and erased: \"", copy, "\"" ); if ( std::string::npos != ( pos = item.rfind( ";", pos ) ) ) { // remove trailing ; copy.erase( pos, std::string::npos ); DBG( 2, "trailing ';' found and erased: \"", copy, "\"" ); } } // remove all whitespace from the item copy.erase( std::remove_if( copy.begin(), copy.end(), isspace ), copy.end() ); // compare mimetype with supported ones for ( auto& mimetype : supportedOutputMimeTypes ) { auto str = mimetype.second; str.erase( std::remove_if( str.begin(), str.end(), isspace ), str.end() ); DBG( 2, "Comparing mimetype '", copy, "' with known Mimetype '", str, "'" ); if ( str == copy ) { DBG( 2, "Found a match!" ); return mimetype.first; } } } return CatchAll; } /* Normally the Accept Header decides what format is returned but certain endpoints can override this, * therefore we have a separate enum for output format */ void my_get_callback( HTTPServer* hs, HTTPRequest const & req, HTTPResponse & resp ) { enum MimeType mt; enum OutputFormat format; HTTPHeader accept; if ( req.hasHeader( "Accept" ) ) { accept = req.getHeader( "Accept" ); mt = matchSupportedWithAcceptedMimeTypes( accept ); } else { // If there is no accept header then the assumption is that the client can handle anything mt = CatchAll; } format = mimeTypeToOutputFormat[ mt ]; URL url; url = req.url(); DBG( 3, "PATH=\"", url.path_, "\", size=", url.path_.size() ); if ( url.path_ == "/favicon.ico" ) { DBG( 3, "my_get_callback: client requesting '/favicon.ico'" ); std::string favicon( favicon_ico, favicon_ico + favicon_ico_len ); resp.createResponse( ImageXIcon, favicon, RC_200_OK ); return; } std::pair,std::shared_ptr> aggregatorPair; if ( (1 == url.path_.size()) && (url.path_ == "/") ) { DBG( 3, "my_get_callback: client requesting '/'" ); // If it is not Prometheus and not JSON just return this html code // It might violate the protocol but it makes coding this easier if ( ApplicationJSON != mt && TextPlainProm_0_0_4 != mt ) { // If you make changes to the HTML, please validate it // Probably best to put this in static files and serve this std::string body = "\ \n\ \n\ \n\ PCM Sensor Server\n\ \n\ \n\

PCM Sensor Server

\n\

PCM Sensor Server provides performance counter data through an HTTP interface. By default this text is served when requesting the endpoint \"/\".

\n\

The endpoints for retrieving counter data, /, /persecond and /persecond/X, support returning data in JSON or prometheus format. For JSON have your client send the HTTP header \"Accept: application/json\" and for prometheus \"Accept: text/plain; version=0.0.4\" along with the request, PCM Sensor Server will then return the counter data in the requested format.

\n\

Endpoints you can call are:

\n\
    \n\
  • / : This will fetch the counter values since start of the daemon, minus overflow so should be considered absolute numbers and should be used for further processing by yourself.
  • \n\
  • /persecond : This will fetch data from the internal sample thread which samples every second and returns the difference between the last 2 samples.
  • \n\
  • /persecond/X : This will fetch data from the internal sample thread which samples every second and returns the difference between the last 2 samples which are X seconds apart. X can be at most 30 seconds without changing the source code.
  • \n\
  • /metrics : The Prometheus server does not send an Accept header to decide what format to return so it got its own endpoint that will always return data in the Prometheus format. pcm-sensor-server is sending the header \"Content-Type: text/plain; version=0.0.4\" as required. This /metrics endpoints mimics the same behavior as / and data is thus absolute, not relative.
  • \n\
  • /dashboard/influxdb : This will return JSON for a Grafana dashboard with InfluxDB backend that holds all counters. Please see the documentation for more information.
  • \n\
  • /dashboard/prometheus : This will return JSON for a Grafana dashboard with Prometheus backend that holds all counters. Please see the documentation for more information.
  • \n\
  • /dashboard/prometheus/default : Same as /dashboard/prometheus but tuned for existing installations with default Prometheus scrape period of 15 seconds and the rate of 1 minute in Grafana. Please see the documentation for more information.
  • \n\
  • /dashboard : same as /dashboard/influxdb
  • \n\
  • /favicon.ico : This will return a small favicon.ico as requested by many browsers.
  • \n\
\n\ \n\ \n"; resp.createResponse( TextHTML, body, RC_200_OK ); return; } //std::shared_ptr current; //std::shared_ptr null; //current = std::make_shared(); //null = std::make_shared(); //current->dispatch( PCM::getInstance()->getSystemTopology() ); //aggregatorPair = std::make_pair( null, current ); aggregatorPair = getNullAndCurrentAggregator(); } else if ( url.path_ == "/dashboard" || url.path_ == "/dashboard/influxdb") { DBG( 3, "client requesting /dashboard path: '", url.path_, "'" ); resp.createResponse( ApplicationJSON, getPCMDashboardJSON(InfluxDB), RC_200_OK ); return; } else if (url.path_ == "/dashboard/prometheus") { DBG( 3, "client requesting /dashboard path: '", url.path_, "'"); resp.createResponse(ApplicationJSON, getPCMDashboardJSON(Prometheus), RC_200_OK); return; } else if (url.path_ == "/dashboard/prometheus/default") { DBG( 3, "client requesting /dashboard path: '", url.path_, "'"); resp.createResponse(ApplicationJSON, getPCMDashboardJSON(Prometheus_Default), RC_200_OK); return; } else if ( 0 == url.path_.rfind( "/persecond", 0 ) ) { DBG( 3, "client requesting /persecond path: '", url.path_, "'" ); if ( 10 == url.path_.size() || ( 11 == url.path_.size() && url.path_.at(10) == '/' ) ) { DBG( 3, "size == 10 or 11" ); // path looks like /persecond or /persecond/ aggregatorPair = hs->getAggregators( 1, 0 ); } else { DBG( 3, "size > 11: size = ", url.path_.size() ); // We're looking for value X after /persecond/X and possibly a trailing / anything else not url.path_.erase( 0, 10 ); // remove /persecond DBG( 3, "after removal: path = \"", url.path_, "\", size = ", url.path_.size() ); if ( url.path_.at(0) == '/' ) { url.path_.erase( 0, 1 ); if ( url.path_.at( url.path_.size() - 1 ) == '/' ) { url.path_.pop_back(); } if ( std::all_of( url.path_.begin(), url.path_.end(), ::isdigit ) ) { size_t seconds; try { seconds = std::stoll( url.path_ ); } catch ( std::exception& e ) { DBG( 3, "Error during conversion of /persecond/ seconds: ", e.what() ); seconds = 0; } if ( 1 <= seconds && 30 >= seconds ) { aggregatorPair = hs->getAggregators( seconds, 0 ); } else { DBG( 3, "seconds equals 0 or seconds larger than 30 is not allowed" ); std::string body( "400 Bad Request. seconds equals 0 or seconds larger than 30 is not allowed" ); resp.createResponse( TextPlain, body, RC_400_BadRequest ); return; } } else { DBG( 3, "/persecond/ Not followed by all numbers" ); std::string body( "400 Bad Request Request starts with /persecond/ but is not followed by numbers only." ); resp.createResponse( TextPlain, body, RC_400_BadRequest ); return; } } else { DBG( 3, "/persecond something requested: something=\"", url.path_, "\"" ); std::string body( "404 Bad Request. Request starts with /persecond but contains bad characters." ); resp.createResponse( TextPlain, body, RC_404_NotFound ); return; } } } else if ( 8 == url.path_.size() && 0 == url.path_.find( "/metrics", 0 ) ) { DBG( 3, "Special snowflake prometheus wants a /metrics URL, it can't be bothered to use its own mimetype in the Accept header" ); format = Prometheus_0_0_4; aggregatorPair = getNullAndCurrentAggregator(); } else { DBG( 3, "Unknown path requested: \"", url.path_, "\"" ); std::string body( "404 Unknown path." ); resp.createResponse( TextPlain, body, RC_404_NotFound ); return; } switch ( format ) { case JSON: { JSONPrinter jp( aggregatorPair ); jp.dispatch( PCM::getInstance()->getSystemTopology() ); resp.createResponse( ApplicationJSON, jp.str(), RC_200_OK ); break; } case Prometheus_0_0_4: { PrometheusPrinter pp( aggregatorPair ); pp.dispatch( PCM::getInstance()->getSystemTopology() ); resp.createResponse( TextPlainProm_0_0_4, pp.str(), RC_200_OK ); break; } default: std::string body( "406 Not Acceptable. Server can only serve \"" ); body += req.url().path_ + "\" as application/json or \"text/plain; version=0.0.4\" (prometheus format)."; resp.createResponse( TextPlain, body, RC_406_NotAcceptable ); } } int startHTTPServer( unsigned short port ) { HTTPServer server( "", port ); try { // HEAD is GET without body, we will remove the body in execute() server.registerCallback( HTTPRequestMethod::GET, my_get_callback ); server.registerCallback( HTTPRequestMethod::HEAD, my_get_callback ); server.run(); } catch (std::exception & e) { std::cerr << "Exception caught: " << e.what() << "\n"; return -1; } return 0; } #if defined (USE_SSL) int startHTTPSServer( unsigned short port, std::string const & cFile, std::string const & pkFile) { HTTPSServer server( "", port ); try { server.setPrivateKeyFile ( pkFile ); server.setCertificateFile( cFile ); server.initialiseSSL(); // HEAD is GET without body, we will remove the body in execute() server.registerCallback( HTTPRequestMethod::GET, my_get_callback ); server.registerCallback( HTTPRequestMethod::HEAD, my_get_callback ); server.run(); } catch (std::exception & e) { std::cerr << "Exception caught: " << e.what() << "\n"; return -1; } return 0; } #endif void printHelpText( std::string const & programName ) { std::cout << "Usage: " << programName << " [OPTION]\n\n"; std::cout << "Valid Options:\n"; std::cout << " -d : Run in the background\n"; #if defined (USE_SSL) std::cout << " -s : Use https protocol (default port " << DEFAULT_HTTPS_PORT << ")\n"; #endif std::cout << " -p portnumber : Run on port (default port is " << DEFAULT_HTTP_PORT << ")\n"; std::cout << " -r|--reset : Reset programming of the performance counters.\n"; std::cout << " -D|--debug level : level = 0: no debug info, > 0 increase verbosity.\n"; #ifndef __APPLE__ std::cout << " -R|--real-time : If possible the daemon will run with real time\n"; #endif std::cout << " priority, could be useful under heavy load to \n"; std::cout << " stabilize the async counter fetching.\n"; #if defined (USE_SSL) std::cout << " -C|--certificateFile : \n"; std::cout << " -P|--privateKeyFile : \n"; #endif std::cout << " -h|--help : This information\n"; std::cout << " -silent : Silence information output and print only measurements\n"; std::cout << " --version : Print application version\n"; print_help_force_rtm_abort_mode(25, ":"); } #if not defined( UNIT_TEST ) /* Main */ PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); // Argument handling bool daemonMode = false; #if defined (USE_SSL) bool useSSL = false; #endif bool forcedProgramming = false; #ifndef __APPLE__ bool useRealtimePriority = false; #endif bool forceRTMAbortMode = false; bool printTopology = false; unsigned short port = 0; unsigned short debug_level = 0; std::string certificateFile; std::string privateKeyFile; AcceleratorCounterState *accs_; accs_ = AcceleratorCounterState::getInstance(); null_stream nullStream; check_and_set_silent(argc, argv, nullStream); ACCEL_IP accel=ACCEL_NOCONFIG; //default is IAA bool evtfile = false; std::string specify_evtfile; // ACCEL_DEV_LOC_MAPPING loc_map = SOCKET_MAP; //default is socket mapping MainLoop mainLoop; std::string ev_file_name; const char* PPTEnv = std::getenv( "PCMSENSORSERVER_PRINT_TOPOLOGY" ); if ( PPTEnv ) { if ( *PPTEnv == '1' ) { printTopology = true; } } else if ( argc > 1 ) { std::string arg_value; for ( int i=1; i < argc; ++i ) { if ( check_argument_equals( argv[i], {"-d"} ) ) daemonMode = true; else if ( check_argument_equals( argv[i], {"-p"} ) ) { if ( (++i) < argc ) { std::stringstream ss( argv[i] ); try { ss >> port; } catch( std::exception& e ) { std::cerr << "main: port number is not an unsigned short!\n"; ::exit( 2 ); } } else { throw std::runtime_error( "main: Error no port argument given" ); } } #if defined (USE_SSL) else if ( check_argument_equals( argv[i], {"-s"} ) ) { useSSL = true; } #endif else if ( check_argument_equals( argv[i], {"-r", "--reset"} ) ) { forcedProgramming = true; } else if ( check_argument_equals( argv[i], {"-D", "--debug"} ) ) { if ( (++i) < argc ) { std::stringstream ss( argv[i] ); try { ss >> debug_level; } catch( std::exception& e ) { std::cerr << "main: debug level is not an unsigned short!\n"; ::exit( 2 ); } } else { throw std::runtime_error( "main: Error no debug level argument given" ); } } #ifndef __APPLE__ else if ( check_argument_equals( argv[i], {"-R", "--real-time"} ) ) { useRealtimePriority = true; } #endif else if ( check_argument_equals( argv[i], {"--help", "-h", "/h"} ) ) { printHelpText( argv[0] ); exit(0); } else if (check_argument_equals( argv[i], { "-force-rtm-abort-mode" })) { forceRTMAbortMode = true; } else if (check_argument_equals(argv[i], {"-iaa", "/iaa"})) { accel = ACCEL_IAA; } else if (check_argument_equals(argv[i], {"-dsa", "/dsa"})) { accel = ACCEL_DSA; std::cout << "Aggregator firstest : " << accs_->getAccelCounterName() << accel; } #ifdef __linux__ else if (check_argument_equals(argv[i], {"-qat", "/qat"})) { accel = ACCEL_QAT; } // else if (check_argument_equals(argv[i], {"-numa", "/numa"})) // { // loc_map = NUMA_MAP; // } #endif else if (extract_argument_value(argv[i], {"-evt", "/evt"}, arg_value)) { evtfile = true; specify_evtfile = std::move(arg_value); } else if ( check_argument_equals( argv[i], {"-silent", "/silent"} ) ) { // handled in check_and_set_silent continue; } #if defined (USE_SSL) else if ( check_argument_equals( argv[i], {"-C", "--certificateFile"} ) ) { if ( (++i) < argc ) { std::ifstream fp( argv[i] ); if ( ! fp.is_open() ) { std::cerr << "Cannot open certificate file \"" << argv[i] << "\".\n"; printHelpText( argv[0] ); exit( 3 ); } certificateFile = argv[i]; } else { std::cerr << "Missing certificate file argument.\n"; printHelpText( argv[0] ); exit( 3 ); } } else if ( check_argument_equals( argv[i], {"-P", "--privateKeyFile"} ) ) { if ( (++i) < argc ) { std::ifstream fp( argv[i] ); if ( ! fp.is_open() ) { std::cerr << "Cannot open private key file \"" << argv[i] << "\".\n"; printHelpText( argv[0] ); exit( 4 ); } privateKeyFile = argv[i]; } else { std::cerr << "Missing private key file argument.\n"; printHelpText( argv[0] ); exit( 4 ); } } #endif else throw std::runtime_error( "Unknown argument" ); } } #ifdef __linux__ // check kernel version for driver dependency. if (accel != ACCEL_NOCONFIG) { std::cout << "Info: IDX - Please ensure the required driver(e.g idxd driver for iaa/dsa, qat driver and etc) correct enabled with this system, else the tool may fail to run.\n"; struct utsname sys_info; if (!uname(&sys_info)) { std::string krel_str; uint32 krel_major_ver=0, krel_minor_ver=0; krel_str = sys_info.release; std::vector krel_info = split(krel_str, '.'); std::istringstream iss_krel_major(krel_info[0]); std::istringstream iss_krel_minor(krel_info[1]); iss_krel_major >> std::setbase(0) >> krel_major_ver; iss_krel_minor >> std::setbase(0) >> krel_minor_ver; switch (accel) { case ACCEL_IAA: case ACCEL_DSA: if ((krel_major_ver < 5) || (krel_major_ver == 5 && krel_minor_ver < 11)) { std::cout<< "Warning: IDX - current linux kernel version(" << krel_str << ") is too old, please upgrade it to the latest due to required idxd driver integrated to kernel since 5.11.\n"; } break; default: std::cout<< "Info: Chosen "<< accel<<" IDX - current linux kernel version(" << krel_str << ")"; } } } #endif debug::dyn_debug_level( debug_level ); #if defined (USE_SSL) if ( useSSL ) { if ( certificateFile.empty() || privateKeyFile.empty() ) { std::cerr << "Error: wanting to use SSL but missing certificate and or private key file(s).\n"; printHelpText( argv[0] ); exit( 5 ); } } #endif #ifndef __APPLE__ if ( useRealtimePriority ) { int priority = sched_get_priority_min( SCHED_RR ); if ( priority == -1 ) { std::cerr << "Could not get SCHED_RR min priority: " << strerror( errno ) << "\n"; exit( 6 ); } else { struct sched_param sp = { .sched_priority = priority }; if ( sched_setscheduler(0, SCHED_RR, &sp ) == -1 ) { int errnosave = errno; std::cerr << "Could not set scheduler to realtime! Errno: " << errnosave << "\n"; std::cerr << "Error message: \"" << strerror( errnosave ) << "\"\n"; exit( 6 ); } else { std::cerr << "Scheduler changed to SCHED_RR and priority to " << priority << "\n"; } } } #endif pid_t pid; if ( daemonMode ) pid = fork(); else pid = 0; if ( pid == 0 ) { /* child */ // Default programming is to use normal core counters and memory bandwidth counters // and if pmem is available to also show this instead of partial writes // A HTTP interface to change the programming is planned PCM::ErrorCode status; PCM * pcmInstance = PCM::getInstance(); pcmInstance->setAccel(accel); assert(pcmInstance); if (forceRTMAbortMode) { pcmInstance->enableForceRTMAbortMode(); } do { status = pcmInstance->program(); switch ( status ) { case PCM::PMUBusy: { if ( forcedProgramming == false ) { std::cout << "Warning: PMU appears to be busy, do you want to reset it? (y/n)\n"; char answer; std::cin >> answer; if ( answer == 'y' || answer == 'Y' ) pcmInstance->resetPMU(); else exit(0); } else { pcmInstance->resetPMU(); } break; } case PCM::Success: break; case PCM::MSRAccessDenied: case PCM::UnknownError: default: exit(1); } } while( status != PCM::Success ); if ( pcmInstance->PMMTrafficMetricsAvailable() ) { DBG( 1, "Programmed PMEM R/W BW instead of Partial Writes" ); } else { DBG( 1, "Programmed Partial Writes instead of PMEM R/W BW" ); } //TODO: check return value when its implemented pcmInstance->programCXLCM(); if (pcmInstance->getAccel()!=ACCEL_NOCONFIG) { if (pcmInstance->supportIDXAccelDev() == false) { std::cerr << "Error: IDX accelerator is NOT supported with this platform! Program aborted\n"; exit(EXIT_FAILURE); } accs_->setEvents(pcmInstance,accel,specify_evtfile,evtfile); accs_->programAccelCounters(); } if ( printTopology ) { TopologyPrinter* tp = new TopologyPrinter(); tp->dispatch( PCM::getInstance()->getSystemTopology() ); std::vector & tpData = tp->topologyDataStrings(); std::sort( tpData.begin(), tpData.end(), TopologyStringCompare ); for( auto& line: tpData ) { std::cout << line << "\n"; } deleteAndNullify( tp ); exit( 0 ); } #if defined (USE_SSL) if ( useSSL ) { if ( port == 0 ) port = DEFAULT_HTTPS_PORT; std::cerr << "Starting SSL enabled server on https://localhost:" << port << "/\n"; startHTTPSServer( port, certificateFile, privateKeyFile ); } else #endif { if ( port == 0 ) port = DEFAULT_HTTP_PORT; std::cerr << "Starting plain HTTP server on http://localhost:" << port << "/\n"; startHTTPServer( port ); } delete pcmInstance; } else if ( pid > 0 ) { /* Parent, just leave */ DBG( 2, "Child pid: ", pid ); return 0; } else { /* Error */ DBG( 2, "Error forking. " ); return 200; } return 0; } #endif // UNIT_TEST pcm-202502/src/pcm-sensor-server.service.in000066400000000000000000000003651475730356400205570ustar00rootroot00000000000000[Unit] Description=Intel Performance Counter Monitor (PCM) Sensor Service Wants=network-online.target After=network-online.target [Service] Type=simple ExecStart=@@CMAKE_INSTALL_SBINDIR@@/pcm-sensor-server [Install] WantedBy=multi-user.target pcm-202502/src/pcm-sensor.cpp000066400000000000000000000745441475730356400160020ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // // monitor CPU conters for ksysguard // // contact: Thomas Willhalm, Patrick Ungerer, Roman Dementiev // // This program is not a tutorial on how to write nice interpreters // but a proof of concept on using ksysguard with performance counters // /*! \file pcm-sensor.cpp \brief Example of using CPU counters: implements a graphical plugin for KDE ksysguard */ #include #include #include #include "cpuasynchcounter.h" #include "utils.h" using namespace std; using namespace pcm; PCM_MAIN_NOTHROW; int mainThrows(int /* argc */, char * /*argv*/ []) { set_signal_handlers(); AsynchronCounterState counters; cout << "CPU counter sensor " << PCM_VERSION << "\n"; cout << "ksysguardd 1.2.0\n"; cout << "ksysguardd> "; while (1) { string s; cin >> s; const auto xpi = counters.getXpi(); // list counters if (s == "monitors") { for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { cout << "Socket" << a << "/CPU" << i << "/Frequency\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/IPC\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/L2CacheHitRatio\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/L3CacheHitRatio\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/L2CacheMisses\tinteger\n"; cout << "Socket" << a << "/CPU" << i << "/L3CacheMisses\tinteger\n"; cout << "Socket" << a << "/CPU" << i << "/L3Occupancy\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/LocalMemoryBandwidth\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/RemoteMemoryBandwidth\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/CoreC0StateResidency\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/CoreC3StateResidency\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/CoreC6StateResidency\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/CoreC7StateResidency\tfloat\n"; cout << "Socket" << a << "/CPU" << i << "/ThermalHeadroom\tinteger\n"; } } for (uint32 a = 0; a < counters.getNumSockets(); ++a) { cout << "Socket" << a << "/BytesReadFromMC\tfloat\n"; cout << "Socket" << a << "/BytesWrittenToMC\tfloat\n"; cout << "Socket" << a << "/BytesReadFromPMM\tfloat\n"; cout << "Socket" << a << "/BytesWrittenToPMM\tfloat\n"; cout << "Socket" << a << "/Frequency\tfloat\n"; cout << "Socket" << a << "/IPC\tfloat\n"; cout << "Socket" << a << "/L2CacheHitRatio\tfloat\n"; cout << "Socket" << a << "/L3CacheHitRatio\tfloat\n"; cout << "Socket" << a << "/L2CacheMisses\tinteger\n"; cout << "Socket" << a << "/L3CacheMisses\tinteger\n"; cout << "Socket" << a << "/L3Occupancy\tfloat\n"; cout << "Socket" << a << "/LocalMemoryBandwidth\tfloat\n"; cout << "Socket" << a << "/RemoteMemoryBandwidth\tfloat\n"; cout << "Socket" << a << "/CoreC0StateResidency\tfloat\n"; cout << "Socket" << a << "/CoreC3StateResidency\tfloat\n"; cout << "Socket" << a << "/CoreC6StateResidency\tfloat\n"; cout << "Socket" << a << "/CoreC7StateResidency\tfloat\n"; cout << "Socket" << a << "/PackageC2StateResidency\tfloat\n"; cout << "Socket" << a << "/PackageC3StateResidency\tfloat\n"; cout << "Socket" << a << "/PackageC6StateResidency\tfloat\n"; cout << "Socket" << a << "/PackageC7StateResidency\tfloat\n"; cout << "Socket" << a << "/ThermalHeadroom\tinteger\n"; cout << "Socket" << a << "/CPUEnergy\tfloat\n"; cout << "Socket" << a << "/DRAMEnergy\tfloat\n"; } for (uint32 a = 0; a < counters.getNumSockets(); ++a) { for (uint32 l = 0; l < counters.getQPILinksPerSocket(); ++l) cout << "Socket" << a << "/BytesIncomingTo" << xpi << l << "\tfloat\n"; } cout << xpi << "_Traffic\tfloat\n"; cout << "Frequency\tfloat\n"; cout << "IPC\tfloat\n"; //double check output cout << "L2CacheHitRatio\tfloat\n"; cout << "L3CacheHitRatio\tfloat\n"; cout << "L2CacheMisses\tinteger\n"; cout << "L3CacheMisses\tinteger\n"; cout << "CoreC0StateResidency\tfloat\n"; cout << "CoreC3StateResidency\tfloat\n"; cout << "CoreC6StateResidency\tfloat\n"; cout << "CoreC7StateResidency\tfloat\n"; cout << "PackageC2StateResidency\tfloat\n"; cout << "PackageC3StateResidency\tfloat\n"; cout << "PackageC6StateResidency\tfloat\n"; cout << "PackageC7StateResidency\tfloat\n"; cout << "CPUEnergy\tfloat\n"; cout << "DRAMEnergy\tfloat\n"; } // provide metadata for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { { stringstream c; c << "Socket" << a << "/CPU" << i << "/Frequency?"; if (s == c.str()) { cout << "FREQ. CPU" << i << "\t\t\tMHz\n"; } } { stringstream c; c << "Socket" << a << "/CPU" << i << "/ThermalHeadroom?"; if (s == c.str()) { cout << "Temperature reading in 1 degree Celsius relative to the TjMax temperature (thermal headroom) for CPU" << i << "\t\t\t°C\n"; } } { stringstream c; c << "Socket" << a << "/CPU" << i << "/CoreC0StateResidency?"; if (s == c.str()) { cout << "core C0-state residency for CPU" << i << "\t\t\t%\n"; } } { stringstream c; c << "Socket" << a << "/CPU" << i << "/CoreC3StateResidency?"; if (s == c.str()) { cout << "core C3-state residency for CPU" << i << "\t\t\t%\n"; } } { stringstream c; c << "Socket" << a << "/CPU" << i << "/CoreC6StateResidency?"; if (s == c.str()) { cout << "core C6-state residency for CPU" << i << "\t\t\t%\n"; } } { stringstream c; c << "Socket" << a << "/CPU" << i << "/CoreC7StateResidency?"; if (s == c.str()) { cout << "core C7-state residency for CPU" << i << "\t\t\t%\n"; } } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/IPC?"; if (s == c.str()) { cout << "IPC CPU" << i << "\t0\t\t\n"; //cout << "CPU" << i << "\tInstructions per Cycle\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/L2CacheHitRatio?"; if (s == c.str()) { cout << "L2 Cache Hit Ratio CPU" << i << "\t0\t\t\n"; // cout << "CPU" << i << "\tL2 Cache Hit Ratio\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/L3CacheHitRatio?"; if (s == c.str()) { cout << "L3 Cache Hit Ratio CPU" << i << "\t0\t\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/L2CacheMisses?"; if (s == c.str()) { cout << "L2 Cache Misses CPU" << i << "\t0\t\t \n"; //cout << "CPU" << i << "\tL2 Cache Misses\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/L3CacheMisses?"; if (s == c.str()) { cout << "L3 Cache Misses CPU" << i << "\t0\t\t \n"; //cout << "CPU" << i << "\tL3 Cache Misses\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/L3Occupancy?"; if (s == c.str()) { cout << "L3 Cache Occupancy CPU " << i << "\t0\t\t \n"; //cout << "CPU" << i << "\tL3 Cache Occupancy\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/LocalMemoryBandwidth?"; if (s == c.str()) { cout << "Local Memory Bandwidth CPU " << i << "\t0\t\t \n"; //cout << "CPU" << i << "\tLocal Memory Bandwidth\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumCores(); ++i) { for (uint32 a = 0; a < counters.getNumSockets(); ++a) if (a == counters.getSocketId(i)) { stringstream c; c << "Socket" << a << "/CPU" << i << "/RemoteMemoryBandwidth?"; if (s == c.str()) { cout << "Remote Memory Bandwidth CPU " << i << "\t0\t\t \n"; //cout << "CPU" << i << "\tRemote Memory Bandwidth\t0\t1\t \n"; } } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/BytesReadFromMC?"; if (s == c.str()) { cout << "read from MC Socket" << i << "\t0\t\tGB\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/BytesReadFromPMM?"; if (s == c.str()) { cout << "read from PMM memory on Socket" << i << "\t0\t\tGB\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/DRAMEnergy?"; if (s == c.str()) { cout << "Energy consumed by DRAM on socket " << i << "\t0\t\tJoule\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/CPUEnergy?"; if (s == c.str()) { cout << "Energy consumed by CPU package " << i << "\t0\t\tJoule\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/ThermalHeadroom?"; if (s == c.str()) { cout << "Temperature reading in 1 degree Celsius relative to the TjMax temperature (thermal headroom) for CPU package " << i << "\t0\t\t°C\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/CoreC0StateResidency?"; if (s == c.str()) { cout << "core C0-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/CoreC3StateResidency?"; if (s == c.str()) { cout << "core C3-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/CoreC6StateResidency?"; if (s == c.str()) { cout << "core C6-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/CoreC7StateResidency?"; if (s == c.str()) { cout << "core C7-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/PackageC2StateResidency?"; if (s == c.str()) { cout << "package C2-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/PackageC3StateResidency?"; if (s == c.str()) { cout << "package C3-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/PackageC6StateResidency?"; if (s == c.str()) { cout << "package C6-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/PackageC7StateResidency?"; if (s == c.str()) { cout << "package C7-state residency for CPU package " << i << "\t0\t\t%\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/BytesWrittenToPMM?"; if (s == c.str()) { cout << "written to PMM memory on Socket" << i << "\t0\t\tGB\n"; //cout << "CPU" << i << "\tBytes written to memory channel\t0\t1\t GB\n"; } } for (uint32 l = 0; l < counters.getQPILinksPerSocket(); ++l) { for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/BytesIncomingTo" << xpi << l << "?"; if (s == c.str()) { //cout << "Socket" << i << "\tBytes incoming to QPI link\t" << l<< "\t\t GB\n"; cout << "incoming to Socket" << i << " " << xpi << " Link" << l << "\t0\t\tGB\n"; } } } { stringstream c; c << xpi << "_Traffic?"; if (s == c.str()) { cout << "Traffic on all " << xpi << " links\t0\t\tGB\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/Frequency?"; if (s == c.str()) { cout << "Socket" << i << " Frequency\t0\t\tMHz\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/IPC?"; if (s == c.str()) { cout << "Socket" << i << " IPC\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/L2CacheHitRatio?"; if (s == c.str()) { cout << "Socket" << i << " L2 Cache Hit Ratio\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/L3CacheHitRatio?"; if (s == c.str()) { cout << "Socket" << i << " L3 Cache Hit Ratio\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/L2CacheMisses?"; if (s == c.str()) { cout << "Socket" << i << " L2 Cache Misses\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/L3CacheMisses?"; if (s == c.str()) { cout << "Socket" << i << " L3 Cache Misses\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/L3Occupancy"; if (s == c.str()) { cout << "Socket" << i << " L3 Cache Occupancy\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/LocalMemoryBandwidth"; if (s == c.str()) { cout << "Socket" << i << " Local Memory Bandwidth\t0\t\t\n"; } } for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/RemoteMemoryBandwidth"; if (s == c.str()) { cout << "Socket" << i << " Remote Memory Bandwidth\t0\t\t\n"; } } { stringstream c; c << "Frequency?"; if (s == c.str()) { cout << "Frequency system wide\t0\t\tMhz\n"; } } { stringstream c; c << "IPC?"; if (s == c.str()) { cout << "IPC system wide\t0\t\t\n"; } } { stringstream c; c << "L2CacheHitRatio?"; if (s == c.str()) { cout << "System wide L2 Cache Hit Ratio\t0\t\t\n"; } } { stringstream c; c << "L3CacheHitRatio?"; if (s == c.str()) { cout << "System wide L3 Cache Hit Ratio\t0\t\t\n"; } } { stringstream c; c << "L2CacheMisses?"; if (s == c.str()) { cout << "System wide L2 Cache Misses\t0\t\t\n"; } } { stringstream c; c << "L3CacheMisses?"; if (s == c.str()) { cout << "System wide L3 Cache Misses\t0\t\t\n"; } } { stringstream c; c << "L3CacheMisses?"; if (s == c.str()) { cout << "System wide L3 Cache Misses\t0\t\t\n"; } } { stringstream c; c << "DRAMEnergy?"; if (s == c.str()) { cout << "System wide energy consumed by DRAM \t0\t\tJoule\n"; } } { stringstream c; c << "CPUEnergy?"; if (s == c.str()) { cout << "System wide energy consumed by CPU packages \t0\t\tJoule\n"; } } { stringstream c; c << "CoreC0StateResidency?"; if (s == c.str()) { cout << "System wide core C0-state residency \t0\t\t%\n"; } } { stringstream c; c << "CoreC3StateResidency?"; if (s == c.str()) { cout << "System wide core C3-state residency \t0\t\t%\n"; } } { stringstream c; c << "CoreC6StateResidency?"; if (s == c.str()) { cout << "System wide core C6-state residency \t0\t\t%\n"; } } { stringstream c; c << "CoreC7StateResidency?"; if (s == c.str()) { cout << "System wide core C7-state residency \t0\t\t%\n"; } } { stringstream c; c << "PackageC2StateResidency?"; if (s == c.str()) { cout << "System wide package C2-state residency \t0\t\t%\n"; } } { stringstream c; c << "PackageC3StateResidency?"; if (s == c.str()) { cout << "System wide package C3-state residency \t0\t\t%\n"; } } { stringstream c; c << "PackageC6StateResidency?"; if (s == c.str()) { cout << "System wide package C6-state residency \t0\t\t%\n"; } } { stringstream c; c << "PackageC7StateResidency?"; if (s == c.str()) { cout << "System wide package C7-state residency \t0\t\t%\n"; } } // sensors #define OUTPUT_CORE_METRIC(name, function) \ for (uint32 i = 0; i(i) / 1000000)) OUTPUT_CORE_METRIC("/IPC", (counters.get(i))) OUTPUT_CORE_METRIC("/L2CacheHitRatio", (counters.get(i))) OUTPUT_CORE_METRIC("/L3CacheHitRatio", (counters.get(i))) OUTPUT_CORE_METRIC("/L2CacheMisses", (counters.get(i))) OUTPUT_CORE_METRIC("/L3CacheMisses", (counters.get(i))) OUTPUT_CORE_METRIC("/L3Occupancy", (counters.get(i))) OUTPUT_CORE_METRIC("/LocalMemoryBandwidth", (counters.get(i))) OUTPUT_CORE_METRIC("/RemoteMemoryBandwidth", (counters.get(i))) OUTPUT_CORE_METRIC("/CoreC0StateResidency", (counters.get(0, i) * 100.)) OUTPUT_CORE_METRIC("/CoreC3StateResidency", (counters.get(3, i) * 100.)) OUTPUT_CORE_METRIC("/CoreC6StateResidency", (counters.get(6, i) * 100.)) OUTPUT_CORE_METRIC("/CoreC7StateResidency", (counters.get(7, i) * 100.)) OUTPUT_CORE_METRIC("/ThermalHeadroom", (counters.get(i))) #define OUTPUT_SOCKET_METRIC(name, function) \ for (uint32 i = 0; i(i))) OUTPUT_SOCKET_METRIC("/CPUEnergy", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/CoreC0StateResidency", (counters.getSocket(0, i) * 100.)) OUTPUT_SOCKET_METRIC("/CoreC3StateResidency", (counters.getSocket(3, i) * 100.)) OUTPUT_SOCKET_METRIC("/CoreC6StateResidency", (counters.getSocket(6, i) * 100.)) OUTPUT_SOCKET_METRIC("/CoreC7StateResidency", (counters.getSocket(7, i) * 100.)) OUTPUT_SOCKET_METRIC("/PackageC2StateResidency", (counters.getSocket(2, i) * 100.)) OUTPUT_SOCKET_METRIC("/PackageC3StateResidency", (counters.getSocket(3, i) * 100.)) OUTPUT_SOCKET_METRIC("/PackageC6StateResidency", (counters.getSocket(6, i) * 100.)) OUTPUT_SOCKET_METRIC("/PackageC7StateResidency", (counters.getSocket(7, i) * 100.)) OUTPUT_SOCKET_METRIC("/ThermalHeadroom", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/BytesReadFromMC", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) OUTPUT_SOCKET_METRIC("/BytesWrittenToMC", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) OUTPUT_SOCKET_METRIC("/BytesReadFromPMM", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) OUTPUT_SOCKET_METRIC("/BytesWrittenToPMM", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) OUTPUT_SOCKET_METRIC("/Frequency", (counters.getSocket(i) / 1000000)) OUTPUT_SOCKET_METRIC("/IPC", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/L2CacheHitRatio", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/L3CacheHitRatio", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/L2CacheMisses", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/L3CacheMisses", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/L3Occupancy", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/LocalMemoryBandwidth", (counters.getSocket(i))) OUTPUT_SOCKET_METRIC("/RemoteMemoryBandwidth", (counters.getSocket(i))) for (uint32 l = 0; l < counters.getQPILinksPerSocket(); ++l) { for (uint32 i = 0; i < counters.getNumSockets(); ++i) { stringstream c; c << "Socket" << i << "/BytesIncomingTo" << xpi << l; if (s == c.str()) { cout << double(counters.getSocket(i, l)) / 1024 / 1024 / 1024 << "\n"; } } } #define OUTPUT_SYSTEM_METRIC(name, function) \ { \ stringstream c; \ c << name; \ if (s == c.str()) { \ cout << function << "\n"; \ } \ } OUTPUT_SYSTEM_METRIC("DRAMEnergy", (counters.getSystem())) OUTPUT_SYSTEM_METRIC("CPUEnergy", (counters.getSystem())) OUTPUT_SYSTEM_METRIC("CoreC0StateResidency", (counters.getSystem(0) * 100.)) OUTPUT_SYSTEM_METRIC("CoreC3StateResidency", (counters.getSystem(3) * 100.)) OUTPUT_SYSTEM_METRIC("CoreC6StateResidency", (counters.getSystem(6) * 100.)) OUTPUT_SYSTEM_METRIC("CoreC7StateResidency", (counters.getSystem(7) * 100.)) OUTPUT_SYSTEM_METRIC("PackageC2StateResidency", (counters.getSystem(2) * 100.)) OUTPUT_SYSTEM_METRIC("PackageC3StateResidency", (counters.getSystem(3) * 100.)) OUTPUT_SYSTEM_METRIC("PackageC6StateResidency", (counters.getSystem(6) * 100.)) OUTPUT_SYSTEM_METRIC("PackageC7StateResidency", (counters.getSystem(7) * 100.)) OUTPUT_SYSTEM_METRIC("Frequency", (double(counters.getSystem()) / 1000000)) OUTPUT_SYSTEM_METRIC("IPC", (double(counters.getSystem()))) OUTPUT_SYSTEM_METRIC("L2CacheHitRatio", (double(counters.getSystem()))) OUTPUT_SYSTEM_METRIC("L3CacheHitRatio", (double(counters.getSystem()))) OUTPUT_SYSTEM_METRIC("L2CacheMisses", (double(counters.getSystem()))) OUTPUT_SYSTEM_METRIC("L3CacheMisses", (double(counters.getSystem()))) OUTPUT_SYSTEM_METRIC(std::string(xpi + std::string("_Traffic")), (double(counters.getSystem()) / 1024 / 1024 / 1024)) // exit if (s == "quit" || s == "exit" || s == "") { break; } cout << "ksysguardd> "; } return 0; } pcm-202502/src/pcm-tpmi.cpp000066400000000000000000000126771475730356400154410ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2023 Intel Corporation // written by Roman Dementiev #include "cpucounters.h" #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #endif #include #include #include #include #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif using namespace pcm; void print_usage(const char * progname) { std::cout << "Usage " << progname << " [-w value] [-d] [-b low:high] [-e entries] ID offset\n\n"; std::cout << " Reads/writes TPMI (Topology Aware Register and PM Capsule Interface) register \n"; std::cout << " ID : TPMI ID\n"; std::cout << " offset : register offset\n"; std::cout << " -w value : write the value before reading \n"; std::cout << " -b low:high : read or write only low..high bits of the register\n"; std::cout << " -e entries : perform read/write on specified entries (default is all entries)\n"; std::cout << " (examples: -e 10 -e 10-11 -e 4,6,12-20,6)\n"; std::cout << " -i instances: perform read/write on specified instances (default is all instances)\n"; std::cout << " (examples: -i 1 -i 0,1 -i 0,2-3)\n"; std::cout << " -d : output all numbers in dec (default is hex)\n"; std::cout << " -v : verbose ouput\n"; std::cout << " --version : print application version\n"; std::cout << "\n"; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) return 0; std::cout << "\n Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; std::cout << "\n TPMI (Topology Aware Register and PM Capsule Interface) read/write utility\n\n"; // register documentation: https://github.com/intel/tpmi_power_management uint64 value = 0; bool write = false; bool dec = false; std::pair bits{-1, -1}; std::list entries, instances; int my_opt = -1; while ((my_opt = getopt(argc, argv, "w:dvb:e:i:")) != -1) { switch (my_opt) { case 'w': write = true; value = read_number(optarg); break; case 'd': dec = true; break; case 'v': TPMIHandle::setVerbose(true); break; case 'b': bits = parseBitsParameter(optarg); break; case 'e': entries = extract_integer_list(optarg); break; case 'i': instances = extract_integer_list(optarg); break; default: print_usage(argv[0]); return -1; } } if (optind + 1 >= argc) { print_usage(argv[0]); return -1; } uint64 requestedID = (uint64)read_number(argv[optind]); uint64 requestedRelativeOffset = (uint64)read_number(argv[optind + 1]); #ifdef _MSC_VER // Increase the priority a bit to improve context switching delays on Windows SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); // WARNING: This driver code (msr.sys) is only for testing purposes, not for production use Driver drv = Driver(Driver::msrLocalPath()); // drv.stop(); // restart driver (usually not needed) if (!drv.start()) { tcerr << "Can not load MSR driver.\n"; tcerr << "You must have a signed driver at " << drv.driverPath() << " and have administrator rights to run this program\n"; return -1; } #endif try { if (instances.empty()) { for (size_t i = 0; i < TPMIHandle::getNumInstances(); ++i) { instances.push_back(i); } } for (const size_t i : instances) { if (i >= TPMIHandle::getNumInstances()) { std::cerr << "Instance " << i << " does not exist\n"; continue; } TPMIHandle h(i, requestedID, requestedRelativeOffset, !write); auto one = [&](const size_t p, uint64 value) { if (!dec) std::cout << std::hex << std::showbase; readOldValueHelper(bits, value, write, [&h, &p](uint64& old_value) { old_value = h.read64(p); return true; }); if (write) { std::cout << " Writing " << value << " to TPMI ID " << requestedID << "@" << requestedRelativeOffset << " for entry " << p << " in instance " << i << "\n"; h.write64(p, value); } value = h.read64(p); extractBitsPrintHelper(bits, value, dec); std::cout << " from TPMI ID " << requestedID << "@" << requestedRelativeOffset << " for entry " << p << " in instance " << i << "\n\n"; }; if (entries.empty()) { for (size_t p = 0; p < h.getNumEntries(); ++p) { entries.push_back(p); } } for (const size_t p : entries) { if (p < h.getNumEntries()) { one(p, value); } } } } catch (std::exception &e) { std::cerr << "Error accessing registers: " << e.what() << "\n"; std::cerr << "Please check if the program can access MSR/PCICFG drivers.\n"; } return 0; } pcm-202502/src/pcm-tsx.cpp000066400000000000000000000724071475730356400153030ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev /*! \file pcm-tsx.cpp \brief Example of using CPU counters: implements a performance counter monitoring utility for Intel Transactional Synchronization Extensions */ #include #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #include #include // for gettimeofday() #endif #include #include #include #include #include #include #include #include "cpucounters.h" #include "utils.h" #ifdef _MSC_VER #include "freegetopt/getopt.h" #endif #include #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs using namespace std; using namespace pcm; struct TSXEvent { const char * name; unsigned char event; unsigned char umask; const char * description; }; vector eventDefinition = { { "RTM_RETIRED.START", 0xC9, 0x01, "Number of times an RTM execution started." }, { "RTM_RETIRED.COMMIT", 0xC9, 0x02, "Number of times an RTM execution successfully committed" }, { "RTM_RETIRED.ABORTED", 0xC9, 0x04, "Number of times an RTM execution aborted due to any reasons (multiple categories may count as one)" }, { "RTM_RETIRED.ABORTED_MEM", 0xC9, 0x08, "Number of times an RTM execution aborted due to various memory events" }, { "RTM_RETIRED.ABORTED_TIMER", 0xC9, 0x10, "Number of times an RTM execution aborted due to uncommon conditions" }, { "RTM_RETIRED.ABORTED_UNFRIENDLY", 0xC9, 0x20, "Number of times an RTM execution aborted due to Intel TSX-unfriendly instructions" }, { "RTM_RETIRED.ABORTED_MEMTYPE", 0xC9, 0x40, "Number of times an RTM execution aborted due to incompatible memory type" }, { "RTM_RETIRED.ABORTED_EVENTS", 0xC9, 0x80, "Number of times an RTM execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "HLE_RETIRED.START", 0xC8, 0x01, "Number of times an HLE execution started." }, { "HLE_RETIRED.COMMIT", 0xC8, 0x02, "Number of times an HLE execution successfully committed" }, { "HLE_RETIRED.ABORTED", 0xC8, 0x04, "Number of times an HLE execution aborted due to any reasons (multiple categories may count as one)" }, { "HLE_RETIRED.ABORTED_MEM", 0xC8, 0x08, "Number of times an HLE execution aborted due to various memory events" }, { "HLE_RETIRED.ABORTED_TIMER", 0xC8, 0x10, "Number of times an HLE execution aborted due to uncommon conditions" }, { "HLE_RETIRED.ABORTED_UNFRIENDLY", 0xC8, 0x20, "Number of times an HLE execution aborted due to Intel TSX-unfriendly instructions" }, { "HLE_RETIRED.ABORTED_MEMTYPE", 0xC8, 0x40, "Number of times an HLE execution aborted due to incompatible memory type" }, { "HLE_RETIRED.ABORTED_EVENTS", 0xC8, 0x80, "Number of times an HLE execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "TX_MEM.ABORT_CONFLICT", 0x54, 0x01, "Number of times a transactional abort was signaled due to a data conflict on a transactionally accessed address" }, { "TX_MEM.ABORT_CAPACITY_WRITE", 0x54, 0x02, "Number of times a transactional abort was signaled due to limited resources for transactional stores" }, { "TX_MEM.ABORT_HLE_STORE_TO_ELIDED_LOCK", 0x54, 0x04, "Number of times a HLE transactional region aborted due to a non XRELEASE prefixed instruction writing to an elided lock in the elision buffer" }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_NOT_EMPTY", 0x54, 0x08, "Number of times an HLE transactional execution aborted due to NoAllocatedElisionBuffer being nonzero." }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_MISMATCH", 0x54, 0x10, "Number of times an HLE transactional execution aborted due to XRELEASE lock not satisfying the address and value requirements in the elision buffer." }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_UNSUPPORTED_ALIGNMENT", 0x54, 0x20, "Number of times an HLE transactional execution aborted due to an unsupported read alignment from the elision buffer." }, { "TX_MEM.HLE_ELISION_BUFFER_FULL", 0x54, 0x40, "Number of times HLE lock could not be elided due to ElisionBufferAvailable being zero." }, { "TX_EXEC.MISC1", 0x5D, 0x01, "Counts the number of times a class of instructions that may cause a transactional abort was executed. Since this is the count of execution, it may not always cause a transactional abort." }, { "TX_EXEC.MISC2", 0x5D, 0x02, "Counts the number of times a class of instructions that may cause a transactional abort was executed inside a transactional region" }, { "TX_EXEC.MISC3", 0x5D, 0x04, "Counts the number of times an instruction execution caused the nest count supported to be exceeded" }, { "TX_EXEC.MISC4", 0x5D, 0x08, "Counts the number of times a XBEGIN instruction was executed inside an HLE transactional region" }, { "TX_EXEC.MISC5", 0x5D, 0x10, "Counts the number of times an HLE XACQUIRE instruction was executed inside an RTM transactional region" } }; const vector sklEventDefinition = { { "RTM_RETIRED.START", 0xC9, 0x01, "Number of times an RTM execution started." }, { "RTM_RETIRED.COMMIT", 0xC9, 0x02, "Number of times an RTM execution successfully committed" }, { "RTM_RETIRED.ABORTED", 0xC9, 0x04, "Number of times an RTM execution aborted due to any reasons (multiple categories may count as one)" }, { "RTM_RETIRED.ABORTED_MEM", 0xC9, 0x08, "Number of times an RTM execution aborted due to various memory events" }, { "RTM_RETIRED.ABORTED_TIMER", 0xC9, 0x10, "Number of times an RTM execution aborted due to uncommon conditions" }, { "RTM_RETIRED.ABORTED_UNFRIENDLY", 0xC9, 0x20, "Number of times an RTM execution aborted due to Intel TSX-unfriendly instructions" }, { "RTM_RETIRED.ABORTED_MEMTYPE", 0xC9, 0x40, "Number of times an RTM execution aborted due to incompatible memory type" }, { "RTM_RETIRED.ABORTED_EVENTS", 0xC9, 0x80, "Number of times an RTM execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "HLE_RETIRED.START", 0xC8, 0x01, "Number of times an HLE execution started." }, { "HLE_RETIRED.COMMIT", 0xC8, 0x02, "Number of times an HLE execution successfully committed" }, { "HLE_RETIRED.ABORTED", 0xC8, 0x04, "Number of times an HLE execution aborted due to any reasons (multiple categories may count as one)" }, { "HLE_RETIRED.ABORTED_MEM", 0xC8, 0x08, "Number of times an HLE execution aborted due to various memory events" }, { "HLE_RETIRED.ABORTED_TIMER", 0xC8, 0x10, "Number of times an HLE execution aborted due to uncommon conditions" }, { "HLE_RETIRED.ABORTED_UNFRIENDLY", 0xC8, 0x20, "Number of times an HLE execution aborted due to Intel TSX-unfriendly instructions" }, { "HLE_RETIRED.ABORTED_MEMTYPE", 0xC8, 0x40, "Number of times an HLE execution aborted due to incompatible memory type" }, { "HLE_RETIRED.ABORTED_EVENTS", 0xC8, 0x80, "Number of times an HLE execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "TX_MEM.ABORT_CONFLICT", 0x54, 0x01, "Number of times a transactional abort was signaled due to a data conflict on a transactionally accessed address" }, { "TX_MEM.ABORT_CAPACITY", 0x54, 0x02, "Number of times a transactional abort was signaled due to a data capacity limitation for transactional reads or writes" }, { "TX_MEM.ABORT_HLE_STORE_TO_ELIDED_LOCK", 0x54, 0x04, "Number of times a HLE transactional region aborted due to a non XRELEASE prefixed instruction writing to an elided lock in the elision buffer" }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_NOT_EMPTY", 0x54, 0x08, "Number of times an HLE transactional execution aborted due to NoAllocatedElisionBuffer being nonzero." }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_MISMATCH", 0x54, 0x10, "Number of times an HLE transactional execution aborted due to XRELEASE lock not satisfying the address and value requirements in the elision buffer." }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_UNSUPPORTED_ALIGNMENT", 0x54, 0x20, "Number of times an HLE transactional execution aborted due to an unsupported read alignment from the elision buffer." }, { "TX_MEM.HLE_ELISION_BUFFER_FULL", 0x54, 0x40, "Number of times HLE lock could not be elided due to ElisionBufferAvailable being zero." }, { "TX_EXEC.MISC1", 0x5D, 0x01, "Counts the number of times a class of instructions that may cause a transactional abort was executed. Since this is the count of execution, it may not always cause a transactional abort." }, { "TX_EXEC.MISC2", 0x5D, 0x02, "Counts the number of times a class of instructions (e.g., vzeroupper) that may cause a transactional abort was executed inside a transactional region" }, { "TX_EXEC.MISC3", 0x5D, 0x04, "Counts the number of times an instruction execution caused the nest count supported to be exceeded" }, { "TX_EXEC.MISC4", 0x5D, 0x08, "Counts the number of times a XBEGIN instruction was executed inside an HLE transactional region" }, { "TX_EXEC.MISC5", 0x5D, 0x10, "Counts the number of times an HLE XACQUIRE instruction was executed inside an RTM transactional region" } }; const vector iclEventDefinition = { { "RTM_RETIRED.START", 0xC9, 0x01, "Number of times an RTM execution started." }, { "RTM_RETIRED.COMMIT", 0xC9, 0x02, "Number of times an RTM execution successfully committed" }, { "RTM_RETIRED.ABORTED", 0xC9, 0x04, "Number of times an RTM execution aborted due to any reasons (multiple categories may count as one)" }, { "RTM_RETIRED.ABORTED_MEM", 0xC9, 0x08, "Number of times an RTM execution aborted due to various memory events" }, { "RTM_RETIRED.ABORTED_TIMER", 0xC9, 0x10, "Number of times an RTM execution aborted due to uncommon conditions" }, { "RTM_RETIRED.ABORTED_UNFRIENDLY", 0xC9, 0x20, "Number of times an RTM execution aborted due to Intel TSX-unfriendly instructions" }, { "RTM_RETIRED.ABORTED_MEMTYPE", 0xC9, 0x40, "Number of times an RTM execution aborted due to incompatible memory type" }, { "RTM_RETIRED.ABORTED_EVENTS", 0xC9, 0x80, "Number of times an RTM execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "HLE_RETIRED.START", 0xC8, 0x01, "Number of times an HLE execution started." }, { "HLE_RETIRED.COMMIT", 0xC8, 0x02, "Number of times an HLE execution successfully committed" }, { "HLE_RETIRED.ABORTED", 0xC8, 0x04, "Number of times an HLE execution aborted due to any reasons (multiple categories may count as one)" }, { "HLE_RETIRED.ABORTED_MEM", 0xC8, 0x08, "Number of times an HLE execution aborted due to various memory events" }, { "HLE_RETIRED.ABORTED_TIMER", 0xC8, 0x10, "Number of times an HLE execution aborted due to uncommon conditions" }, { "HLE_RETIRED.ABORTED_UNFRIENDLY", 0xC8, 0x20, "Number of times an HLE execution aborted due to Intel TSX-unfriendly instructions" }, { "HLE_RETIRED.ABORTED_MEMTYPE", 0xC8, 0x40, "Number of times an HLE execution aborted due to incompatible memory type" }, { "HLE_RETIRED.ABORTED_EVENTS", 0xC8, 0x80, "Number of times an HLE execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "TX_MEM.ABORT_CONFLICT", 0x54, 0x01, "Number of times a transactional abort was signaled due to a data conflict on a transactionally accessed address" }, { "TX_MEM.ABORT_CAPACITY_WRITE", 0x54, 0x02, "Speculatively counts the number of TSX aborts due to a data capacity limitation for transactional writes" }, { "TX_MEM.ABORT_CAPACITY_READ", 0x54, 0x80, "Speculatively counts the number of TSX aborts due to a data capacity limitation for transactional reads" }, { "TX_MEM.ABORT_HLE_STORE_TO_ELIDED_LOCK", 0x54, 0x04, "Number of times a HLE transactional region aborted due to a non XRELEASE prefixed instruction writing to an elided lock in the elision buffer" }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_NOT_EMPTY", 0x54, 0x08, "Number of times an HLE transactional execution aborted due to NoAllocatedElisionBuffer being nonzero." }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_MISMATCH", 0x54, 0x10, "Number of times an HLE transactional execution aborted due to XRELEASE lock not satisfying the address and value requirements in the elision buffer." }, { "TX_MEM.ABORT_HLE_ELISION_BUFFER_UNSUPPORTED_ALIGNMENT", 0x54, 0x20, "Number of times an HLE transactional execution aborted due to an unsupported read alignment from the elision buffer." }, { "TX_MEM.HLE_ELISION_BUFFER_FULL", 0x54, 0x40, "Number of times HLE lock could not be elided due to ElisionBufferAvailable being zero." }, { "TX_EXEC.MISC2", 0x5D, 0x02, "Counts the number of times a class of instructions (e.g., vzeroupper) that may cause a transactional abort was executed inside a transactional region" }, { "TX_EXEC.MISC3", 0x5D, 0x04, "Counts the number of times an instruction execution caused the nest count supported to be exceeded" } }; const vector sprEventDefinition = { { "RTM_RETIRED.START", 0xC9, 0x01, "Number of times an RTM execution started." }, { "RTM_RETIRED.COMMIT", 0xC9, 0x02, "Number of times an RTM execution successfully committed" }, { "RTM_RETIRED.ABORTED", 0xC9, 0x04, "Number of times an RTM execution aborted." }, { "RTM_RETIRED.ABORTED_MEM", 0xC9, 0x08, "Number of times an RTM execution aborted due to various memory events (e.g. read/write capacity and conflicts)" }, { "RTM_RETIRED.ABORTED_UNFRIENDLY", 0xC9, 0x20, "Number of times an RTM execution aborted due to HLE-unfriendly instructions" }, { "RTM_RETIRED.ABORTED_MEMTYPE", 0xC9, 0x40, "Number of times an RTM execution aborted due to incompatible memory type" }, { "RTM_RETIRED.ABORTED_EVENTS", 0xC9, 0x80, "Number of times an RTM execution aborted due to none of the previous 4 categories (e.g. interrupt)" }, { "TX_MEM.ABORT_CONFLICT", 0x54, 0x01, "Number of times a transactional abort was signaled due to a data conflict on a transactionally accessed address" }, { "TX_MEM.ABORT_CAPACITY_WRITE", 0x54, 0x02, "Speculatively counts the number of TSX aborts due to a data capacity limitation for transactional writes." }, { "TX_MEM.ABORT_CAPACITY_READ", 0x54, 0x80, "Speculatively counts the number of TSX aborts due to a data capacity limitation for transactional reads" } }; void print_usage(const string & progname) { cout << "\n Usage: \n " << progname << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -F | -force => force running this program despite lack of HW RTM support (optional)\n"; cout << " -pid PID | /pid PID => collect core metrics only for specified process ID\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " [-e event1] [-e event2] [-e event3]=> optional list of custom TSX events to monitor (up to 4)." << " The list of supported events:\n"; for (auto & e: eventDefinition) { cout << e.name << "\t" << e.description << "\n"; } cout << "\n"; cout << " Examples:\n"; cout << " " << progname << " 1 => print counters every second without core and socket output\n"; cout << " " << progname << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << progname << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } #define TX_CYCLES_POS (1) #define TX_CYCLES_COMMITED_POS (2) #define N_HLE_POS (3) #define N_RTM_POS (0) bool supportNHLECountBasicStat = true; template void print_basic_stats(const StateType & BeforeState, const StateType & AfterState, bool csv) { uint64 cycles = getCycles(BeforeState, AfterState); uint64 instr = getInstructionsRetired(BeforeState, AfterState); const uint64 TXcycles = getNumberOfCustomEvents(TX_CYCLES_POS, BeforeState, AfterState); const uint64 TXcycles_commited = getNumberOfCustomEvents(TX_CYCLES_COMMITED_POS, BeforeState, AfterState); const uint64 Abr_cycles = (TXcycles > TXcycles_commited) ? (TXcycles - TXcycles_commited) : 0ULL; uint64 nRTM = getNumberOfCustomEvents(N_RTM_POS, BeforeState, AfterState); uint64 nHLE = getNumberOfCustomEvents(N_HLE_POS, BeforeState, AfterState); if (csv) { cout << double(instr) / double(cycles) << ","; cout << instr << ","; cout << cycles << ","; cout << TXcycles << "," << std::setw(5) << 100. * double(TXcycles) / double(cycles) << "%,"; cout << Abr_cycles << "," << std::setw(5) << 100. * double(Abr_cycles) / double(cycles) << "%,"; cout << nRTM << ","; if (supportNHLECountBasicStat) { cout << nHLE << ","; } } else { cout << double(instr) / double(cycles) << " "; cout << unit_format(instr) << " "; cout << unit_format(cycles) << " "; cout << unit_format(TXcycles) << " (" << std::setw(5) << 100. * double(TXcycles) / double(cycles) << "%) "; cout << unit_format(Abr_cycles) << " (" << std::setw(5) << 100. * double(Abr_cycles) / double(cycles) << "%) "; cout << unit_format(nRTM) << " "; if (supportNHLECountBasicStat) { cout << unit_format(nHLE) << " "; } } if (nRTM + nHLE) { uint64 cyclesPerTransaction = TXcycles / (nRTM + nHLE); if (csv) cout << cyclesPerTransaction << "\n"; else cout << unit_format(cyclesPerTransaction) << "\n"; } else cout << " N/A\n"; } std::vector events; template void print_custom_stats(const StateType & BeforeState, const StateType & AfterState, bool csv) { for (size_t i = 0; i < events.size(); ++i) if (!csv) cout << unit_format(getNumberOfCustomEvents((pcm::int32)i, BeforeState, AfterState)) << " "; else cout << getNumberOfCustomEvents((pcm::int32)i, BeforeState, AfterState) << ","; cout << "\n"; } int findEvent(const char * name) { for (size_t i = 0; i < eventDefinition.size(); ++i) { if (strcmp(name, eventDefinition[i].name) == 0) return (int)i; } return -1; } PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; std::cout.rdbuf(&nullStream1); std::cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor: Intel(r) Transactional Synchronization Extensions Monitoring Utility \n"; cerr << "\n"; double delay = -1.0; int pid{ -1 }; char * sysCmd = NULL; char ** sysArgv = NULL; int cur_event; bool csv = false; bool force = false; MainLoop mainLoop; string program = string(argv[0]); parsePID(argc, argv, pid); PCM * m = PCM::getInstance(); const size_t numCtrSupported = m->getMaxCustomCoreEvents(); switch (m->getCPUFamilyModel()) { case PCM::SKL: case PCM::SKX: case PCM::KBL: eventDefinition = sklEventDefinition; break; case PCM::ICL: case PCM::ICX: case PCM::RKL: eventDefinition = iclEventDefinition; break; case PCM::SPR: case PCM::EMR: case PCM::GNR: case PCM::GNR_D: eventDefinition = sprEventDefinition; break; } if (argc > 1) do { argv++; argc--; string arg_value; if (*argv == nullptr) { continue; } else if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_usage(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (isPIDOption(argv)) { argv++; argc--; continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"-e"})) { argv++; argc--; if (events.size() >= numCtrSupported) { cerr << "At most " << numCtrSupported << " events are allowed\n"; exit(EXIT_FAILURE); } cur_event = findEvent(*argv); if (cur_event < 0) { cerr << "Event " << *argv << " is not supported. See the list of supported events\n"; print_usage(program); exit(EXIT_FAILURE); } events.push_back(cur_event); continue; } else if (CheckAndForceRTMAbortMode(*argv, m)) // for pcm-tsx this option is enabled for testing only, not exposed in the help { continue; } else if (check_argument_equals(*argv, {"-F", "-f", "-force"})) { force = true; } else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_usage); continue; } } while (argc > 1); // end of command line partsing loop EventSelectRegister def_event_select_reg; def_event_select_reg.value = 0; def_event_select_reg.fields.usr = 1; def_event_select_reg.fields.os = 1; def_event_select_reg.fields.enable = 1; PCM::ExtendedCustomCoreEventDescription conf; conf.fixedCfg = NULL; // default EventSelectRegister regs[PERF_MAX_CUSTOM_COUNTERS]; conf.gpCounterCfg = regs; for (int i = 0; i < PERF_MAX_CUSTOM_COUNTERS; ++i) regs[i] = def_event_select_reg; if (events.empty()) { conf.nGPCounters = 4; if (m->getMaxCustomCoreEvents() == 3) { conf.nGPCounters = 3; supportNHLECountBasicStat = false; } regs[N_RTM_POS].fields.event_select = 0xc9; regs[N_RTM_POS].fields.umask = 0x01; regs[N_HLE_POS].fields.event_select = 0xc8; regs[N_HLE_POS].fields.umask = 0x01; regs[TX_CYCLES_COMMITED_POS].fields.event_select = 0x3c; regs[TX_CYCLES_COMMITED_POS].fields.in_tx = 1; regs[TX_CYCLES_COMMITED_POS].fields.in_txcp = 1; regs[TX_CYCLES_POS].fields.event_select = 0x3c; regs[TX_CYCLES_POS].fields.in_tx = 1; } else { conf.nGPCounters = (uint32) events.size(); for (unsigned int i = 0; i < events.size(); ++i) { const auto event = eventDefinition[events[i]].event; if (event == 0x54 && i >= 4) { cerr << "Error: a TX_MEM.* event found in position " << i << " which is not supported. Reorder the events in the command line such that TX_MEM events are at positions 0..3.\n"; return -1; } regs[i].fields.event_select = event; regs[i].fields.umask = eventDefinition[events[i]].umask; } } bool rtm_support = m->supportsRTM(); if (!rtm_support) { if (!force) { cerr << "No RTM support detected, use -F if you still want to run this program.\n"; exit(EXIT_FAILURE); } cerr << "No RTM support detected, but -F found as argument, running anyway.\n"; } print_pid_collection_message(pid); PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf, false, pid); m->checkError(status); print_cpu_details(); uint64 BeforeTime = 0, AfterTime = 0; SystemCounterState SysBeforeState, SysAfterState; const uint32 ncores = m->getNumCores(); std::vector BeforeState, AfterState; std::vector DummySocketStates; if ((sysCmd != NULL) && (delay <= 0.0)) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } if (csv) { if (delay <= 0.0) delay = PCM_DELAY_DEFAULT; } else { // for non-CSV mode delay < 1.0 does not make a lot of practical sense: // hard to read from the screen, or // in case delay is not provided in command line => set default if (((delay < 1.0) && (delay > 0.0)) || (delay <= 0.0)) delay = PCM_DELAY_DEFAULT; } cerr << "Update every " << delay << " seconds\n"; std::cout.precision(2); std::cout << std::fixed; BeforeTime = m->getTickCount(); m->getAllCounterStates(SysBeforeState, DummySocketStates, BeforeState); if (sysCmd != NULL) { MySystem(sysCmd, sysArgv); } mainLoop([&]() { if (!csv) cout << std::flush; calibratedSleep(delay, sysCmd, mainLoop, m); AfterTime = m->getTickCount(); m->getAllCounterStates(SysAfterState, DummySocketStates, AfterState); cout << "Time elapsed: " << dec << fixed << AfterTime - BeforeTime << " ms\n"; //cout << "Called sleep function for " <isBlocked()) { // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } pcm-202502/src/pcm.cpp000066400000000000000000002116271475730356400144660ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev, // Thomas Willhalm, // Patrick Ungerer /*! \file pcm.cpp \brief Example of using CPU counters: implements a simple performance counter monitoring utility */ #include #ifdef _MSC_VER #include #include "windows/windriver.h" #else #include #include // for atexit() #include // for gettimeofday() #endif #include #include #include #include #include #include #include #include #include #include "cpucounters.h" #include "utils.h" #define SIZE (10000000) #define PCM_DELAY_DEFAULT 1.0 // in seconds #define PCM_DELAY_MIN 0.015 // 15 milliseconds is practical on most modern CPUs #define MAX_CORES 4096 using namespace std; using namespace pcm; template double float_format(IntType n) { return double(n) / 1e6; } std::string temp_format(int32 t) { char buffer[1024]; if (t == PCM_INVALID_THERMAL_HEADROOM) return "N/A"; snprintf(buffer, 1024, "%2d", t); return buffer; } std::string l3cache_occ_format(uint64 o) { char buffer[1024]; if (o == PCM_INVALID_QOS_MONITORING_DATA) return "N/A"; snprintf(buffer, 1024, "%6u", (uint32) o); return buffer; } template double getAverageUncoreFrequencyGhz(const UncoreStateType& before, const UncoreStateType& after) // in GHz { return getAverageUncoreFrequency(before, after) / 1e9; } void print_help(const string & prog_name) { cout << "\n Usage: \n " << prog_name << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; cout << " => time interval to sample performance counters.\n"; cout << " If not specified, or 0, with external program given\n"; cout << " will read counters only after external program finishes\n"; cout << " Supported are: \n"; cout << " -h | --help | /h => print this help and exit\n"; cout << " -silent => silence information output and print only measurements\n"; cout << " --version => print application version\n"; cout << " -pid PID | /pid PID => collect core metrics only for specified process ID\n"; #ifdef _MSC_VER cout << " --uninstallDriver | --installDriver=> (un)install driver\n"; #endif cout << " -r | --reset | /reset => reset PMU configuration (at your own risk)\n"; cout << " -nc | --nocores | /nc => hide core related output\n"; cout << " -yc | --yescores | /yc => enable specific cores to output\n"; cout << " -ns | --nosockets | /ns => hide socket related output\n"; cout << " -nsys | --nosystem | /nsys => hide system related output\n"; cout << " --color => use ASCII colors\n"; cout << " --no-color => don't use ASCII colors\n"; cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" << " to a file, in case filename is provided\n" << " the format used is documented here: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-pcm-column-names-decoder-ring.html\n"; cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; cout << " -m=integer | /m=integer => metrics version (default = 2)\n"; print_enforce_flush_option_help(); print_help_force_rtm_abort_mode(37); cout << " Examples:\n"; cout << " " << prog_name << " 1 -nc -ns => print counters every second without core and socket output\n"; cout << " " << prog_name << " 1 -i=10 => print counters every second 10 times and exit\n"; cout << " " << prog_name << " 0.5 -csv=test.log => twice a second save counter values to test.log in CSV format\n"; cout << " " << prog_name << " /csv 5 2>/dev/null => one sample every 5 seconds, and discard all diagnostic output\n"; cout << "\n"; } template void print_basic_metrics(const PCM * m, const State & state1, const State & state2, const int metricVersion) { switch (metricVersion) { case 2: if (m->isCoreCStateResidencySupported(0)) { cout << setNextColor() << " " << getCoreCStateResidency(0, state1, state2); } cout << setNextColor() << " " << getIPC(state1, state2); if (m->isActiveRelativeFrequencyAvailable()) { cout << setNextColor() << " " << getActiveAverageFrequency(state1, state2)/1e9; } break; default: cout << setNextColor() << " " << getExecUsage(state1, state2) << setNextColor() << " " << getIPC(state1, state2) << setNextColor() << " " << getRelativeFrequency(state1, state2); if (m->isActiveRelativeFrequencyAvailable()) cout << setNextColor() << " " << getActiveRelativeFrequency(state1, state2); } if (m->isL3CacheMissesAvailable()) cout << setNextColor() << " " << unit_format(getL3CacheMisses(state1, state2)); if (m->isL2CacheMissesAvailable()) cout << setNextColor() << " " << unit_format(getL2CacheMisses(state1, state2)); if (m->isL3CacheHitRatioAvailable()) cout << setNextColor() << " " << getL3CacheHitRatio(state1, state2); if (m->isL2CacheHitRatioAvailable()) cout << setNextColor() << " " << getL2CacheHitRatio(state1, state2); cout.precision(4); if (m->isL3CacheMissesAvailable()) cout << setNextColor() << " " << double(getL3CacheMisses(state1, state2)) / getInstructionsRetired(state1, state2); if (m->isL2CacheMissesAvailable()) cout << setNextColor() << " " << double(getL2CacheMisses(state1, state2)) / getInstructionsRetired(state1, state2); cout.precision(2); } template void print_other_metrics(const PCM * m, const State & state1, const State & state2) { if (m->L3CacheOccupancyMetricAvailable()) cout << setNextColor() << " " << setw(6) << l3cache_occ_format(getL3CacheOccupancy(state2)); if (m->CoreLocalMemoryBWMetricAvailable()) cout << setNextColor() << " " << setw(6) << getLocalMemoryBW(state1, state2); if (m->CoreRemoteMemoryBWMetricAvailable()) cout << setNextColor() << " " << setw(6) << getRemoteMemoryBW(state1, state2); cout << setNextColor() << " " << temp_format(state2.getThermalHeadroom()) << "\n"; } void print_output(PCM * m, const std::vector & cstates1, const std::vector & cstates2, const std::vector & sktstate1, const std::vector & sktstate2, const std::bitset & ycores, const SystemCounterState& sstate1, const SystemCounterState& sstate2, const int cpu_family_model, const bool show_core_output, const bool show_partial_core_output, const bool show_socket_output, const bool show_system_output, const int metricVersion ) { cout << "\n"; switch (metricVersion) { case 2: if (m->isCoreCStateResidencySupported(0)) { cout << " UTIL : utlization (same as core C0 state active state residency, the value is in 0..1) \n"; } cout << " IPC : instructions per CPU cycle\n"; if (m->isActiveRelativeFrequencyAvailable()) { cout << " CFREQ : core frequency in Ghz\n"; } break; default: cout << " EXEC : instructions per nominal CPU cycle\n"; cout << " IPC : instructions per CPU cycle\n"; cout << " FREQ : relation to nominal CPU frequency='unhalted clock ticks'/'invariant timer ticks' (includes Intel Turbo Boost)\n"; if (m->isActiveRelativeFrequencyAvailable()) cout << " AFREQ : relation to nominal CPU frequency while in active state (not in power-saving C state)='unhalted clock ticks'/'invariant timer ticks while in C0-state' (includes Intel Turbo Boost)\n"; }; if (m->isL3CacheMissesAvailable()) cout << " L3MISS: L3 (read) cache misses \n"; if (m->isL2CacheHitsAvailable()) { if (m->isAtom() || cpu_family_model == PCM::KNL) cout << " L2MISS: L2 (read) cache misses \n"; else cout << " L2MISS: L2 (read) cache misses (including other core's L2 cache *hits*) \n"; } if (m->isL3CacheHitRatioAvailable()) cout << " L3HIT : L3 (read) cache hit ratio (0.00-1.00)\n"; if (m->isL2CacheHitRatioAvailable()) cout << " L2HIT : L2 cache hit ratio (0.00-1.00)\n"; if (m->isL3CacheMissesAvailable()) cout << " L3MPI : number of L3 (read) cache misses per instruction\n"; if (m->isL2CacheMissesAvailable()) cout << " L2MPI : number of L2 (read) cache misses per instruction\n"; if (m->memoryTrafficMetricsAvailable()) cout << " READ : bytes read from main memory controller (in GBytes)\n"; if (m->memoryTrafficMetricsAvailable()) cout << " WRITE : bytes written to main memory controller (in GBytes)\n"; if (m->localMemoryRequestRatioMetricAvailable()) cout << " LOCAL : ratio of local memory requests to memory controller in %\n"; if (m->LLCReadMissLatencyMetricsAvailable()) cout << "LLCRDMISSLAT: average latency of last level cache miss for reads and prefetches (in ns)\n"; if (m->PMMTrafficMetricsAvailable()) cout << " PMM RD : bytes read from PMM memory (in GBytes)\n"; if (m->PMMTrafficMetricsAvailable()) cout << " PMM WR : bytes written to PMM memory (in GBytes)\n"; if (m->HBMmemoryTrafficMetricsAvailable()) cout << " HBM READ : bytes read from HBM controller (in GBytes)\n"; if (m->HBMmemoryTrafficMetricsAvailable()) cout << " HBM WRITE : bytes written to HBM controller (in GBytes)\n"; if (m->memoryIOTrafficMetricAvailable()) { cout << " IO : bytes read/written due to IO requests to memory controller (in GBytes); this may be an over estimate due to same-cache-line partial requests\n"; cout << " IA : bytes read/written due to IA requests to memory controller (in GBytes); this may be an over estimate due to same-cache-line partial requests\n"; cout << " GT : bytes read/written due to GT requests to memory controller (in GBytes); this may be an over estimate due to same-cache-line partial requests\n"; } if (m->L3CacheOccupancyMetricAvailable()) cout << " L3OCC : L3 occupancy (in KBytes)\n"; if (m->CoreLocalMemoryBWMetricAvailable()) cout << " LMB : L3 cache external bandwidth satisfied by local memory (in MBytes)\n"; if (m->CoreRemoteMemoryBWMetricAvailable()) cout << " RMB : L3 cache external bandwidth satisfied by remote memory (in MBytes)\n"; cout << " TEMP : Temperature reading in 1 degree Celsius relative to the TjMax temperature (thermal headroom): 0 corresponds to the max temperature\n"; cout << " energy: Energy in Joules\n"; cout << "\n"; cout << "\n"; const char * longDiv = "---------------------------------------------------------------------------------------------------------------\n"; cout.precision(2); cout << std::fixed; if (cpu_family_model == PCM::KNL) cout << " Proc Tile Core Thread |"; else cout << " Core (SKT) |"; switch (metricVersion) { case 2: if (m->isCoreCStateResidencySupported(0)) { cout << setNextColor() << " UTIL |"; } cout << setNextColor() << " IPC |"; if (m->isActiveRelativeFrequencyAvailable()) { cout << setNextColor() << " CFREQ |"; } break; default: cout << setNextColor() << " EXEC |" << setNextColor() << " IPC |" << setNextColor() <<" FREQ |"; if (m->isActiveRelativeFrequencyAvailable()) cout << setNextColor() << " AFREQ |"; } if (m->isL3CacheMissesAvailable()) cout << setNextColor() << " L3MISS |"; if (m->isL2CacheMissesAvailable()) cout << setNextColor() << " L2MISS |"; if (m->isL3CacheHitRatioAvailable()) cout << setNextColor() << " L3HIT |"; if (m->isL2CacheHitRatioAvailable()) cout << setNextColor() << " L2HIT |"; if (m->isL3CacheMissesAvailable()) cout << setNextColor() << " L3MPI |"; if (m->isL2CacheMissesAvailable()) cout << setNextColor() << " L2MPI | "; if (m->L3CacheOccupancyMetricAvailable()) cout << setNextColor() << " L3OCC |"; if (m->CoreLocalMemoryBWMetricAvailable()) cout << setNextColor() << " LMB |"; if (m->CoreRemoteMemoryBWMetricAvailable()) cout << setNextColor() << " RMB |"; cout << setNextColor() << " TEMP\n\n"; cout << resetColor(); if (show_core_output) { for (uint32 i = 0; i < m->getNumCores(); ++i) { if (m->isCoreOnline(i) == false || (show_partial_core_output && ycores.test(i) == false)) continue; if (cpu_family_model == PCM::KNL) cout << setfill(' ') << internal << setw(5) << i << setw(5) << m->getTileId(i) << setw(5) << m->getCoreId(i) << setw(7) << m->getThreadId(i); else cout << " " << setw(3) << i << " " << setw(2) << m->getSocketId(i); print_basic_metrics(m, cstates1[i], cstates2[i], metricVersion); print_other_metrics(m, cstates1[i], cstates2[i]); cout << resetColor(); } } if (show_socket_output) { if (!(m->getNumSockets() == 1 && (m->isAtom() || cpu_family_model == PCM::KNL))) { cout << longDiv; for (uint32 i = 0; i < m->getNumSockets(); ++i) { cout << " SKT " << setw(2) << i; print_basic_metrics(m, sktstate1[i], sktstate2[i], metricVersion); print_other_metrics(m, sktstate1[i], sktstate2[i]); cout << resetColor(); } } } cout << longDiv; if (show_system_output) { if (cpu_family_model == PCM::KNL) cout << setw(22) << left << " TOTAL" << internal << setw(7-5); else cout << " TOTAL *"; print_basic_metrics(m, sstate1, sstate2, metricVersion); if (m->L3CacheOccupancyMetricAvailable()) cout << setNextColor() <<" N/A "; if (m->CoreLocalMemoryBWMetricAvailable()) cout << setNextColor() <<" N/A "; if (m->CoreRemoteMemoryBWMetricAvailable()) cout << setNextColor() <<" N/A "; cout << setNextColor() << " N/A\n"; cout << resetColor(); cout << setNextColor() << "\n Instructions retired: " << unit_format(getInstructionsRetired(sstate1, sstate2)) << " ;" << setNextColor() << " Active cycles: " << unit_format(getCycles(sstate1, sstate2)) << " ;" << setNextColor() << " Time (TSC): " << unit_format(getInvariantTSC(cstates1[0], cstates2[0])) << "ticks;"; if (m->systemEnergyMetricAvailable() && systemEnergyStatusValid(sstate1) && systemEnergyStatusValid(sstate2)) { cout << setNextColor() << " SYS energy: " << getSystemConsumedJoules(sstate1, sstate2) << " J;"; } cout << "\n\n"; cout << resetColor() << setNextColor() << " Core C-state residencies: "<< setNextColor() << "C0 (active,non-halted): " << (getCoreCStateResidency(0, sstate1, sstate2)*100.) << " %;"; for (int s = 1; s <= PCM::MAX_C_STATE; ++s) { if (m->isCoreCStateResidencySupported(s)) { std::cout << setNextColor() << " C" << s << ": " << (getCoreCStateResidency(s, sstate1, sstate2)*100.) << " %;"; } } cout << "\n" ; cout << resetColor() << setNextColor() << " Package C-state residencies: "; std::vector CoreCStateStackedBar, PackageCStateStackedBar; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) { std::ostringstream sstr(std::ostringstream::out); sstr << std::hex << s; const char fill = sstr.str().c_str()[0]; if (m->isCoreCStateResidencySupported(s)) { CoreCStateStackedBar.push_back(StackedBarItem(getCoreCStateResidency(s, sstate1, sstate2), "", fill)); } if (m->isPackageCStateResidencySupported(s)) { std::cout << setNextColor() << " C" << s << ": " << (getPackageCStateResidency(s, sstate1, sstate2)*100.) << " %;"; PackageCStateStackedBar.push_back(StackedBarItem(getPackageCStateResidency(s, sstate1, sstate2), "", fill)); } } cout << "\n" << resetColor() << setColor(ASCII_BRIGHT_GREEN); drawStackedBar(" Core C-state distribution", CoreCStateStackedBar, 80); cout << setColor(ASCII_GREEN); drawStackedBar(" Package C-state distribution", PackageCStateStackedBar, 80); cout << resetColor(); if (m->getNumCores() == m->getNumOnlineCores() && false) { cout << "\n PHYSICAL CORE IPC : " << getCoreIPC(sstate1, sstate2) << " => corresponds to " << 100. * (getCoreIPC(sstate1, sstate2) / double(m->getMaxIPC())) << " % utilization for cores in active state"; cout << "\n Instructions per nominal CPU cycle: " << getTotalExecUsage(sstate1, sstate2) << " => corresponds to " << 100. * (getTotalExecUsage(sstate1, sstate2) / double(m->getMaxIPC())) << " % core utilization over time interval\n"; } if (m->isHWTMAL2Supported()) { cout << setColor(ASCII_BRIGHT_MAGENTA); cout << " Pipeline stalls: " << setColor(ASCII_BRIGHT_CYAN) << "Frontend (fetch latency: " << int(100. * getFetchLatencyBound(sstate1, sstate2)) <<" %, fetch bandwidth: " << int(100. * getFetchBandwidthBound(sstate1, sstate2)) << " %)\n " << setColor(ASCII_BRIGHT_RED) << "bad Speculation (branch misprediction: " << int(100. * getBranchMispredictionBound(sstate1, sstate2)) << " %, machine clears: " << int(100. * getMachineClearsBound(sstate1, sstate2)) << " %)\n " << setColor(ASCII_BRIGHT_YELLOW) << "Backend (buffer/cache/memory: " << int(100. * getMemoryBound(sstate1, sstate2)) << " %, core: " << int(100. * getCoreBound(sstate1, sstate2)) << " %)\n " << setColor(ASCII_BRIGHT_GREEN) << "Retiring (heavy operations: " << int(100. * getHeavyOperationsBound(sstate1, sstate2)) << " %, light operations: " << int(100. * getLightOperationsBound(sstate1, sstate2)) << " %)\n"; } else if (m->isHWTMAL1Supported()) { cout << setColor(ASCII_BRIGHT_MAGENTA); cout << " Pipeline stalls: " << setColor(ASCII_BRIGHT_CYAN) << "Frontend bound: " << int(100. * getFrontendBound(sstate1, sstate2)) << " %, " << setColor(ASCII_BRIGHT_RED) << "bad Speculation: " << int(100. * getBadSpeculation(sstate1, sstate2)) << " %, " << setColor(ASCII_BRIGHT_YELLOW) << "Backend bound: " << int(100. * getBackendBound(sstate1, sstate2)) << " %, " << setColor(ASCII_BRIGHT_GREEN) << "Retiring: " << int(100. * getRetiring(sstate1, sstate2)) << " %\n"; } if (m->isHWTMAL1Supported()) { cout << setColor(ASCII_BRIGHT_MAGENTA); std::vector TMAStackedBar; TMAStackedBar.push_back(StackedBarItem(getFrontendBound(sstate1, sstate2), "", 'F')); TMAStackedBar.push_back(StackedBarItem(getBadSpeculation(sstate1, sstate2), "", 'S')); TMAStackedBar.push_back(StackedBarItem(getBackendBound(sstate1, sstate2), "", 'B')); TMAStackedBar.push_back(StackedBarItem(getRetiring(sstate1, sstate2), "", 'R')); drawStackedBar(" Pipeline stall distribution ", TMAStackedBar, 80); cout << resetColor() << "\n"; } #if 0 cout << " SMI count: " << getSMICount(sstate1, sstate2) << "\n"; #endif } cout << setColor(ASCII_CYAN); if (show_socket_output) { if (m->getNumSockets() > 1 && m->incomingQPITrafficMetricsAvailable()) // QPI info only for multi socket systems { cout << "Intel(r) " << m->xPI() << " data traffic estimation in bytes (data traffic coming to CPU/socket through " << m->xPI() << " links):\n\n"; const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); cout << " "; for (uint32 i = 0; i < qpiLinks; ++i) cout << " " << m->xPI() << i << " "; if (m->qpiUtilizationMetricsAvailable()) { cout << "| "; for (uint32 i = 0; i < qpiLinks; ++i) cout << " " << m->xPI() << i << " "; } cout << "\n" << longDiv; for (uint32 i = 0; i < m->getNumSockets(); ++i) { cout << " SKT " << setw(2) << i << " "; for (uint32 l = 0; l < qpiLinks; ++l) cout << unit_format(getIncomingQPILinkBytes(i, l, sstate1, sstate2)) << " "; if (m->qpiUtilizationMetricsAvailable()) { cout << "| "; for (uint32 l = 0; l < qpiLinks; ++l) cout << setw(3) << std::dec << int(100. * getIncomingQPILinkUtilization(i, l, sstate1, sstate2)) << "% "; } cout << "\n"; } } } if (show_system_output) { cout << longDiv; if (m->getNumSockets() > 1 && m->incomingQPITrafficMetricsAvailable()) // QPI info only for multi socket systems cout << "Total " << m->xPI() << " incoming data traffic: " << unit_format(getAllIncomingQPILinkBytes(sstate1, sstate2)) << " " << m->xPI() << " data traffic/Memory controller traffic: " << getQPItoMCTrafficRatio(sstate1, sstate2) << "\n"; } cout << setColor(ASCII_BRIGHT_CYAN); if (show_socket_output) { if (m->getNumSockets() > 1 && (m->outgoingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { cout << "\nIntel(r) " << m->xPI() << " traffic estimation in bytes (data and non-data traffic outgoing from CPU/socket through " << m->xPI() << " links):\n\n"; const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); cout << " "; for (uint32 i = 0; i < qpiLinks; ++i) cout << " " << m->xPI() << i << " "; cout << "| "; for (uint32 i = 0; i < qpiLinks; ++i) cout << " " << m->xPI() << i << " "; cout << "\n" << longDiv; for (uint32 i = 0; i < m->getNumSockets(); ++i) { cout << " SKT " << setw(2) << i << " "; for (uint32 l = 0; l < qpiLinks; ++l) cout << unit_format(getOutgoingQPILinkBytes(i, l, sstate1, sstate2)) << " "; cout << "| "; for (uint32 l = 0; l < qpiLinks; ++l) cout << setw(3) << std::dec << int(100. * getOutgoingQPILinkUtilization(i, l, sstate1, sstate2)) << "% "; cout << "\n"; } cout << longDiv; cout << "Total " << m->xPI() << " outgoing data and non-data traffic: " << unit_format(getAllOutgoingQPILinkBytes(sstate1, sstate2)) << "\n"; } } cout << resetColor(); if (show_socket_output) { cout << "\nMEM (GB)->|"; if (m->memoryTrafficMetricsAvailable()) cout << setNextColor() << " READ | WRITE |"; if (m->localMemoryRequestRatioMetricAvailable()) cout << setNextColor() << " LOCAL |"; if (m->PMMTrafficMetricsAvailable()) cout << setNextColor() << " PMM RD | PMM WR |"; if (m->HBMmemoryTrafficMetricsAvailable()) cout << setNextColor() << " HBM READ | HBM WRITE |"; if (m->memoryIOTrafficMetricAvailable()) cout << setNextColor() << " IO |"; if (m->memoryIOTrafficMetricAvailable()) cout << setNextColor() << " IA |"; if (m->memoryIOTrafficMetricAvailable()) cout << setNextColor() << " GT |"; if (m->packageEnergyMetricsAvailable()) cout << setNextColor() << " CPU energy |"; if (m->ppEnergyMetricsAvailable()) { cout << setNextColor() << " PP0 energy |"; cout << setNextColor() << " PP1 energy |"; } if (m->dramEnergyMetricsAvailable()) cout << setNextColor() << " DIMM energy |"; if (m->LLCReadMissLatencyMetricsAvailable()) cout << setNextColor() << " LLCRDMISSLAT (ns)|"; if (m->uncoreFrequencyMetricAvailable()) cout << setNextColor() << " UncFREQ (Ghz)|"; auto printCentered = [](const std::string& str, int width) { int len = str.length(); if(width < len) { std::cout << str; } else { int diff = width - len; int pad1 = diff/2; int pad2 = diff - pad1; std::cout << std::string(pad1, ' ') << str << std::string(pad2, ' '); } }; const std::vector uncoreDieTypes{getUncoreDieTypes(sktstate2[0])}; if (uncoreDieTypes.empty() == false) { cout << setNextColor() << " Unc(Ghz) "; for (auto & d: uncoreDieTypes) { cout << setNextColor(); printCentered(UncoreCounterState::getDieTypeStr(d), 7); cout << " "; } std::cout << "|" ; } cout << resetColor() << "\n"; cout << longDiv; for (uint32 i = 0; i < m->getNumSockets(); ++i) { cout << " SKT " << setw(2) << i; if (m->memoryTrafficMetricsAvailable()) cout << setNextColor() << " " << setw(5) << getBytesReadFromMC(sktstate1[i], sktstate2[i]) / double(1e9) << " " << setw(5) << getBytesWrittenToMC(sktstate1[i], sktstate2[i]) / double(1e9); if (m->localMemoryRequestRatioMetricAvailable()) cout << setNextColor() << " " << setw(3) << int(100.* getLocalMemoryRequestRatio(sktstate1[i], sktstate2[i])) << " %"; if (m->PMMTrafficMetricsAvailable()) cout << setNextColor() << " " << setw(5) << getBytesReadFromPMM(sktstate1[i], sktstate2[i]) / double(1e9) << " " << setw(5) << getBytesWrittenToPMM(sktstate1[i], sktstate2[i]) / double(1e9); if (m->HBMmemoryTrafficMetricsAvailable()) cout << setNextColor() << " " << setw(11) << getBytesReadFromEDC(sktstate1[i], sktstate2[i]) / double(1e9) << " " << setw(11) << getBytesWrittenToEDC(sktstate1[i], sktstate2[i]) / double(1e9); if (m->memoryIOTrafficMetricAvailable()) { cout << setNextColor() << " " << setw(5) << getIORequestBytesFromMC(sktstate1[i], sktstate2[i]) / double(1e9); cout << setNextColor() << " " << setw(5) << getIARequestBytesFromMC(sktstate1[i], sktstate2[i]) / double(1e9); cout << setNextColor() << " " << setw(5) << getGTRequestBytesFromMC(sktstate1[i], sktstate2[i]) / double(1e9); } if(m->packageEnergyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getConsumedJoules(sktstate1[i], sktstate2[i]); } if (m->ppEnergyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getConsumedJoules(0, sktstate1[i], sktstate2[i]); cout << setNextColor() << " "; cout << setw(6) << getConsumedJoules(1, sktstate1[i], sktstate2[i]); } if(m->dramEnergyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getDRAMConsumedJoules(sktstate1[i], sktstate2[i]); } if (m->LLCReadMissLatencyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getLLCReadMissLatency(sktstate1[i], sktstate2[i]); } if (m->uncoreFrequencyMetricAvailable()) { cout << setNextColor() << " "; cout << setw(4) << getAverageUncoreFrequencyGhz(sktstate1[i], sktstate2[i]); } const std::vector uncoreFrequencies{getUncoreFrequency(sktstate2[i])}; assert(uncoreFrequencies.size() == uncoreDieTypes.size()); if (uncoreFrequencies.empty() == false) { cout << setNextColor() << " "; for (auto & d: uncoreFrequencies) { cout << setNextColor() << " " << std::setw(4) << d/1e9 << " "; } } cout << resetColor() << "\n"; } cout << longDiv; if (m->getNumSockets() > 1) { cout << " *"; if (m->memoryTrafficMetricsAvailable()) cout << setNextColor() << " " << setw(5) << getBytesReadFromMC(sstate1, sstate2) / double(1e9) << " " << setw(5) << getBytesWrittenToMC(sstate1, sstate2) / double(1e9); if (m->localMemoryRequestRatioMetricAvailable()) cout << setNextColor() << " " << setw(3) << int(100.* getLocalMemoryRequestRatio(sstate1, sstate2)) << " %"; if (m->PMMTrafficMetricsAvailable()) cout << setNextColor() << " " << setw(5) << getBytesReadFromPMM(sstate1, sstate2) / double(1e9) << " " << setw(5) << getBytesWrittenToPMM(sstate1, sstate2) / double(1e9); if (m->memoryIOTrafficMetricAvailable()) cout << setNextColor() << " " << setw(5) << getIORequestBytesFromMC(sstate1, sstate2) / double(1e9); if (m->packageEnergyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getConsumedJoules(sstate1, sstate2); } if (m->ppEnergyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getConsumedJoules(0, sstate1, sstate2); cout << setNextColor() << " "; cout << setw(6) << getConsumedJoules(1, sstate1, sstate2); } if (m->dramEnergyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getDRAMConsumedJoules(sstate1, sstate2); } if (m->LLCReadMissLatencyMetricsAvailable()) { cout << setNextColor() << " "; cout << setw(6) << getLLCReadMissLatency(sstate1, sstate2); } if (m->uncoreFrequencyMetricAvailable()) { cout << setNextColor() << " "; cout << setw(4) << getAverageUncoreFrequencyGhz(sstate1, sstate2); } cout << resetColor() << "\n"; } } } void print_basic_metrics_csv_header(const PCM * m) { cout << "EXEC,IPC,FREQ,"; if (m->isActiveRelativeFrequencyAvailable()) cout << "AFREQ,CFREQ,"; if (m->isL3CacheMissesAvailable()) cout << "L3MISS,"; if (m->isL2CacheMissesAvailable()) cout << "L2MISS,"; if (m->isL3CacheHitRatioAvailable()) cout << "L3HIT,"; if (m->isL2CacheHitRatioAvailable()) cout << "L2HIT,"; if (m->isL3CacheMissesAvailable()) cout << "L3MPI,"; if (m->isL2CacheMissesAvailable()) cout << "L2MPI,"; if (m->isHWTMAL1Supported()) cout << "Frontend_bound(%),Bad_Speculation(%),Backend_Bound(%),Retiring(%),"; if (m->isHWTMAL2Supported()) { cout << "Fetch_latency_bound(%),Fetch_bandwidth_bound(%),Branch_misprediction_bound(%),Machine_clears_bound(%)," << "Buffer_Cache_Memory_bound(%),Core_bound(%),Heavy_operations_bound(%),Light_operations_bound(%),"; } } void print_csv_header_helper(const string & header, int count=1){ for(int i = 0; i < count; i++){ cout << header << ","; } } void print_basic_metrics_csv_semicolons(const PCM * m, const string & header) { print_csv_header_helper(header, 3); // EXEC;IPC;FREQ; if (m->isActiveRelativeFrequencyAvailable()) print_csv_header_helper(header, 2); // AFREQ;CFREQ; if (m->isL3CacheMissesAvailable()) print_csv_header_helper(header); // L3MISS; if (m->isL2CacheMissesAvailable()) print_csv_header_helper(header); // L2MISS; if (m->isL3CacheHitRatioAvailable()) print_csv_header_helper(header); // L3HIT if (m->isL2CacheHitRatioAvailable()) print_csv_header_helper(header); // L2HIT; if (m->isL3CacheMissesAvailable()) print_csv_header_helper(header); // L3MPI; if (m->isL2CacheMissesAvailable()) print_csv_header_helper(header); // L2MPI; if (m->isHWTMAL1Supported()) print_csv_header_helper(header, 4); // Frontend_bound(%),Bad_Speculation(%),Backend_Bound(%),Retiring(%) if (m->isHWTMAL2Supported()) print_csv_header_helper(header, 8); } void print_csv_header(PCM * m, const std::bitset & ycores, const bool show_core_output, const bool show_partial_core_output, const bool show_socket_output, const bool show_system_output ) { // print first header line string header; header = "System"; print_csv_header_helper(header,2); if (show_system_output) { print_basic_metrics_csv_semicolons(m,header); if (m->memoryTrafficMetricsAvailable()) print_csv_header_helper(header,2); if (m->localMemoryRequestRatioMetricAvailable()) print_csv_header_helper(header); if (m->PMMTrafficMetricsAvailable()) print_csv_header_helper(header,2); if (m->HBMmemoryTrafficMetricsAvailable()) print_csv_header_helper(header,2); print_csv_header_helper(header,7); if (m->getNumSockets() > 1) { // QPI info only for multi socket systems if (m->incomingQPITrafficMetricsAvailable()) print_csv_header_helper(header,2); if (m->outgoingQPITrafficMetricsAvailable()) print_csv_header_helper(header); } for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) print_csv_header_helper("System Core C-States"); for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isPackageCStateResidencySupported(s)) print_csv_header_helper("System Pack C-States"); if (m->packageEnergyMetricsAvailable()) print_csv_header_helper(header); if (m->ppEnergyMetricsAvailable()) print_csv_header_helper(header, 2); if (m->dramEnergyMetricsAvailable()) print_csv_header_helper(header); if (m->systemEnergyMetricAvailable()) print_csv_header_helper(header); if (m->LLCReadMissLatencyMetricsAvailable()) print_csv_header_helper(header); if (m->uncoreFrequencyMetricAvailable()) print_csv_header_helper(header); } if (show_socket_output) { for (uint32 i = 0; i < m->getNumSockets(); ++i) { header = "Socket " + std::to_string(i); print_basic_metrics_csv_semicolons(m,header); if (m->L3CacheOccupancyMetricAvailable()) print_csv_header_helper(header); if (m->CoreLocalMemoryBWMetricAvailable()) print_csv_header_helper(header); if (m->CoreRemoteMemoryBWMetricAvailable()) print_csv_header_helper(header); if (m->memoryTrafficMetricsAvailable()) print_csv_header_helper(header,2); if (m->localMemoryRequestRatioMetricAvailable()) print_csv_header_helper(header); if (m->PMMTrafficMetricsAvailable()) print_csv_header_helper(header,2); if (m->HBMmemoryTrafficMetricsAvailable()) print_csv_header_helper(header,2); if (m->memoryIOTrafficMetricAvailable()) print_csv_header_helper(header,3); print_csv_header_helper(header, 8); //TEMP,INST,ACYC,TIME(ticks),PhysIPC,PhysIPC%,INSTnom,INSTnom%, } if (m->getNumSockets() > 1 && (m->incomingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); for (uint32 s = 0; s < m->getNumSockets(); ++s) { header = "SKT" + std::to_string(s) + "dataIn"; print_csv_header_helper(header,qpiLinks); if (m->qpiUtilizationMetricsAvailable()) { header = "SKT" + std::to_string(s) + "dataIn (percent)"; print_csv_header_helper(header,qpiLinks); } } } if (m->getNumSockets() > 1 && (m->outgoingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); for (uint32 s = 0; s < m->getNumSockets(); ++s) { header = "SKT" + std::to_string(s) + "trafficOut"; print_csv_header_helper(header,qpiLinks); header = "SKT" + std::to_string(s) + "trafficOut (percent)"; print_csv_header_helper(header,qpiLinks); } } for (uint32 i = 0; i < m->getNumSockets(); ++i) { header = "SKT" + std::to_string(i) + " Core C-State"; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) print_csv_header_helper(header); header = "SKT" + std::to_string(i) + " Package C-State"; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isPackageCStateResidencySupported(s)) print_csv_header_helper(header); } if (m->packageEnergyMetricsAvailable()) { header = "Proc Energy (Joules)"; print_csv_header_helper(header,m->getNumSockets()); } if (m->ppEnergyMetricsAvailable()) { header = "Power Plane 0 Energy (Joules)"; print_csv_header_helper(header, m->getNumSockets()); header = "Power Plane 1 Energy (Joules)"; print_csv_header_helper(header, m->getNumSockets()); } if (m->dramEnergyMetricsAvailable()) { header = "DRAM Energy (Joules)"; print_csv_header_helper(header,m->getNumSockets()); } if (m->LLCReadMissLatencyMetricsAvailable()) { header = "LLCRDMISSLAT (ns)"; print_csv_header_helper(header,m->getNumSockets()); } if (m->uncoreFrequencyMetricAvailable()) { header = "UncFREQ (Ghz)"; print_csv_header_helper(header, m->getNumSockets()); } for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (size_t die = 0; die < m->getNumUFSDies(); ++die) { header = "UncFREQ Die " + std::to_string(die) + " (Ghz)"; print_csv_header_helper(header); } } } if (show_core_output) { for (uint32 i = 0; i < m->getNumCores(); ++i) { if (show_partial_core_output && ycores.test(i) == false) continue; std::stringstream hstream; hstream << "Core" << i << " (Socket" << setw(2) << m->getSocketId(i) << ")"; header = hstream.str(); print_basic_metrics_csv_semicolons(m,header); if (m->L3CacheOccupancyMetricAvailable()) print_csv_header_helper(header); if (m->CoreLocalMemoryBWMetricAvailable()) print_csv_header_helper(header); if (m->CoreRemoteMemoryBWMetricAvailable()) print_csv_header_helper(header); for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) print_csv_header_helper(header); print_csv_header_helper(header);// TEMP print_csv_header_helper(header,7); //ACYC,TIME(ticks),PhysIPC,PhysIPC%,INSTnom,INSTnom%, } } // print second header line cout << "\n"; printDateForCSV(Header2); if (show_system_output) { print_basic_metrics_csv_header(m); if (m->memoryTrafficMetricsAvailable()) cout << "READ,WRITE,"; if (m->localMemoryRequestRatioMetricAvailable()) cout << "LOCAL,"; if (m->PMMTrafficMetricsAvailable()) cout << "PMM_RD,PMM_WR,"; if (m->HBMmemoryTrafficMetricsAvailable()) cout << "HBM_READ,HBM_WRITE,"; cout << "INST,ACYC,TIME(ticks),PhysIPC,PhysIPC%,INSTnom,INSTnom%,"; if (m->getNumSockets() > 1) { // QPI info only for multi socket systems if (m->incomingQPITrafficMetricsAvailable()) cout << "Total" << m->xPI() << "in," << m->xPI() << "toMC,"; if (m->outgoingQPITrafficMetricsAvailable()) cout << "Total" << m->xPI() << "out,"; } for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) cout << "C" << s << "res%,"; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isPackageCStateResidencySupported(s)) cout << "C" << s << "res%,"; if (m->packageEnergyMetricsAvailable()) cout << "Proc Energy (Joules),"; if (m->ppEnergyMetricsAvailable()) { cout << "Power Plane 0 Energy (Joules),"; cout << "Power Plane 1 Energy (Joules),"; } if (m->dramEnergyMetricsAvailable()) cout << "DRAM Energy (Joules),"; if (m->systemEnergyMetricAvailable()) cout << "SYSTEM Energy (Joules),"; if (m->LLCReadMissLatencyMetricsAvailable()) cout << "LLCRDMISSLAT (ns),"; if (m->uncoreFrequencyMetricAvailable()) cout << "UncFREQ (Ghz),"; } if (show_socket_output) { for (uint32 i = 0; i < m->getNumSockets(); ++i) { print_basic_metrics_csv_header(m); if (m->L3CacheOccupancyMetricAvailable()) cout << "L3OCC,"; if (m->CoreLocalMemoryBWMetricAvailable()) cout << "LMB,"; if (m->CoreRemoteMemoryBWMetricAvailable()) cout << "RMB,"; if (m->memoryTrafficMetricsAvailable()) cout << "READ,WRITE,"; if (m->localMemoryRequestRatioMetricAvailable()) cout << "LOCAL,"; if (m->PMMTrafficMetricsAvailable()) cout << "PMM_RD,PMM_WR,"; if (m->HBMmemoryTrafficMetricsAvailable()) cout << "HBM_READ,HBM_WRITE,"; if (m->memoryIOTrafficMetricAvailable()) cout << "IO,IA,GT,"; cout << "TEMP,INST,ACYC,TIME(ticks),PhysIPC,PhysIPC%,INSTnom,INSTnom%,"; } if (m->getNumSockets() > 1 && (m->incomingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 i = 0; i < qpiLinks; ++i) cout << m->xPI() << i << ","; if (m->qpiUtilizationMetricsAvailable()) for (uint32 i = 0; i < qpiLinks; ++i) cout << m->xPI() << i << ","; } } if (m->getNumSockets() > 1 && (m->outgoingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); for (uint32 s = 0; s < m->getNumSockets(); ++s) { for (uint32 i = 0; i < qpiLinks; ++i) cout << m->xPI() << i << ","; for (uint32 i = 0; i < qpiLinks; ++i) cout << m->xPI() << i << ","; } } for (uint32 i = 0; i < m->getNumSockets(); ++i) { for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) cout << "C" << s << "res%,"; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isPackageCStateResidencySupported(s)) cout << "C" << s << "res%,"; } auto printSKT = [] (const uint32 i, const uint32 count = 1) { for (uint32 j = 0; j < count; ++j) { cout << "SKT" << i << ","; } }; if (m->packageEnergyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) printSKT(i); } if (m->ppEnergyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) printSKT(i, 2); } if (m->dramEnergyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) printSKT(i); } if (m->LLCReadMissLatencyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) printSKT(i); } if (m->uncoreFrequencyMetricAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) printSKT(i); } for (uint32 i = 0; i < m->getNumSockets(); ++i) { printSKT(i, m->getNumUFSDies()); } } if (show_core_output) { for (uint32 i = 0; i < m->getNumCores(); ++i) { if (show_partial_core_output && ycores.test(i) == false) continue; print_basic_metrics_csv_header(m); if (m->L3CacheOccupancyMetricAvailable()) cout << "L3OCC,"; if (m->CoreLocalMemoryBWMetricAvailable()) cout << "LMB,"; if (m->CoreRemoteMemoryBWMetricAvailable()) cout << "RMB,"; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) cout << "C" << s << "res%,"; cout << "TEMP,"; cout << "INST,ACYC,TIME(ticks),PhysIPC,PhysIPC%,INSTnom,INSTnom%,"; } } } template void print_basic_metrics_csv(const PCM * m, const State & state1, const State & state2, const bool print_last_semicolon = true) { cout << getExecUsage(state1, state2) << ',' << getIPC(state1, state2) << ',' << getRelativeFrequency(state1, state2); if (m->isActiveRelativeFrequencyAvailable()) cout << ',' << getActiveRelativeFrequency(state1, state2) << ',' << getActiveAverageFrequency(state1, state2)/1e9; if (m->isL3CacheMissesAvailable()) cout << ',' << float_format(getL3CacheMisses(state1, state2)); if (m->isL2CacheMissesAvailable()) cout << ',' << float_format(getL2CacheMisses(state1, state2)); if (m->isL3CacheHitRatioAvailable()) cout << ',' << getL3CacheHitRatio(state1, state2); if (m->isL2CacheHitRatioAvailable()) cout << ',' << getL2CacheHitRatio(state1, state2); cout.precision(4); if (m->isL3CacheMissesAvailable()) cout << ',' << double(getL3CacheMisses(state1, state2)) / getInstructionsRetired(state1, state2); if (m->isL2CacheMissesAvailable()) cout << ',' << double(getL2CacheMisses(state1, state2)) / getInstructionsRetired(state1, state2); cout.precision(2); if (m->isHWTMAL1Supported()) { cout << ',' << int(100. * getFrontendBound(state1, state2)); cout << ',' << int(100. * getBadSpeculation(state1, state2)); cout << ',' << int(100. * getBackendBound(state1, state2)); cout << ',' << int(100. * getRetiring(state1, state2)); } if (m->isHWTMAL2Supported()) { cout << ',' << int(100. * getFetchLatencyBound(state1, state2)); cout << ',' << int(100. * getFetchBandwidthBound(state1, state2)); cout << ',' << int(100. * getBranchMispredictionBound(state1, state2)); cout << ',' << int(100. * getMachineClearsBound(state1, state2)); cout << ',' << int(100. * getMemoryBound(state1, state2)); cout << ',' << int(100. * getCoreBound(state1, state2)); cout << ',' << int(100. * getHeavyOperationsBound(state1, state2)); cout << ',' << int(100. * getLightOperationsBound(state1, state2)); } if (print_last_semicolon) cout << ","; } template void print_other_metrics_csv(const PCM * m, const State & state1, const State & state2) { if (m->L3CacheOccupancyMetricAvailable()) cout << ',' << l3cache_occ_format(getL3CacheOccupancy(state2)); if (m->CoreLocalMemoryBWMetricAvailable()) cout << ',' << getLocalMemoryBW(state1, state2); if (m->CoreRemoteMemoryBWMetricAvailable()) cout << ',' << getRemoteMemoryBW(state1, state2); } void print_csv(PCM * m, const std::vector & cstates1, const std::vector & cstates2, const std::vector & sktstate1, const std::vector & sktstate2, const std::bitset & ycores, const SystemCounterState& sstate1, const SystemCounterState& sstate2, const bool show_core_output, const bool show_partial_core_output, const bool show_socket_output, const bool show_system_output ) { cout << "\n"; printDateForCSV(CsvOutputType::Data); if (show_system_output) { print_basic_metrics_csv(m, sstate1, sstate2); if (m->memoryTrafficMetricsAvailable()) cout << getBytesReadFromMC(sstate1, sstate2) / double(1e9) << ',' << getBytesWrittenToMC(sstate1, sstate2) / double(1e9) << ','; if (m->localMemoryRequestRatioMetricAvailable()) cout << int(100. * getLocalMemoryRequestRatio(sstate1, sstate2)) << ','; if (m->PMMTrafficMetricsAvailable()) cout << getBytesReadFromPMM(sstate1, sstate2) / double(1e9) << ',' << getBytesWrittenToPMM(sstate1, sstate2) / double(1e9) << ','; if (m->HBMmemoryTrafficMetricsAvailable()) cout << getBytesReadFromEDC(sstate1, sstate2) / double(1e9) << ',' << getBytesWrittenToEDC(sstate1, sstate2) / double(1e9) << ','; cout << float_format(getInstructionsRetired(sstate1, sstate2)) << "," << float_format(getCycles(sstate1, sstate2)) << "," << float_format(getInvariantTSC(cstates1[0], cstates2[0])) << "," << getCoreIPC(sstate1, sstate2) << "," << 100. * (getCoreIPC(sstate1, sstate2) / double(m->getMaxIPC())) << "," << getTotalExecUsage(sstate1, sstate2) << "," << 100. * (getTotalExecUsage(sstate1, sstate2) / double(m->getMaxIPC())) << ","; if (m->getNumSockets() > 1) { // QPI info only for multi socket systems if (m->incomingQPITrafficMetricsAvailable()) cout << float_format(getAllIncomingQPILinkBytes(sstate1, sstate2)) << "," << getQPItoMCTrafficRatio(sstate1, sstate2) << ","; if (m->outgoingQPITrafficMetricsAvailable()) cout << float_format(getAllOutgoingQPILinkBytes(sstate1, sstate2)) << ","; } for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) cout << getCoreCStateResidency(s, sstate1, sstate2) * 100 << ","; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isPackageCStateResidencySupported(s)) cout << getPackageCStateResidency(s, sstate1, sstate2) * 100 << ","; if (m->packageEnergyMetricsAvailable()) cout << getConsumedJoules(sstate1, sstate2) << ","; if (m->ppEnergyMetricsAvailable()) cout << getConsumedJoules(0, sstate1, sstate2) << "," << getConsumedJoules(1, sstate1, sstate2) << ","; if (m->dramEnergyMetricsAvailable()) cout << getDRAMConsumedJoules(sstate1, sstate2) << ","; if (m->systemEnergyMetricAvailable()) cout << getSystemConsumedJoules(sstate1, sstate2) << ","; if (m->LLCReadMissLatencyMetricsAvailable()) cout << getLLCReadMissLatency(sstate1, sstate2) << ","; if (m->uncoreFrequencyMetricAvailable()) cout << getAverageUncoreFrequencyGhz(sstate1, sstate2) << ","; } if (show_socket_output) { for (uint32 i = 0; i < m->getNumSockets(); ++i) { print_basic_metrics_csv(m, sktstate1[i], sktstate2[i], false); print_other_metrics_csv(m, sktstate1[i], sktstate2[i]); if (m->memoryTrafficMetricsAvailable()) cout << ',' << getBytesReadFromMC(sktstate1[i], sktstate2[i]) / double(1e9) << ',' << getBytesWrittenToMC(sktstate1[i], sktstate2[i]) / double(1e9); if (m->localMemoryRequestRatioMetricAvailable()) cout << ',' << int(100. * getLocalMemoryRequestRatio(sktstate1[i], sktstate2[i])); if (m->PMMTrafficMetricsAvailable()) cout << ',' << getBytesReadFromPMM(sktstate1[i], sktstate2[i]) / double(1e9) << ',' << getBytesWrittenToPMM(sktstate1[i], sktstate2[i]) / double(1e9); if (m->HBMmemoryTrafficMetricsAvailable()) cout << ',' << getBytesReadFromEDC(sktstate1[i], sktstate2[i]) / double(1e9) << ',' << getBytesWrittenToEDC(sktstate1[i], sktstate2[i]) / double(1e9); if (m->memoryIOTrafficMetricAvailable()) { cout << ',' << getIORequestBytesFromMC(sktstate1[i], sktstate2[i]) / double(1e9) << ',' << getIARequestBytesFromMC(sktstate1[i], sktstate2[i]) / double(1e9) << ',' << getGTRequestBytesFromMC(sktstate1[i], sktstate2[i]) / double(1e9); } cout << ',' << temp_format(sktstate2[i].getThermalHeadroom()) << ','; cout << float_format(getInstructionsRetired(sktstate1[i], sktstate2[i])) << "," << float_format(getCycles(sktstate1[i], sktstate2[i])) << "," << float_format(getInvariantTSC(cstates1[0], cstates2[0])) << "," << getCoreIPC(sktstate1[i], sktstate2[i]) << "," << 100. * (getCoreIPC(sktstate1[i], sktstate2[i]) / double(m->getMaxIPC())) << "," << getTotalExecUsage(sktstate1[i], sktstate2[i]) << "," << 100. * (getTotalExecUsage(sktstate1[i], sktstate2[i]) / double(m->getMaxIPC())) << ","; } if (m->getNumSockets() > 1 && (m->incomingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); for (uint32 i = 0; i < m->getNumSockets(); ++i) { for (uint32 l = 0; l < qpiLinks; ++l) cout << float_format(getIncomingQPILinkBytes(i, l, sstate1, sstate2)) << ","; if (m->qpiUtilizationMetricsAvailable()) { for (uint32 l = 0; l < qpiLinks; ++l) cout << setw(3) << std::dec << int(100. * getIncomingQPILinkUtilization(i, l, sstate1, sstate2)) << "%,"; } } } if (m->getNumSockets() > 1 && (m->outgoingQPITrafficMetricsAvailable())) // QPI info only for multi socket systems { const uint32 qpiLinks = (uint32)m->getQPILinksPerSocket(); for (uint32 i = 0; i < m->getNumSockets(); ++i) { for (uint32 l = 0; l < qpiLinks; ++l) cout << float_format(getOutgoingQPILinkBytes(i, l, sstate1, sstate2)) << ","; for (uint32 l = 0; l < qpiLinks; ++l) cout << setw(3) << std::dec << int(100. * getOutgoingQPILinkUtilization(i, l, sstate1, sstate2)) << "%,"; } } for (uint32 i = 0; i < m->getNumSockets(); ++i) { for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) cout << getCoreCStateResidency(s, sktstate1[i], sktstate2[i]) * 100 << ","; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isPackageCStateResidencySupported(s)) cout << getPackageCStateResidency(s, sktstate1[i], sktstate2[i]) * 100 << ","; } if (m->packageEnergyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) cout << getConsumedJoules(sktstate1[i], sktstate2[i]) << ","; } if (m->ppEnergyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) cout << getConsumedJoules(0, sktstate1[i], sktstate2[i]) << "," << getConsumedJoules(1, sktstate1[i], sktstate2[i]) << ","; } if (m->dramEnergyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) cout << getDRAMConsumedJoules(sktstate1[i], sktstate2[i]) << " ,"; } if (m->LLCReadMissLatencyMetricsAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) cout << getLLCReadMissLatency(sktstate1[i], sktstate2[i]) << " ,"; } if (m->uncoreFrequencyMetricAvailable()) { for (uint32 i = 0; i < m->getNumSockets(); ++i) cout << getAverageUncoreFrequencyGhz(sktstate1[i], sktstate2[i]) << ","; } for (uint32 i = 0; i < m->getNumSockets(); ++i) { const auto freqs = getUncoreFrequency(sktstate2[i]); assert(freqs.size() == (size_t)m->getNumUFSDies()); for (auto & f : freqs) { cout << f/1e9 << ","; } } } if (show_core_output) { for (uint32 i = 0; i < m->getNumCores(); ++i) { if (show_partial_core_output && ycores.test(i) == false) continue; print_basic_metrics_csv(m, cstates1[i], cstates2[i], false); print_other_metrics_csv(m, cstates1[i], cstates2[i]); cout << ','; for (int s = 0; s <= PCM::MAX_C_STATE; ++s) if (m->isCoreCStateResidencySupported(s)) cout << getCoreCStateResidency(s, cstates1[i], cstates2[i]) * 100 << ","; cout << temp_format(cstates2[i].getThermalHeadroom()) << ','; cout << float_format(getInstructionsRetired(cstates1[i], cstates2[i])) << "," << float_format(getCycles(cstates1[i], cstates2[i])) << "," << float_format(getInvariantTSC(cstates1[0], cstates2[0])) << "," << getCoreIPC(cstates1[i], cstates2[i]) << "," << 100. * (getCoreIPC(cstates1[i], cstates2[i]) / double(m->getMaxIPC())) << "," << getTotalExecUsage(cstates1[i], cstates2[i]) << "," << 100. * (getTotalExecUsage(cstates1[i], cstates2[i]) / double(m->getMaxIPC())) << ","; } } } #ifndef UNIT_TEST PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) { if(print_version(argc, argv)) exit(EXIT_SUCCESS); null_stream nullStream2; #ifdef PCM_FORCE_SILENT null_stream nullStream1; std::cout.rdbuf(&nullStream1); std::cerr.rdbuf(&nullStream2); #else check_and_set_silent(argc, argv, nullStream2); #endif set_signal_handlers(); cerr << "\n"; cerr << " Intel(r) Performance Counter Monitor " << PCM_VERSION << "\n"; cerr << "\n"; cerr << "\n"; // if delay is not specified: use either default (1 second), // or only read counters before or after PCM started: keep PCM blocked double delay = -1.0; int pid{-1}; char *sysCmd = NULL; char **sysArgv = NULL; bool show_core_output = true; bool show_partial_core_output = false; bool show_socket_output = true; bool show_system_output = true; bool csv_output = false; bool reset_pmu = false; bool disable_JKT_workaround = false; // as per http://software.intel.com/en-us/articles/performance-impact-when-sampling-certain-llc-events-on-snb-ep-with-vtune bool enforceFlush = false; int metricVersion = 2; parsePID(argc, argv, pid); MainLoop mainLoop; std::bitset ycores; string program = string(argv[0]); PCM * m = PCM::getInstance(); if (argc > 1) do { argv++; argc--; std::string arg_value; if (*argv == nullptr) { continue; } else if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { print_help(program); exit(EXIT_FAILURE); } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { // handled in check_and_set_silent continue; } else if (check_argument_equals(*argv, {"--yescores", "-yc", "/yc"})) { argv++; argc--; show_partial_core_output = true; if(*argv == NULL) { cerr << "Error: --yescores requires additional argument.\n"; exit(EXIT_FAILURE); } std::stringstream ss(*argv); while(ss.good()) { string s; int core_id; std::getline(ss, s, ','); if(s.empty()) continue; core_id = atoi(s.c_str()); if(core_id > MAX_CORES) { cerr << "Core ID:" << core_id << " exceed maximum range " << MAX_CORES << ", program abort\n"; exit(EXIT_FAILURE); } ycores.set(core_id, true); } if(m->getNumCores() > MAX_CORES) { cerr << "Error: --yescores option is enabled, but #define MAX_CORES " << MAX_CORES << " is less than m->getNumCores() = " << m->getNumCores() << "\n"; cerr << "There is a potential to crash the system. Please increase MAX_CORES to at least " << m->getNumCores() << " and re-enable this option.\n"; exit(EXIT_FAILURE); } continue; } else if (check_argument_equals(*argv, {"--nocores", "-nc", "/nc"})) { show_core_output = false; continue; } else if (check_argument_equals(*argv, {"--nosockets", "-ns", "/ns"})) { show_socket_output = false; continue; } else if (check_argument_equals(*argv, {"--nosystem", "-nsys", "/nsys"})) { show_system_output = false; continue; } else if (check_argument_equals(*argv, {"--color"})) { setColorEnabled(); continue; } else if (check_argument_equals(*argv, { "--no-color" })) { setColorEnabled(false); continue; } else if (check_argument_equals(*argv, {"-csv", "/csv"})) { csv_output = true; } else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) { csv_output = true; if (!arg_value.empty()) { m->setOutput(arg_value); } continue; } else if (extract_argument_value(*argv, {"-m", "/m"}, arg_value)) { if (!arg_value.empty()) { metricVersion = atoi(arg_value.c_str()); } if (metricVersion == 0) { metricVersion = 2; } continue; } else if (isPIDOption(argv)) { argv++; argc--; continue; } else if (mainLoop.parseArg(*argv)) { continue; } else if (check_argument_equals(*argv, {"-reset", "/reset", "-r"})) { reset_pmu = true; continue; } else if (CheckAndForceRTMAbortMode(*argv, m)) { continue; } else if (check_argument_equals(*argv, {"--noJKTWA"})) { disable_JKT_workaround = true; continue; } PCM_ENFORCE_FLUSH_OPTION #ifdef _MSC_VER else if (check_argument_equals(*argv, {"--uninstallDriver"})) { Driver tmpDrvObject; tmpDrvObject.uninstall(); cerr << "msr.sys driver has been uninstalled. You might need to reboot the system to make this effective.\n"; exit(EXIT_SUCCESS); } else if (check_argument_equals(*argv, {"--installDriver"})) { Driver tmpDrvObject = Driver(Driver::msrLocalPath()); if (!tmpDrvObject.start()) { tcerr << "Can not access CPU counters\n"; tcerr << "You must have a signed driver at " << tmpDrvObject.driverPath() << " and have administrator rights to run this program\n"; exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } #endif else if (check_argument_equals(*argv, {"--"})) { argv++; sysCmd = *argv; sysArgv = argv; break; } else { delay = parse_delay(*argv, program, (print_usage_func)print_help); continue; } } while (argc > 1); // end of command line partsing loop if (disable_JKT_workaround) m->disableJKTWorkaround(); if (reset_pmu) { cerr << "\n Resetting PMU configuration\n"; m->resetPMU(); } // program() creates common semaphore for the singleton, so ideally to be called before any other references to PCM const PCM::ErrorCode status = m->program(PCM::DEFAULT_EVENTS, nullptr, false, pid); switch (status) { case PCM::Success: break; case PCM::MSRAccessDenied: cerr << "Access to Intel(r) Performance Counter Monitor has denied (no MSR or PCI CFG space access).\n"; exit(EXIT_FAILURE); case PCM::PMUBusy: cerr << "Access to Intel(r) Performance Counter Monitor has denied (Performance Monitoring Unit is occupied by other application). Try to stop the application that uses PMU.\n"; cerr << "Alternatively you can try running PCM with option -r to reset PMU.\n"; exit(EXIT_FAILURE); default: cerr << "Access to Intel(r) Performance Counter Monitor has denied (Unknown error).\n"; exit(EXIT_FAILURE); } print_cpu_details(); std::vector cstates1, cstates2; std::vector sktstate1, sktstate2; SystemCounterState sstate1, sstate2; const auto cpu_family_model = m->getCPUFamilyModel(); print_pid_collection_message(pid); if ((sysCmd != NULL) && (delay <= 0.0)) { // in case external command is provided in command line, and // delay either not provided (-1) or is zero m->setBlocked(true); } else { m->setBlocked(false); } // in case delay is not provided in command line => set default if (delay <= 0.0) delay = PCM_DELAY_DEFAULT; // cerr << "DEBUG: Delay: " << delay << " seconds. Blocked: " << m->isBlocked() << "\n"; if (csv_output) { print_csv_header(m, ycores, show_core_output, show_partial_core_output, show_socket_output, show_system_output); } m->getAllCounterStates(sstate1, sktstate1, cstates1); if (sysCmd != NULL) { MySystem(sysCmd, sysArgv); } mainLoop([&]() { if (enforceFlush || !csv_output) cout << std::flush; calibratedSleep(delay, sysCmd, mainLoop, m); m->getAllCounterStates(sstate2, sktstate2, cstates2); if (csv_output) print_csv(m, cstates1, cstates2, sktstate1, sktstate2, ycores, sstate1, sstate2, show_core_output, show_partial_core_output, show_socket_output, show_system_output); else print_output(m, cstates1, cstates2, sktstate1, sktstate2, ycores, sstate1, sstate2, cpu_family_model, show_core_output, show_partial_core_output, show_socket_output, show_system_output, metricVersion); std::swap(sstate1, sstate2); std::swap(sktstate1, sktstate2); std::swap(cstates1, cstates2); if (m->isBlocked()) { // in case PCM was blocked after spawning child application: break monitoring loop here return false; } return true; }); exit(EXIT_SUCCESS); } #endif // UNIT_TESTpcm-202502/src/pmt.cpp000066400000000000000000000232541475730356400145040ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2024, Intel Corporation #include "pmt.h" #include "utils.h" #include #include #include #include #include #include #ifdef PCM_PUGIXML_AVAILABLE #include "pugixml/src/pugixml.hpp" #endif #ifdef __linux__ #include #endif namespace pcm { #ifdef __linux__ class TelemetryArrayLinux : public TelemetryArrayInterface { TelemetryArrayLinux() = delete; typedef std::vector FileVector; typedef std::unordered_map FileMap; static std::shared_ptr TelemetryFiles; static FileMap & getTelemetryFiles() { if (!TelemetryFiles.get()) { std::shared_ptr TelemetryFilesTemp = std::make_shared(); auto paths = findPathsFromPattern("/sys/class/intel_pmt/telem*"); for (auto & path : paths) { const auto guid = read_number(readSysFS((path + "/guid").c_str()).c_str()); #if 0 auto size = read_number(readSysFS((path + "/size").c_str()).c_str()); std::cout << "path: " << path << " guid: 0x" << std::hex << guid << " size: "<< std::dec << size << std::endl; #endif auto file = std::fopen((path + "/telem").c_str(), "rb"); if (!file) { std::cerr << "Error: failed to open " << path << "/telem" << std::endl; continue; } TelemetryFilesTemp->operator[](guid).push_back(file); } // print the telemetry files for (auto & guid : *TelemetryFilesTemp) { auto & files = guid.second; for (auto & file : files) { if (!file) { std::cerr << "Error: file is null" << std::endl; continue; } // std::cout << "guid: 0x" << std::hex << guid.first << " file: " << file << std::endl; } } TelemetryFiles = TelemetryFilesTemp; } return *TelemetryFiles; } std::vector data; size_t uid, instance; public: TelemetryArrayLinux(const size_t uid_, const size_t instance_): uid(uid_), instance(instance_) { assert(instance < numInstances(uid)); TelemetryArrayLinux::load(); } static size_t numInstances(const size_t uid) { auto t = getTelemetryFiles(); if (t.find(uid) == t.end()) { return 0; } return t.at(uid).size(); } static std::vector getUIDs() { auto t = getTelemetryFiles(); std::vector result; for (auto & guid : t) { result.push_back(guid.first); } return result; } virtual ~TelemetryArrayLinux() override { } size_t size() override { return data.size(); } void load() override { FILE * file = getTelemetryFiles().at(uid).at(instance); assert(file); // get the file size fseek(file, 0, SEEK_END); const auto pos = ftell(file); if (pos < 0) { std::cerr << "Error: failed to get file size" << std::endl; return; } const size_t fileSize = pos; fseek(file, 0, SEEK_SET); data.resize(fileSize); const size_t bytesRead = fread(data.data(), 1, fileSize, file); if (bytesRead != fileSize) { std::cerr << "Error: failed to read " << fileSize << " bytes from telemetry file" << std::endl; } } uint64 get(size_t qWordOffset, size_t lsb, size_t msb) override { assert(qWordOffset * sizeof(uint64) + sizeof(uint64) <= data.size()); return extract_bits(*reinterpret_cast(&data[qWordOffset * sizeof(uint64)]), lsb, msb); } }; std::shared_ptr TelemetryArrayLinux::TelemetryFiles; #else class TelemetryArrayDummy : public TelemetryArrayInterface { TelemetryArrayDummy() = delete; public: TelemetryArrayDummy(const size_t /* uid */, const size_t /* instance */) {}; static size_t numInstances(const size_t /* uid */) { return 0; }; static std::vector getUIDs() { return std::vector(); }; virtual ~TelemetryArrayDummy() override {}; size_t size() override { return 0;}; // in bytes void load() override {}; uint64 get(size_t , size_t , size_t ) override { return 0;} ; }; #endif TelemetryArray::TelemetryArray(const size_t uid, const size_t instance) { #ifdef __linux__ impl = std::make_shared(uid, instance); #else impl = std::make_shared(uid, instance); #endif } size_t TelemetryArray::numInstances(const size_t uid) { #ifdef __linux__ return TelemetryArrayLinux::numInstances(uid); #else return TelemetryArrayDummy::numInstances(uid); #endif } std::vector TelemetryArray::getUIDs() { #ifdef __linux__ return TelemetryArrayLinux::getUIDs(); #else return TelemetryArrayDummy::getUIDs(); #endif } TelemetryArray::~TelemetryArray() {} size_t TelemetryArray::size() { assert(impl.get()); return impl->size(); } void TelemetryArray::load() { assert(impl.get()); impl->load(); } uint64 TelemetryArray::get(size_t qWordOffset, size_t lsb, size_t msb) { assert(impl.get()); return impl->get(qWordOffset, lsb, msb); } bool TelemetryDB::loadFromXML(const std::string& pmtXMLPath) { #ifdef PCM_PUGIXML_AVAILABLE pugi::xml_document doc; auto result = doc.load_file((pmtXMLPath + "/xml/pmt.xml").c_str()); if (!result) { std::cerr << "Error: failed to load " << pmtXMLPath << "/xml/pmt.xml" << std::endl; return false; } constexpr bool debug = false; auto guids = TelemetryArray::getUIDs(); for (pugi::xml_node mapping: doc.child("pmt").child("mappings").children("mapping")) { auto guid = read_number(mapping.attribute("guid").value()); if (std::find(guids.begin(), guids.end(), guid) == guids.end()) { // std::cerr << " guid " << std::hex << guid << " not found in telemetry files" << std::endl; continue; } if (debug) std::cout << "Found mapping with guid: " << mapping.attribute("guid").value() << std::endl; if (debug) std::cout << " Description: " << mapping.child("description").text().as_string() << std::endl; const auto xmlset = mapping.child("xmlset"); const auto basedir = xmlset.child("basedir").text().as_string(); const auto aggregator = xmlset.child("aggregator").text().as_string(); const auto aggregator_path = pmtXMLPath + "/xml/" + basedir + "/" + aggregator; if (debug) std::cout << " Aggregator XML path: " << aggregator_path << std::endl; pugi::xml_document aggregatorDoc; auto aggregatorResult = aggregatorDoc.load_file(aggregator_path.c_str()); if (!aggregatorResult) { std::cerr << "Error: failed to load " << aggregator_path << std::endl; return false; } auto aggregatorNode = aggregatorDoc.child("TELEM:Aggregator"); const std::string aggregatorName = aggregatorNode.child("TELEM:name").text().as_string(); if (debug) std::cout << " Agregator name: " << aggregatorName << std::endl; PMTRecord record; record.uid = guid; for (pugi::xml_node sampleGroup: aggregatorNode.children("TELEM:SampleGroup")) { const auto sampleID = sampleGroup.attribute("sampleID").as_uint(); if (debug) std::cout << " SampleID: " << sampleID << std::endl; record.qWordOffset = sampleID; for (pugi::xml_node sample: sampleGroup.children("TELC:sample")) { const auto name = sample.attribute("name").as_string(); const std::string sampleSubGroup = sample.child("TELC:sampleSubGroup").text().as_string(); record.fullName = aggregatorName + "." + sampleSubGroup + "." + name; record.sampleType = sample.child("TELC:sampleType").text().as_string(); record.lsb = sample.child("TELC:lsb").text().as_uint(); record.msb = sample.child("TELC:msb").text().as_uint(); record.description = sample.child("TELC:description").text().as_string(); if (debug) std::cout << " "; if (debug) record.print(std::cout); records.push_back(record); } } if (debug) std::cout << std::endl; } return true; #else (void)pmtXMLPath; // suppress warning std::cerr << "INFO: pugixml library is not available" << std::endl; return false; #endif } std::vector TelemetryDB::lookup(const std::string & name) { std::vector result; for (auto & record : records) { if (record.fullName.find(name) != std::string::npos) { result.push_back(record); } } return result; } std::vector TelemetryDB::ilookup(const std::string & name) { std::vector result; auto to_lower = [](const std::string & s) -> std::string { std::string result; for (auto c : s) { result.push_back(std::tolower(c)); } return result; }; for (auto & record : records) { if (to_lower(record.fullName).find(to_lower(name)) != std::string::npos) { result.push_back(record); } } return result; } }; // namespace pcmpcm-202502/src/pmt.h000066400000000000000000000034431475730356400141470ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2024, Intel Corporation #pragma once #include "types.h" #include #include namespace pcm { class TelemetryArrayInterface { public: virtual size_t size() = 0; // in bytes virtual size_t numQWords() { return size() / sizeof(uint64); } virtual void load() = 0; virtual uint64 get(size_t qWordOffset, size_t lsb, size_t msb) = 0; virtual ~TelemetryArrayInterface() {}; }; class TelemetryArray : public TelemetryArrayInterface { TelemetryArray() = delete; std::shared_ptr impl; public: TelemetryArray(const size_t /* uid */, const size_t /* instance */); static size_t numInstances(const size_t /* uid */); static std::vector getUIDs(); virtual ~TelemetryArray() override; size_t size() override; // in bytes void load() override; uint64 get(size_t qWordOffset, size_t lsb, size_t msb) override; }; class TelemetryDB { public: struct PMTRecord { size_t uid; std::string fullName; std::string sampleType; size_t qWordOffset; uint32 lsb; uint32 msb; std::string description; void print(std::ostream & os) const { os << "uid: " << uid << " fullName: " << fullName << " description: \"" << description << "\" sampleType: " << sampleType << " qWordOffset: " << qWordOffset << " lsb: " << lsb << " msb: " << msb << std::endl; } }; std::vector records; TelemetryDB() = default; bool loadFromXML(const std::string& pmtXMLPath); virtual ~TelemetryDB() = default; std::vector lookup(const std::string & name); std::vector ilookup(const std::string & name); }; } // namespace pcmpcm-202502/src/pugixml/000077500000000000000000000000001475730356400146575ustar00rootroot00000000000000pcm-202502/src/readmem.cpp000066400000000000000000000027601475730356400153150ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // #include "cpucounters.h" #include #include #include #include #include using std::cout; inline double my_timestamp() { struct timeval tp; gettimeofday(&tp, NULL); return double(tp.tv_sec) + tp.tv_usec / 1000000.; } struct T { int key[1] = { 0 }; int data[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; T() { } T(int a) { key[0] = a; } bool operator == (const T & k) const { return k.key[0] == key[0]; } }; template void Memory_intensive_task(DS & ds) { // cppcheck-suppress ignoredReturnValue std::find(ds.begin(), ds.end(), ds.size()); } int main(int argc, char * argv[]) { std::vector vector; int nelements = 13000000; int i = 0; int delay = atoi(argv[1]); cout << "Elements data size: " << sizeof(T) * nelements / 1024 << " KB\n"; for ( ; i < nelements; ++i) { vector.push_back(i); } double before_ts, after_ts; while (1) { before_ts = my_timestamp(); cout << "Reading memory for " << delay << " seconds\n" << flush; do { Memory_intensive_task(vector); after_ts = my_timestamp(); } while ((after_ts - before_ts) < delay); cout << "Sleeping for " << delay << " seconds\n" << flush; sleep(delay); } return 0; } pcm-202502/src/realtime.cpp000066400000000000000000000160131475730356400155010ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // #include "cpucounters.h" #include "cpuasynchcounter.h" #include #include #include #include #include /*! \file realtime.cpp \brief Two use-cases: realtime data structure performance analysis and memory-bandwidth aware scheduling */ using std::cout; inline double my_timestamp() { struct timeval tp; gettimeofday(&tp, NULL); return double(tp.tv_sec) + tp.tv_usec / 1000000.; } long long int fib(long long int num) { long long int result = 1, a = 1, b = 1; for (long long int i = 3; i <= num; ++i) { result = a + b; a = b; b = result; } return result; } SystemCounterState before_sstate, after_sstate; double before_time, after_time; AsynchronCounterState counters; long long int all_fib = 0; void CPU_intensive_task() { cout << "CPU task\n"; all_fib += fib(80000000ULL + (rand() % 2)); } template void Memory_intensive_task(DS & ds) { cout << "Mem task\n"; // cppcheck-suppress ignoredReturnValue std::find(ds.begin(), ds.end(), ds.size()); } double currentMemoryBandwidth() { return (counters.getSystem() + counters.getSystem()) / (1024 * 1024); } template void measure(DS & ds, size_t repeat, size_t nelements) { SystemCounterState before_sstate, after_sstate; double before_ts = 0.0, after_ts; // warm up // cppcheck-suppress ignoredReturnValue std::find(ds.begin(), ds.end(), nelements); double before1_ts; #if 0 for (int kkk = 1000; kkk > 0; --kkk) { ::sleep(1); before1_ts = my_timestamp(); // start measuring before_sstate = getSystemCounterState(); before_ts = my_timestamp(); cout << "Response time of getSystemCounterState(): " << 1000. * (before_ts - before1_ts) << " ms\n"; } #endif // cppcheck-suppress ignoredReturnValue for (int j = 0; j < repeat; ++j) std::find(ds.begin(), ds.end(), nelements); // stop measuring after_sstate = getSystemCounterState(); after_ts = my_timestamp(); cout << "\nSearch runtime: " << ((after_ts - before_ts) * 1000. / repeat) << " ms \n"; cout << "Search runtime per element: " << ((after_ts - before_ts) * 1000000000. / repeat) / nelements << " ns \n"; cout << "Number of L2 cache misses per 1000 elements: " << (1000. * getL2CacheMisses(before_sstate, after_sstate) / repeat) / nelements << " \nL2 Cache hit ratio : " << getL2CacheHitRatio(before_sstate, after_sstate) * 100. << " %\n"; cout << "Number of L3 cache misses per 1000 elements: " << (1000. * getL3CacheMisses(before_sstate, after_sstate) / repeat) / nelements << " \nL3 Cache hit ratio : " << getL3CacheHitRatio(before_sstate, after_sstate) * 100. << " %\n"; cout << "Bytes written to memory controller per element: " << (double(getBytesWrittenToMC(before_sstate, after_sstate)) / repeat) / nelements << "\n"; cout << "Bytes read from memory controller per element : " << (double(getBytesReadFromMC(before_sstate, after_sstate)) / repeat) / nelements << "\n"; cout << "Used memory bandwidth: " << ((getBytesReadFromMC(before_sstate, after_sstate) + getBytesWrittenToMC(before_sstate, after_sstate)) / (after_ts - before_ts)) / (1024 * 1024) << " MByte/sec\n"; cout << "Instructions retired: " << getInstructionsRetired(before_sstate, after_sstate) / 1000000 << "mln\n"; cout << "CPU cycles: " << getCycles(before_sstate, after_sstate) / 1000000 << "mln\n"; cout << "Instructions per cycle: " << getCoreIPC(before_sstate, after_sstate) << "\n"; cout << flush; } #if 0 typedef int T; #else struct T { int key[1] = { 0 }; int data[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };; T() { } T(int a) { key[0] = a; } bool operator == (const T & k) const { return k.key[0] == key[0]; } }; #endif int main(int argc, char * argv[]) { PCM * m = PCM::getInstance(); if (!m->good()) { cout << "Can not access CPU counters\n"; cout << "Try to execute 'modprobe msr' as root user and then\n"; cout << "you also must have read and write permissions for /dev/cpu/?/msr devices (the 'chown' command can help)."; return -1; } if (m->program() != PCM::Success) { cout << "Program was not successful...\n"; deleteAndNullify(m); return -1; } int nelements = atoi(argv[1]); #if 1 /* use-case: compare data structures in real-time */ std::list list; std::vector vector; int i = 0; for ( ; i < nelements; ++i) { list.push_back(i); vector.push_back(i); } unsigned long long int totalops = 200000ULL * 1000ULL * 64ULL / sizeof(T); int repeat = totalops / nelements, j; cout << "\n\nElements to traverse: " << totalops << "\n"; cout << "Items in data structure: " << nelements << "\n"; cout << "Elements data size: " << sizeof(T) * nelements / 1024 << " KB\n"; cout << "Test repetitions: " << repeat << "\n"; cout << "\n*List data structure*\n"; measure(list, repeat, nelements); cout << "\n\n*Vector/array data structure*\n"; measure(vector, repeat, nelements); #else /* use-case: memory bandwidth-aware scheduling */ std::vector vector; nelements = 13000000; int i = 0; cout << "Elements data size: " << sizeof(T) * nelements / 1024 << " KB\n"; for ( ; i < nelements; ++i) { vector.push_back(i); } double before_ts, after_ts; before_ts = my_timestamp(); { int m_tasks = 1000; int c_tasks = 1000; while (m_tasks + c_tasks != 0) { if (m_tasks > 0) { Memory_intensive_task(vector); --m_tasks; continue; } if (c_tasks > 0) { CPU_intensive_task(); --c_tasks; } } } after_ts = my_timestamp(); cout << "In order scheduling, Running time: " << (after_ts - before_ts) << " seconds\n"; before_ts = my_timestamp(); { int m_tasks = 1000; int c_tasks = 1000; while (m_tasks + c_tasks != 0) { double band = currentMemoryBandwidth(); //cout << "Mem band: " << band << " MB/sec\n"; if (m_tasks > 0 && (band < (25 * 1024 /* MB/sec*/) || c_tasks == 0)) { Memory_intensive_task(vector); --m_tasks; continue; } if (c_tasks > 0) { CPU_intensive_task(); --c_tasks; continue; } } } after_ts = my_timestamp(); cout << "CPU monitoring conscoius scheduling, Running time: " << (after_ts - before_ts) << " seconds\n"; #endif m->cleanup(); return 0; } pcm-202502/src/resctrl.cpp000066400000000000000000000113361475730356400153600ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation // written by Roman Dementiev #ifdef __linux__ #include "resctrl.h" #include "cpucounters.h" #include #include #include #include #include namespace pcm { bool Resctrl::isMounted() { struct stat st; if (stat("/sys/fs/resctrl/mon_groups", &st) < 0) { return false; } return true; } void Resctrl::init() { if (isMounted() == false) { std::cerr << "ERROR: /sys/fs/resctrl is not mounted\n"; std::cerr << "ERROR: RDT metrics (L3OCC,LMB,RMB) will not be available\n"; std::cerr << "Mount it to make it work: mount -t resctrl resctrl /sys/fs/resctrl\n"; return; } const auto numCores = pcm.getNumCores(); for (unsigned int c = 0; c < numCores; ++c) { if (pcm.isCoreOnline(c)) { const auto C = std::to_string(c); const auto dir = std::string(PCMPath) + C; struct stat st; if (stat(dir.c_str(), &st) < 0 && mkdir(dir.c_str(), 0700) < 0) { std::cerr << "INFO: can't create directory " << dir << " error: " << strerror(errno) << "\n"; const auto containerDir = std::string("/pcm") + dir; if (stat(containerDir.c_str(), &st) < 0 && mkdir(containerDir.c_str(), 0700) < 0) { std::cerr << "INFO: can't create directory " << containerDir << " error: " << strerror(errno) << "\n"; std::cerr << "ERROR: RDT metrics (L3OCC,LMB,RMB) will not be available\n"; break; } } const auto cpus_listFilename = dir + "/cpus_list"; writeSysFS(cpus_listFilename.c_str(), C, false); auto generateMetricFiles = [&dir, c] (PCM & pcm, const std::string & metric, FileMapType & fileMap) { auto getMetricFilename = [] (const std::string & dir, const uint64 s, const std::string & metric) { std::ostringstream ostr; ostr << dir << "/mon_data/mon_L3_" << std::setfill('0') << std::setw(2) << s << "/" << metric; return ostr.str(); }; for (uint64 s = 0; s < pcm.getNumSockets(); ++s) { fileMap[c].push_back(getMetricFilename(dir, s, metric)); } }; if (pcm.L3CacheOccupancyMetricAvailable()) { generateMetricFiles(pcm, "llc_occupancy", L3OCC); } if (pcm.CoreLocalMemoryBWMetricAvailable()) { generateMetricFiles(pcm, "mbm_local_bytes", MBL); } if (pcm.CoreRemoteMemoryBWMetricAvailable()) { generateMetricFiles(pcm, "mbm_total_bytes", MBT); } } } } void Resctrl::cleanup() { const auto numCores = pcm.getNumCores(); for (unsigned int c = 0; c < numCores; ++c) { if (pcm.isCoreOnline(c)) { const auto dir = std::string(PCMPath) + std::to_string(c); rmdir(dir.c_str()); const auto containerDir = std::string("/pcm") + dir; rmdir(containerDir.c_str()); } } } size_t Resctrl::getMetric(const Resctrl::FileMapType & fileMap, int core) { auto files = fileMap.find(core); if (files == fileMap.end()) { return 0ULL; } size_t result = 0; for (auto& f : files->second) { const auto data = readSysFS(f.c_str(), false); if (data.empty() == false) { result += atoll(data.c_str()); } else { static std::mutex lock; std::lock_guard _(lock); std::cerr << "Error reading " << f << ". Error: " << strerror(errno) << "\n"; if (errno == 24) { std::cerr << PCM_ULIMIT_RECOMMENDATION; } } } return result; } size_t Resctrl::getL3OCC(int core) { return getMetric(L3OCC, core); } size_t Resctrl::getMBL(int core) { return getMetric(MBL, core); } size_t Resctrl::getMBT(int core) { return getMetric(MBT, core); } }; #endif // __linux__ pcm-202502/src/resctrl.h000066400000000000000000000017121475730356400150220ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation // written by Roman Dementiev #pragma once /*! \file resctrl.h \brief interface to MBM and CMT using Linux resctrl */ #ifdef __linux__ #include #include #include #include #include #include namespace pcm { class PCM; class Resctrl { PCM & pcm; typedef std::unordered_map > FileMapType; FileMapType L3OCC, MBL, MBT; Resctrl() = delete; size_t getMetric(const FileMapType & fileMap, int core); static constexpr auto PCMPath = "/sys/fs/resctrl/mon_groups/pcm"; public: Resctrl(PCM & m) : pcm(m) {} bool isMounted(); void init(); size_t getL3OCC(int core); size_t getMBL(int core); size_t getMBT(int core); void cleanup(); }; }; #endif // __linux__ pcm-202502/src/simdjson/000077500000000000000000000000001475730356400150205ustar00rootroot00000000000000pcm-202502/src/threadpool.cpp000066400000000000000000000012121475730356400160330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #include "threadpool.h" #include "utils.h" namespace pcm { void ThreadPool::execute( ThreadPool* tp ) { while( 1 ) { Work* w = tp->retrieveWork(); if ( w == nullptr ) break; w->execute(); // There can never be a double delete here, once taken from the tp it is owned by this thread // but in order to silence cppcheck w is set explicitly to null deleteAndNullify( w ); DBG( 5, "Work deleted, waiting for more work..." ); } DBG( 4, "Thread is explicitly dying now..." ); } } // namespace pcm pcm-202502/src/threadpool.h000066400000000000000000000064171475730356400155140ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2022, Intel Corporation #pragma once #include "debug.h" #include #include #include #include #include #include namespace pcm { class Work { public: Work() {} virtual ~Work() {} virtual void execute() = 0; }; template class LambdaJob : public Work { public: template LambdaJob( F&& f, Args&& ... args ) //: task_( std::forward(f)(std::forward( args )... ) ) { : task_(std::bind( f, args... ) ) { } virtual void execute() override { task_(); } std::future getFuture() { return task_.get_future(); } private: std::packaged_task task_; }; class WorkQueue; class ThreadPool { private: ThreadPool( const int n ) { for ( int i = 0; i < n; ++i ) addThread(); } ThreadPool( ThreadPool const& ) = delete; ThreadPool & operator = ( ThreadPool const& ) = delete; public: void emptyThreadPool( void ) { try { for (size_t i = 0; i < threads_.size(); ++i) addWork(nullptr); for (size_t i = 0; i < threads_.size(); ++i) threads_[i].join(); threads_.clear(); } catch (const std::exception& e) { std::cerr << "PCM Error. Exception in ThreadPool::~ThreadPool: " << e.what() << "\n"; } } ~ThreadPool() { DBG( 5, "Threadpool is being deleted..." ); emptyThreadPool(); } public: static ThreadPool& getInstance() { static ThreadPool tp_(64); return tp_; } void addWork( Work* w ) { DBG( 5, "WQ: Adding work" ); std::lock_guard lg( qMutex_ ); workQ_.push( w ); queueCV_.notify_one(); DBG( 5, "WQ: Work available" ); } Work* retrieveWork() { DBG( 5, "WQ: Retrieving work" ); std::unique_lock lock( qMutex_ ); queueCV_.wait( lock, [this]{ return !workQ_.empty(); } ); Work* w = workQ_.front(); workQ_.pop(); lock.unlock(); DBG( 5, "WQ: Work retrieved" ); return w; } private: void addThread() { threads_.push_back( std::thread( std::bind( &this->execute, this ) ) ); } // Executes work items from a std::thread, do not call manually static void execute( ThreadPool* ); private: std::vector threads_; std::queue workQ_; std::mutex qMutex_; std::condition_variable queueCV_; }; class WorkQueue { private: WorkQueue( size_t init ) : tp_( ThreadPool::getInstance() ), workProcessed_( init ) { DBG( 5, "Constructing WorkQueue..." ); } WorkQueue( WorkQueue const& ) = delete; WorkQueue & operator = ( WorkQueue const& ) = delete; public: ~WorkQueue() { DBG( 5, "Destructing WorkQueue..." ); } public: static WorkQueue* getInstance() { static WorkQueue wq_( 0 ); return &wq_; } // Just forwarding to the threadpool void addWork( Work* w ) { ++workProcessed_; tp_.addWork( w ); } private: ThreadPool& tp_; size_t workProcessed_; }; } // namespace pcm pcm-202502/src/topology.cpp000066400000000000000000000100751475730356400155550ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2016-2022, Intel Corporation #include "topology.h" #include "pcm-accel-common.h" namespace pcm { UncoreCounterState ServerUncore::uncoreCounterState( void ) const { UncoreCounterState ucs; // Fill the ucs PCM* pcm = PCM::getInstance(); pcm->readAndAggregateUncoreMCCounters( socketID(), ucs ); pcm->readAndAggregateEnergyCounters( socketID(), ucs ); pcm->readAndAggregatePackageCStateResidencies( refCore()->msrHandle(), ucs ); return ucs; } UncoreCounterState ClientUncore::uncoreCounterState( void ) const { UncoreCounterState ucs; // Fill the ucs PCM* pcm = PCM::getInstance(); pcm->readAndAggregateUncoreMCCounters( socketID(), ucs ); pcm->readAndAggregateEnergyCounters( socketID(), ucs ); pcm->readAndAggregatePackageCStateResidencies( refCore()->msrHandle(), ucs ); return ucs; } Socket::Socket( PCM* m, int32 logicalID ) : pcm_(m), refCore_(nullptr), logicalID_(logicalID) { if ( pcm_->isServerCPU() ) uncore_ = new ServerUncore( pcm_, logicalID ); else if ( pcm_->isClientCPU() ) uncore_ = new ClientUncore( pcm_, logicalID ); else throw std::runtime_error( "ERROR: Neither a client nor a server part, please fix the code!" ); } SocketCounterState Socket::socketCounterState( void ) const { SocketCounterState scs; // Fill the scs // by iterating the cores for( auto& core : cores_ ) { scs.BasicCounterState::operator += ( core->coreCounterState() ); } // and the uncore scs.UncoreCounterState::operator += ( uncore_->uncoreCounterState() ); PCM::getInstance()->readPackageThermalHeadroom( socketID(), scs ); return scs; } void Aggregator::dispatch( SystemRoot const& syp ) { // std::cerr << "Aggregator::dispatch( SystemRoot )\n"; dispatchedAt_ = std::chrono::steady_clock::now(); // CoreCounterStates are fetched asynchronously here for ( auto* socket : syp.sockets() ) socket->accept( *this ); // Dispatching offlined cores for ( auto* htp : syp.offlinedThreadsAtStart() ) htp->accept( *this ); auto ccsFuturesIter = ccsFutures_.begin(); auto ccsIter = ccsVector_.begin(); // int i; // i = 0; for ( ; ccsFuturesIter != ccsFutures_.end() && ccsIter != ccsVector_.end(); ++ccsFuturesIter, ++ccsIter ) { // std::cerr << "Works ccsFuture: " << ++i << "\n"; (*ccsIter) = (*ccsFuturesIter).get(); } // Aggregate BasicCounterStates for ( auto* socket : syp.sockets() ) { for ( auto* core : socket->cores() ) for ( auto* thread : core->threads() ) socsVector_[ socket->socketID() ] += ( ccsVector_[ thread->osID() ] ); // UncoreCounterStates have not been filled here so it is ok to add // the entire SocketCounterState here sycs_ += socsVector_[ socket->socketID() ]; } // Fetch and aggregate UncoreCounterStates auto ucsFuturesIter = ucsFutures_.begin(); auto socsIter = socsVector_.begin(); // i = 0; for ( ; ucsFuturesIter != ucsFutures_.end() && socsIter != socsVector_.end(); ++ucsFuturesIter, ++socsIter ) { // std::cerr << "Works ucsFuture: " << ++i << "\n"; // Because we already aggregated the Basic/CoreCounterStates above, sycs_ // only needs the ucs added here. If we would add socs to sycs we would // count all Basic/CoreCounterState counters double UncoreCounterState ucs = (*ucsFuturesIter).get(); sycs_ += ucs; (*socsIter) = std::move( ucs ); } PCM* pcm = PCM::getInstance(); pcm->readQPICounters( sycs_ ); pcm->readAndAggregateCXLCMCounters( sycs_ ); readAccelCounters(sycs_); } bool TopologyStringCompare( const std::string& topology1, const std::string& topology2 ) { if ( topology1.size() == 0 ) return true; if ( topology2.size() == 0 ) return false; int topo1asint, topo2asint; std::stringstream ss1(topology1); std::stringstream ss2(topology2); ss1 >> topo1asint; ss2 >> topo2asint; return topo1asint < topo2asint; } }// namespace pcm pcm-202502/src/topology.h000066400000000000000000000520561475730356400152270ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // 2016-2020, Intel Corporation #pragma once #include #include #include #include #include "types.h" #include "cpucounters.h" #include "threadpool.h" namespace pcm { // all can be done with forwards, anything hat actually uses PCM should be put in the topology.cpp file class PCM; class SystemRoot; class Socket; class Core; class HyperThread; class ServerUncore; class ClientUncore; class Visitor { public: Visitor() { // Set the floatingpoint format to fixed. Setting the number of decimal digits to 3. ss << std::fixed << std::setprecision(3); } Visitor(const Visitor &) = delete; Visitor & operator = (const Visitor &) = delete; public: virtual void dispatch( SystemRoot const & ) = 0; virtual void dispatch( Socket* ) = 0; virtual void dispatch( Core* ) = 0; virtual void dispatch( HyperThread* ) = 0; virtual void dispatch( ServerUncore* ) = 0; virtual void dispatch( ClientUncore* ) = 0; virtual ~Visitor() {}; protected: std::stringstream ss{}; }; class SystemObject { public: virtual void accept( Visitor & v ) = 0; virtual ~SystemObject() {}; }; enum Status { Offline = 0, Online = 1 }; class HyperThread : public SystemObject { public: HyperThread( PCM* m, int32 osID, TopologyEntry te, enum Status status ) : pcm_(m), osID_(osID), te_(te), status_(status) {} virtual ~HyperThread() { pcm_ = nullptr; } virtual void accept( Visitor& v ) override { v.dispatch( this ); } CoreCounterState coreCounterState() const { CoreCounterState ccs; // fill ccs ccs.BasicCounterState::readAndAggregate( msrHandle_ ); return ccs; } std::string topologyDataString() const { std::stringstream ss; ss << osID_ << "\t" << te_.socket_id << "\t" << te_.die_grp_id << "\t" << te_.die_id << "\t" << te_.tile_id << "\t" << te_.core_id << "\t" << te_.thread_id << "\t"; return ss.str(); } TopologyEntry topologyEntry() const { return te_; } void addMSRHandle( std::shared_ptr handle ) { msrHandle_ = handle; } int32 osID() const { return osID_; } int32 threadID() const { return te_.thread_id; } int32 coreID() const { return te_.core_id; } int32 moduleID() const { return te_.module_id; } int32 tileID() const { return te_.tile_id; } int32 dieID() const { return te_.die_id; } int32 dieGroupID() const { return te_.die_grp_id; } int32 socketID() const { return te_.socket_id; } int32 socketUniqueCoreID() const { return te_.socket_unique_core_id; } // We simply pass by value, this way the refcounting works best and as expected std::shared_ptr msrHandle() const { return msrHandle_; } bool isOnline() const { return (status_ == Status::Online); } private: PCM* pcm_; std::shared_ptr msrHandle_; // osID is the expected osID, offlined cores have te.os_id == -1 int32 osID_; TopologyEntry te_; enum Status status_; }; class Core : public SystemObject { public: Core( PCM* m ) : pcm_(m) { // PCM* m is not 0, we're being called from the PCM constructor // Just before this Core object is constructed, the value for // threads_per_core is determined MAX_THREADS_PER_CORE = pcm_->getThreadsPerCore(); } virtual ~Core() { pcm_ = nullptr; for ( auto& thread : threads_ ) deleteAndNullify(thread); } virtual void accept( Visitor& v ) override { v.dispatch( this ); } CoreCounterState coreCounterState() const { CoreCounterState ccs; // Fill bcs for ( HyperThread* thread : threads_ ) { ccs += thread->coreCounterState(); } return ccs; } void addHyperThreadInfo( int32 osID, TopologyEntry te ) { if ( te.thread_id >= MAX_THREADS_PER_CORE ) { std::stringstream ss; ss << "ERROR: Core: thread_id " << te.thread_id << " cannot be larger than " << MAX_THREADS_PER_CORE << ".\n"; throw std::runtime_error( ss.str() ); } if ( threads_.size() == 0 || std::find_if( threads_.begin(), threads_.end(), [osID]( HyperThread const * ht ) -> bool { return ht->osID() == osID; } ) == threads_.end() ) { // std::cerr << "Core::addHyperThreadInfo: " << te.thread_id << ", " << te.os_id << "\n"; threads_.push_back( new HyperThread( pcm_, osID, te, Status::Online ) ); } } HyperThread* hyperThread( size_t threadNo ) const { if ( threadNo >= threads_.size() ) throw std::runtime_error( "ERROR: hyperThread: threadNo larger than vector." ); return threads_[ threadNo ]; } HyperThread* findThreadByOSID( int32 osID ) { for ( HyperThread* thread : threads_ ) { if ( thread->osID() == osID ) return thread; } return nullptr; } std::vector threads() const { return threads_; } std::shared_ptr msrHandle() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a msrHandle!"); return threads_.front()->msrHandle(); } int32 coreID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a coreID!"); return threads_.front()->coreID(); } int32 moduleID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a moduleID!"); return threads_.front()->moduleID(); } int32 tileID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a tileID!"); return threads_.front()->tileID(); } int32 dieID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a tileID!"); return threads_.front()->dieID(); } int32 dieGroupID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a tileID!"); return threads_.front()->dieGroupID(); } int32 socketID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a socketID!"); return threads_.front()->socketID(); } int32 socketUniqueCoreID() const { if ( 0 == threads_.size() ) throw std::runtime_error("BUG: No threads yet but asking for a socketID!"); return threads_.front()->socketUniqueCoreID(); } bool isOnline() const { for( auto& thread : threads_ ) if ( thread->isOnline() ) return true; return false; } private: PCM* pcm_; std::vector threads_; int32 MAX_THREADS_PER_CORE; }; class Uncore : public SystemObject { public: Uncore( PCM* m, int32 socketID ) : pcm_( m ), refCore_( nullptr ), socketID_( socketID ) {} virtual ~Uncore() { pcm_ = nullptr; refCore_ = nullptr; } virtual void accept( Visitor& v ) = 0; virtual UncoreCounterState uncoreCounterState( void ) const = 0; Core* refCore() const { if ( refCore_ == nullptr ) throw std::runtime_error( "BUG: Uncore: refCore was never set!" ); return refCore_; } int32 socketID() const { return socketID_; } void setRefCore( Core* refCore ) { refCore_ = refCore; } private: PCM* pcm_; Core* refCore_; int32 socketID_; }; class ServerUncore : public Uncore { public: ServerUncore( PCM* m, int32 socketID ) : Uncore( m, socketID ) {} virtual ~ServerUncore() {} virtual void accept( Visitor& v ) override { v.dispatch( this ); } virtual UncoreCounterState uncoreCounterState( void ) const override; }; class ClientUncore : public Uncore { public: ClientUncore( PCM* m, int32 socketID ) : Uncore( m, socketID ) {} virtual ~ClientUncore() {} virtual void accept( Visitor& v ) override { v.dispatch( this ); } virtual UncoreCounterState uncoreCounterState( void ) const override; }; class Socket : public SystemObject { Socket(const Socket &) = delete; Socket & operator = (const Socket &) = delete; public: Socket( PCM* m, int32 logicalID ); virtual ~Socket() { pcm_ = nullptr; refCore_ = nullptr; // cores_ is owner, set it to null before deleting it one below for ( auto& core : cores_ ) deleteAndNullify(core); deleteAndNullify(uncore_); } virtual void accept( Visitor& v ) override { v.dispatch( this ); } void addCore( Core* c ) { cores_.push_back( c ); } HyperThread* findThreadByOSID( int32 osID ) { HyperThread* thread; for ( Core* core : cores_ ) { thread = core->findThreadByOSID(osID); if ( nullptr != thread ) return thread; } return nullptr; } void setRefCore() { if ( cores_.size() == 0 ) throw std::runtime_error("No cores added to the socket so cannot set reference core"); refCore_ = cores_.front(); // uncore_ cannot be null, it is set in the constructor uncore_->setRefCore( refCore_ ); } SocketCounterState socketCounterState( void ) const; Core* findCoreByTopologyEntry( TopologyEntry te ) { for ( auto& core : cores_ ) if ( core->hyperThread( 0 )->topologyEntry().isSameCore( te ) ) return core; return nullptr; } std::vector const & cores( void ) const { return cores_; } Uncore* uncore( void ) const { return uncore_; } int32 socketID() const { return logicalID_; } bool isOnline() const { return refCore_->isOnline(); } private: std::vector cores_; PCM* pcm_; Core* refCore_; Uncore* uncore_; int32 logicalID_; }; class SystemRoot : public SystemObject { public: SystemRoot(PCM * p) : pcm_(p) {} SystemRoot( SystemRoot const & ) = delete; // do not try to copy this please SystemRoot & operator = ( SystemRoot const & ) = delete; // do not try to copy this please virtual ~SystemRoot() { pcm_ = nullptr; for ( auto& socket : sockets_ ) deleteAndNullify(socket); for ( auto& thread : offlinedThreadsAtStart_ ) deleteAndNullify(thread); } virtual void accept( Visitor& v ) override { v.dispatch( *this ); } void addSocket( int32 logical_id ) { Socket* s = new Socket( pcm_, logical_id ); sockets_.push_back( s ); } // osID is the expected os_id, this is used in case te.os_id = -1 (offlined core) void addThread( int32 osID, TopologyEntry& te ) { // std::cerr << "SystemRoot::addThread: coreid: " << te.core_id << ", module_id: " << te.module_id << ", tile_id: " << te.tile_id << ", die_id: " << te.die_id << ", die_grp_id: " << te.die_grp_id << ", socket_id: " << te.socket_id << ", os_id: " << osID << "\n"; // quick check during development to see if expected osId == te.os_id for onlined cores // assert( te.os_id != -1 && osID == te.os_id ); bool entryAdded = false; for ( auto& socket : sockets_ ) { if ( socket->socketID() == te.socket_id ) { Core* core = nullptr; if ( (core = socket->findCoreByTopologyEntry( te )) == nullptr ) { core = new Core( pcm_ ); // std::cerr << "new Core ThreadID: " << te.thread_id << "\n"; core->addHyperThreadInfo( osID, te ); socket->addCore( core ); // std::cerr << "Added core " << te.core_id << " with os_id " << osID << ", threadid " << te.thread_id << " and tileid " << te.tile_id << " to socket " << te.socket_id << ".\n"; } else { // std::cerr << "existing Core ThreadID: " << te.thread_id << "\n"; core->addHyperThreadInfo( osID, te ); // std::cerr << "Augmented core " << te.core_id << " with osID " << osID << " and threadid " << te.thread_id << " for the hyperthread to socket " << te.socket_id << ".\n"; } entryAdded = true; break; } } if ( !entryAdded ) { // if ( te.os_id == -1 ) // std::cerr << "TE not added because os_id == -1, core is offline\n"; offlinedThreadsAtStart_.push_back( new HyperThread( pcm_, osID, te, Status::Offline ) ); } } HyperThread* findThreadByOSID( int32 osID ) { HyperThread* thread; for ( Socket* socket : sockets_ ) { thread = socket->findThreadByOSID( osID ); if ( nullptr != thread ) return thread; } for ( HyperThread* ht: offlinedThreadsAtStart_ ) if ( ht->osID() == osID ) return ht; return nullptr; } void addMSRHandleToOSThread( std::shared_ptr handle, uint32 osID ) { // std::cerr << "addMSRHandleToOSThread: osID: " << osID << "\n"; HyperThread* thread = findThreadByOSID( osID ); if ( nullptr == thread ) throw std::runtime_error( "SystemRoot::addMSRHandleToOSThread osID not found" ); thread->addMSRHandle( handle ); } SystemCounterState systemCounterState() const { SystemCounterState scs; // Fill scs // by iterating the sockets for ( auto& socket : sockets_ ) { scs += ( socket->socketCounterState() ); } return scs; } std::vector const & sockets( void ) const { return sockets_; } std::vector const & offlinedThreadsAtStart( void ) const { return offlinedThreadsAtStart_; } private: std::vector sockets_; std::vector offlinedThreadsAtStart_; PCM* pcm_; }; /* Method used here: while walking the tree and iterating the vector * elements, collect the counters. Once all elements have been walked * the vectors contain the aggregates. */ class Aggregator : Visitor { public: Aggregator() : wq_( WorkQueue::getInstance() ) { PCM* const pcm = PCM::getInstance(); // Resize user provided vectors to the right size ccsVector_.resize( pcm->getNumCores() ); socsVector_.resize( pcm->getNumSockets() ); // Internal use only, need to be the same size as the user provided vectors ccsFutures_.resize( pcm->getNumCores() ); ucsFutures_.resize( pcm->getNumSockets() ); } virtual ~Aggregator() { wq_ = nullptr; } public: virtual void dispatch( SystemRoot const& syp ) override; virtual void dispatch( Socket* sop ) override { // std::cerr << "Aggregator::dispatch( Socket )\n"; // Fetch CoreCounterStates for ( auto* core : sop->cores() ) core->accept( *this ); // Fetch UncoreCounterState async result auto job = new LambdaJob( []( Socket* s ) -> UncoreCounterState { DBG( 5, "Lambda fetching UncoreCounterState async" ); UncoreCounterState ucs; if ( !s->isOnline() ) return ucs; return s->uncore()->uncoreCounterState(); }, sop ); ucsFutures_[ sop->socketID() ] = job->getFuture(); wq_->addWork( job ); } virtual void dispatch( Core* cop ) override { // std::cerr << "Aggregator::dispatch( Core )\n"; // Loop each HyperThread for ( auto* thread : cop->threads() ) { // Fetch the CoreCounterState thread->accept( *this ); } } virtual void dispatch( HyperThread* htp ) override { // std::cerr << "Aggregator::dispatch( HyperThread )\n"; // std::cerr << "Dispatch htp with osID=" << htp->osID() << "\n"; auto job = new LambdaJob( []( HyperThread* h ) -> CoreCounterState { DBG( 5, "Lambda fetching CoreCounterState async" ); CoreCounterState ccs; if ( !h->isOnline() ) return ccs; return h->coreCounterState(); }, htp ); ccsFutures_[ htp->osID() ] = job->getFuture(); wq_->addWork( job ); } virtual void dispatch( ServerUncore* /*sup*/ ) override { // std::cerr << "Aggregator::dispatch( ServerUncore )\n"; } virtual void dispatch( ClientUncore* /*cup*/ ) override { // std::cerr << "Aggregator::dispatch( ClientUncore )\n"; } std::vectorconst & coreCounterStates( void ) const { return ccsVector_; } std::vectorconst & socketCounterStates( void ) const { return socsVector_; } SystemCounterState const & systemCounterState( void ) const { return sycs_; } std::chrono::steady_clock::time_point dispatchedAt( void ) const { return dispatchedAt_; } private: WorkQueue* wq_; std::vector ccsVector_; std::vector socsVector_; SystemCounterState sycs_; std::vector> ccsFutures_; std::vector> ucsFutures_; std::chrono::steady_clock::time_point dispatchedAt_{}; }; /* Method used here: while walking the cores in the tree and iterating the * vector elements, print the core related ids into a large string. Once all * cores have been walked the vector of strings contains all ids. */ class TopologyPrinter : Visitor { public: TopologyPrinter() : wq_( WorkQueue::getInstance() ) { PCM* const pcm = PCM::getInstance(); // Resize user provided vectors to the right size threadIDsVector_.resize( pcm->getNumCores() ); // Internal use only, need to be the same size as the user provided vectors threadIDsFutures_.resize( pcm->getNumCores() ); } virtual ~TopologyPrinter() { wq_ = nullptr; } public: virtual void dispatch( SystemRoot const& syp ) override { // std::cerr << "TopologyPrinter::dispatch( SystemRoot )\n"; for ( auto* socket : syp.sockets() ) socket->accept( *this ); auto tidFuturesIter = threadIDsFutures_.begin(); auto tidIter = threadIDsVector_.begin(); // int i; // i = 0; for ( ; tidFuturesIter != threadIDsFutures_.end() && tidIter != threadIDsVector_.end(); ++tidFuturesIter, ++tidIter ) { // std::cerr << "Works tidFuture: " << ++i << "\n"; (*tidIter) = (*tidFuturesIter).get(); } } virtual void dispatch( Socket* sop ) override { // std::cerr << "TopologyPrinter::dispatch( Socket )\n"; // Fetch Topology Data for ( auto* core : sop->cores() ) core->accept( *this ); } virtual void dispatch( Core* cop ) override { // std::cerr << "TopologyPrinter::dispatch( Core )\n"; // Loop each HyperThread for ( auto* thread : cop->threads() ) { // Fetch the Topology Data thread->accept( *this ); } } virtual void dispatch( HyperThread* htp ) override { // std::cerr << "TopologyPrinter::dispatch( HyperThread )\n"; // std::cerr << "Dispatch htp with osID=" << htp->osID() << "\n"; auto job = new LambdaJob( []( HyperThread* h ) -> std::string { DBG( 5, "Lambda fetching Topology Data async" ); std::string s; if ( !h->isOnline() ) return s; return h->topologyDataString(); }, htp ); threadIDsFutures_[ htp->osID() ] = job->getFuture(); wq_->addWork( job ); } virtual void dispatch( ServerUncore* /*sup*/ ) override { // std::cerr << "TopologyPrinter::dispatch( ServerUncore )\n"; } virtual void dispatch( ClientUncore* /*cup*/ ) override { // std::cerr << "TopologyPrinter::dispatch( ClientUncore )\n"; } std::vector & topologyDataStrings( void ) { return threadIDsVector_; } std::chrono::steady_clock::time_point dispatchedAt( void ) const { return dispatchedAt_; } private: WorkQueue* wq_; std::vector threadIDsVector_; std::vector> threadIDsFutures_; std::chrono::steady_clock::time_point dispatchedAt_{}; }; bool TopologyStringCompare( const std::string& topology1, const std::string& topology2 ); } // namespace pcm pcm-202502/src/topologyentry.h000066400000000000000000000141421475730356400163030ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2022-, Intel Corporation #pragma once #include "types.h" namespace pcm { struct PCM_API TopologyEntry // describes a core { /* * core_id is the hw specific physical core id that was introduced in newer generations, on * older generations this is the same as socket_unique_core_id. * socket_unique_core_id is like the name says a socket unique core id that is built out of the core_id, module_id, thread_id, die_id and die_grp_id. * With it, we have unique core ids inside a socket for keeping backward compatibility in the prometheus and json output. */ int32 os_id; int32 thread_id; int32 core_id; int32 module_id; int32 tile_id; // tile is a constellation of 1 or more cores sharing same L2 cache. Unique for entire system int32 die_id; int32 die_grp_id; int32 socket_id; int32 socket_unique_core_id; int32 native_cpu_model = -1; enum DomainTypeID { InvalidDomainTypeID = 0, LogicalProcessorDomain = 1, CoreDomain = 2, ModuleDomain = 3, TileDomain = 4, DieDomain = 5, DieGrpDomain = 6, SocketPackageDomain = 0xffff }; enum CoreType { Atom = 0x20, Core = 0x40, Invalid = -1 }; CoreType core_type = Invalid; TopologyEntry() : os_id(-1), thread_id (-1), core_id(-1), module_id(-1), tile_id(-1), die_id(-1), die_grp_id(-1), socket_id(-1), socket_unique_core_id(-1) { } const char* getCoreTypeStr() { switch (core_type) { case Atom: return "Atom"; case Core: return "Core"; case Invalid: return "invalid"; } return "unknown"; } static const char* getDomainTypeStr(const DomainTypeID & id) { switch (id) { case InvalidDomainTypeID: return "invalid"; case LogicalProcessorDomain: return "LogicalProcessor"; case CoreDomain: return "Core"; case ModuleDomain: return "Module"; case TileDomain: return "Tile"; case DieDomain: return "Die"; case DieGrpDomain: return "DieGroup"; case SocketPackageDomain: return "Socket/Package"; } return "unknown"; } bool isSameSocket( TopologyEntry& te ) { return this->socket_id == te.socket_id; } bool isSameDieGroup( TopologyEntry& te ) { return this->die_grp_id == te.die_grp_id && isSameSocket(te); } bool isSameDie( TopologyEntry& te ) { return this->die_id == te.die_id && isSameDieGroup(te); } bool isSameTile( TopologyEntry& te ) { return this->tile_id == te.tile_id && isSameDie(te); } bool isSameModule( TopologyEntry& te ) { return this->module_id == te.module_id && isSameTile (te); } bool isSameCore( TopologyEntry& te ) { return this->core_id == te.core_id && isSameModule(te); } }; inline void fillEntry(TopologyEntry & entry, const uint32 & smtMaskWidth, const uint32 & coreMaskWidth, const uint32 & l2CacheMaskShift, const int apic_id) { entry.thread_id = smtMaskWidth ? extract_bits_32(apic_id, 0, smtMaskWidth - 1) : 0; entry.core_id = (smtMaskWidth + coreMaskWidth) ? extract_bits_32(apic_id, smtMaskWidth, smtMaskWidth + coreMaskWidth - 1) : 0; entry.socket_id = extract_bits_32(apic_id, smtMaskWidth + coreMaskWidth, 31); entry.tile_id = extract_bits_32(apic_id, l2CacheMaskShift, 31); entry.socket_unique_core_id = entry.core_id; } inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 & l2CacheMaskShift) { // init constants for CPU topology leaf 0xB // adapted from Topology Enumeration Reference code for Intel 64 Architecture // https://software.intel.com/en-us/articles/intel-64-architecture-processor-topology-enumeration int wasCoreReported = 0, wasThreadReported = 0; PCM_CPUID_INFO cpuid_args; if (true) { uint32 corePlusSMTMaskWidth = 0; int subleaf = 0, levelType, levelShift; do { pcm_cpuid(0xb, subleaf, cpuid_args); if (cpuid_args.array[1] == 0) { // if EBX ==0 then this subleaf is not valid, we can exit the loop break; } levelType = extract_bits_32(cpuid_args.array[2], 8, 15); levelShift = extract_bits_32(cpuid_args.array[0], 0, 4); switch (levelType) { case 1: //level type is SMT, so levelShift is the SMT_Mask_Width smtMaskWidth = levelShift; wasThreadReported = 1; break; case 2: //level type is Core, so levelShift is the CorePlusSMT_Mask_Width corePlusSMTMaskWidth = levelShift; wasCoreReported = 1; break; default: break; } subleaf++; } while (1); if (wasThreadReported && wasCoreReported) { coreMaskWidth = corePlusSMTMaskWidth - smtMaskWidth; } else if (!wasCoreReported && wasThreadReported) { coreMaskWidth = smtMaskWidth; } else { return false; } (void) coreMaskWidth; // to suppress warnings on MacOS (unused vars) #ifdef PCM_DEBUG_TOPOLOGY uint32 threadsSharingL2; #endif uint32 l2CacheMaskWidth; pcm_cpuid(0x4, 2, cpuid_args); // get ID for L2 cache l2CacheMaskWidth = 1 + extract_bits_32(cpuid_args.array[0],14,25); // number of APIC IDs sharing L2 cache #ifdef PCM_DEBUG_TOPOLOGY threadsSharingL2 = l2CacheMaskWidth; #endif for( ; l2CacheMaskWidth > 1; l2CacheMaskWidth >>= 1) { l2CacheMaskShift++; } #ifdef PCM_DEBUG_TOPOLOGY std::cerr << "DEBUG: Number of threads sharing L2 cache = " << threadsSharingL2 << " [the most significant bit = " << l2CacheMaskShift << "]\n"; #endif } return true; } } pcm-202502/src/tpmi.cpp000066400000000000000000000332461475730356400146570ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2023, Intel Corporation // written by Roman Dementiev // #include "tpmi.h" #include "pci.h" #include "utils.h" #include #include #include #ifdef __linux__ #include #endif namespace pcm { constexpr uint32 TPMIInvalidValue = ~0U; bool TPMIverbose = false; class PFSInstances { public: // [TPMI ID][entry] -> base address typedef std::unordered_map > PFSMapType; // [PFS instance][TPMI ID][entry] -> base address typedef std::vector PFSInstancesType; private: static std::shared_ptr PFSInstancesSingleton; public: static PFSInstancesType & get() { if (PFSInstancesSingleton.get()) { return *PFSInstancesSingleton.get(); } // PFSInstancesSingleton not initialized, let us initialize it auto PFSInstancesSingletonInit = std::make_shared(); processDVSEC([](const VSEC & vsec) { return vsec.fields.cap_id == 0xb // Vendor Specific DVSEC && vsec.fields.vsec_id == 0x42; // TPMI PM_Features }, [&](const uint64 bar, const VSEC & vsec) { struct PFS { uint64 TPMI_ID:8; uint64 NumEntries:8; uint64 EntrySize:16; uint64 CapOffset:16; uint64 Attribute:2; uint64 Reserved:14; }; static_assert(sizeof(PFS) == sizeof(uint64), "sizeof(PFS) != sizeof(uint64)"); assert(vsec.fields.EntrySize == 2); std::vector pfsArray(vsec.fields.NumEntries); try { mmio_memcpy(&(pfsArray[0]), bar + vsec.fields.Address, vsec.fields.NumEntries * sizeof(PFS), true, true); } catch (std::runtime_error & e) { std::cerr << "Can't read PFS\n"; std::cerr << e.what(); } PFSInstancesSingletonInit->push_back(PFSMapType()); for (const auto & pfs : pfsArray) { if (TPMIverbose) { std::cout << "PFS" << "\t TPMI_ID: " << pfs.TPMI_ID << "\t NumEntries: " << pfs.NumEntries << "\t EntrySize: " << pfs.EntrySize << "\t CapOffset: " << pfs.CapOffset << "\t Attribute: " << pfs.Attribute << "\n"; } for (uint64 p = 0; p < pfs.NumEntries; ++p) { uint32 reg0 = 0; const auto addr = bar + vsec.fields.Address + pfs.CapOffset * 1024ULL + p * pfs.EntrySize * sizeof(uint32); try { mmio_memcpy(®0, addr, sizeof(uint32), false, true); } catch (std::runtime_error & e) { if (TPMIverbose) { std::cout << "can't read entry " << p << "\n"; std::cout << e.what(); } PFSInstancesSingletonInit->back()[pfs.TPMI_ID].push_back(addr); continue; } if (reg0 == TPMIInvalidValue) { if (TPMIverbose) { std::cout << "invalid entry " << p << "\n"; } } else { if (TPMIverbose) { std::cout << "Entry "<< p << std::hex; for (uint64 i_offset = 0; i_offset < pfs.EntrySize * sizeof(uint32); i_offset += sizeof(uint64)) { uint64 reg = 0; mmio_memcpy(®, addr + i_offset, sizeof(uint64), false); std::cout << " register "<< i_offset << " = " << reg; } std::cout << std::dec << "\n"; } PFSInstancesSingletonInit->back()[pfs.TPMI_ID].push_back(addr); } } } }); PFSInstancesSingleton = PFSInstancesSingletonInit; return *PFSInstancesSingleton.get(); } }; std::shared_ptr PFSInstances::PFSInstancesSingleton; class TPMIHandleMMIO : public TPMIHandleInterface { TPMIHandleMMIO(const TPMIHandleMMIO&) = delete; TPMIHandleMMIO& operator = (const TPMIHandleMMIO&) = delete; struct Entry { std::shared_ptr range; size_t offset; }; std::vector entries; public: static size_t getNumInstances(); static void setVerbose(const bool); TPMIHandleMMIO(const size_t instance_, const size_t ID_, const size_t offset_, const bool readonly_ = true); size_t getNumEntries() const override { return entries.size(); } uint64 read64(size_t entryPos) override; void write64(size_t entryPos, uint64 val) override; }; size_t TPMIHandleMMIO::getNumInstances() { return PFSInstances::get().size(); } void TPMIHandle::setVerbose(const bool v) { TPMIverbose = v; } TPMIHandleMMIO::TPMIHandleMMIO(const size_t instance_, const size_t ID_, const size_t requestedRelativeOffset, const bool readonly_) { auto & pfsInstances = PFSInstances::get(); assert(instance_ < pfsInstances.size()); for (const auto & addr: pfsInstances[instance_][ID_]) { const auto requestedAddr = addr + requestedRelativeOffset; const auto baseAddr = roundDownTo4K(requestedAddr); const auto baseOffset = requestedAddr - baseAddr; Entry e; e.range = std::make_shared(baseAddr, 4096ULL, readonly_); e.offset = baseOffset; entries.push_back(e); } } uint64 TPMIHandleMMIO::read64(size_t entryPos) { assert(entryPos < entries.size()); return entries[entryPos].range->read64(entries[entryPos].offset); } void TPMIHandleMMIO::write64(size_t entryPos, uint64 val) { assert(entryPos < entries.size()); entries[entryPos].range->write64(entries[entryPos].offset, val); } #ifdef __linux__ class TPMIHandleDriver : public TPMIHandleInterface { TPMIHandleDriver(const TPMIHandleDriver&) = delete; TPMIHandleDriver& operator = (const TPMIHandleDriver&) = delete; static std::vector instancePaths; typedef std::unordered_map TPMI_IDPathMap; static std::vector AllIDPaths; static int available; static bool isAvailable(); const size_t instance; const size_t ID; const size_t offset; // const bool readonly; // not used size_t nentries; struct TPMIEntry { unsigned int offset{0}; std::vector data; }; size_t findValidIndex(const std::vector & entries, const size_t & entryPos) { size_t validIndex = 0; for (size_t i = 0; i < entries.size(); ++i) { if (entries[i].data.empty() || entries[i].data[0] == TPMIInvalidValue) { // invalid, skip it continue; } if (validIndex == entryPos) { // found the right instance return i; } ++validIndex; } assert(0 && "TPMIHandleDriver: entryPos not found"); return 0; } std::vector readTPMIFile(std::string filePath) { filePath += "/mem_dump"; std::vector entries; std::ifstream file(filePath); std::string line; if (!file.is_open()) { std::cerr << "Error opening file: " << filePath << std::endl; return entries; } TPMIEntry currentEntry; while (getline(file, line)) { if (line.find("TPMI Instance:") != std::string::npos) { // If we have a previous instance, push it back to the vector if (!currentEntry.data.empty()) { entries.push_back(currentEntry); currentEntry.data.clear(); } std::istringstream iss(line); std::string temp; iss >> temp >> temp >> temp; // Skip "TPMI Instance:" iss >> temp; // Skip entry number iss >> temp >> std::hex >> currentEntry.offset; // Read offset } else { std::istringstream iss(line); std::string address; iss >> address; // Skip the address part uint32_t value; while (iss >> std::hex >> value) { currentEntry.data.push_back(value); } } } // Push the last instance if it exists if (!currentEntry.data.empty()) { entries.push_back(currentEntry); } return entries; } public: static size_t getNumInstances(); TPMIHandleDriver(const size_t instance_, const size_t ID_, const size_t offset_, const bool /* readonly_ */ = true) : instance(instance_), ID(ID_), offset(offset_), // readonly(readonly_), // not used nentries(0) { assert(available > 0); assert(instance < getNumInstances()); const auto entries = readTPMIFile(AllIDPaths[instance][ID]); for (auto & e: entries) { if (e.data.empty() == false && e.data[0] != TPMIInvalidValue) { // count valid entries ++nentries; } } } size_t getNumEntries() const override { assert(available > 0); return nentries; } uint64 read64(size_t entryPos) override { assert(available > 0); assert(instance < getNumInstances()); const auto entries = readTPMIFile(AllIDPaths[instance][ID]); size_t i = findValidIndex(entries, entryPos); cvt_ds result; const auto i4 = offset / 4; assert(i4 + 1 < entries[i].data.size()); result.ui32.low = entries[i].data[i4]; result.ui32.high = entries[i].data[i4 + 1]; return result.ui64; } void write64(size_t entryPos, uint64 val) override { assert(available > 0); assert(instance < getNumInstances()); const auto entries = readTPMIFile(AllIDPaths[instance][ID]); size_t i = findValidIndex(entries, entryPos); cvt_ds out; out.ui64 = val; const auto path = AllIDPaths[instance][ID] + "/mem_write"; writeSysFS(path.c_str(), std::to_string(i) + "," + std::to_string(offset) + "," + std::to_string(out.ui32.low)); writeSysFS(path.c_str(), std::to_string(i) + "," + std::to_string(offset + 4) + "," + std::to_string(out.ui32.high)); } }; int TPMIHandleDriver::available = -1; std::vector TPMIHandleDriver::instancePaths; std::vector TPMIHandleDriver::AllIDPaths; bool TPMIHandleDriver::isAvailable() { if (available < 0) // not initialized yet { instancePaths = findPathsFromPattern("/sys/kernel/debug/tpmi-*"); std::sort(instancePaths.begin(), instancePaths.end()); for (size_t i = 0; i < instancePaths.size(); ++i) { // std::cout << instancePaths[i] << std::endl; std::string prefix = instancePaths[i] + "/tpmi-id-"; std::vector IDPaths = findPathsFromPattern((prefix + "*").c_str()); TPMI_IDPathMap idMap; for (auto & p : IDPaths) { const auto id = read_number((std::string("0x") + p.substr(prefix.size())).c_str()); // std::cout << p << " -> " << id << std::endl; idMap[id] = p; std::ifstream mem_dump((p + "/mem_dump").c_str()); std::ifstream mem_write((p + "/mem_write").c_str()); if (mem_dump.good() && mem_write.good()) { available = 1; } } AllIDPaths.push_back(idMap); } if (available < 0) { available = 0; } if (safe_getenv("PCM_NO_TPMI_DRIVER") == std::string("1")) { available = 0; } } return available > 0; } size_t TPMIHandleDriver::getNumInstances() { // std::cout << "isAvailable: " << isAvailable() << std::endl; if (isAvailable()) { return AllIDPaths.size(); } return 0; } #endif size_t TPMIHandle::getNumInstances() { #ifdef __linux__ const auto tpmiNInstances = TPMIHandleDriver::getNumInstances(); if (tpmiNInstances) { return tpmiNInstances; } #endif return TPMIHandleMMIO::getNumInstances(); } TPMIHandle::TPMIHandle(const size_t instance_, const size_t ID_, const size_t requestedRelativeOffset, const bool readonly_) { #ifdef __linux__ const auto tpmiNInstances = TPMIHandleDriver::getNumInstances(); if (tpmiNInstances) { impl = std::make_shared(instance_, ID_, requestedRelativeOffset, readonly_); return; } #endif impl = std::make_shared(instance_, ID_, requestedRelativeOffset, readonly_); } size_t TPMIHandle::getNumEntries() const { assert(impl.get());; return impl->getNumEntries(); } uint64 TPMIHandle::read64(size_t entryPos) { assert(impl.get()); return impl->read64(entryPos); } void TPMIHandle::write64(size_t entryPos, uint64 val) { assert(impl.get()); impl->write64(entryPos, val); } } // namespace pcm pcm-202502/src/tpmi.h000066400000000000000000000020351475730356400143140ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2023-2024, Intel Corporation // written by Roman Dementiev // #pragma once /*! \file tpmi.h \brief Interface to access TPMI registers */ #include "mmio.h" #include namespace pcm { class TPMIHandleInterface { public: virtual size_t getNumEntries() const = 0; virtual uint64 read64(size_t entryPos) = 0; virtual void write64(size_t entryPos, uint64 val) = 0; virtual ~TPMIHandleInterface() {} }; class TPMIHandle : public TPMIHandleInterface { TPMIHandle(const TPMIHandle&) = delete; TPMIHandle& operator = (const TPMIHandle&) = delete; std::shared_ptr impl; public: static size_t getNumInstances(); static void setVerbose(const bool); TPMIHandle(const size_t instance_, const size_t ID_, const size_t offset_, const bool readonly_ = true); size_t getNumEntries() const override; uint64 read64(size_t entryPos) override; void write64(size_t entryPos, uint64 val) override; }; } // namespace pcm pcm-202502/src/types.h000066400000000000000000001703451475730356400145210ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2024, Intel Corporation // written by Roman Dementiev // #ifndef CPUCounters_TYPES_H #define CPUCounters_TYPES_H /*! \file types.h \brief Internal type and constant definitions */ #undef PCM_DEBUG #ifndef KERNEL #include #include #include #include #include #include #include #ifdef _MSC_VER #include #include #endif #endif // #ifndef KERNEL namespace pcm { typedef unsigned long long uint64; typedef signed long long int64; typedef unsigned int uint32; typedef signed int int32; #define PCM_ULIMIT_RECOMMENDATION ("try executing 'ulimit -n 1000000' to increase the limit on the number of open files.\n") /* MSR addresses from "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3B: System Programming Guide, Part 2", Appendix A "PERFORMANCE-MONITORING EVENTS" */ constexpr auto INST_RETIRED_ADDR = 0x309; constexpr auto CPU_CLK_UNHALTED_THREAD_ADDR = 0x30A; constexpr auto CPU_CLK_UNHALTED_REF_ADDR = 0x30B; constexpr auto TOPDOWN_SLOTS_ADDR = 0x30C; constexpr auto PERF_METRICS_ADDR = 0x329; constexpr auto IA32_CR_PERF_GLOBAL_CTRL = 0x38F; constexpr auto IA32_CR_FIXED_CTR_CTRL = 0x38D; constexpr auto IA32_PERFEVTSEL0_ADDR = 0x186; constexpr auto IA32_PERFEVTSEL1_ADDR = IA32_PERFEVTSEL0_ADDR + 1; constexpr auto IA32_PERFEVTSEL2_ADDR = IA32_PERFEVTSEL0_ADDR + 2; constexpr auto IA32_PERFEVTSEL3_ADDR = IA32_PERFEVTSEL0_ADDR + 3; constexpr auto IA32_PERF_GLOBAL_STATUS = 0x38E; constexpr auto IA32_PERF_GLOBAL_OVF_CTRL = 0x390; constexpr auto IA32_PEBS_ENABLE_ADDR = 0x3F1; constexpr auto PERF_MAX_FIXED_COUNTERS = 3; constexpr auto PERF_MAX_CUSTOM_COUNTERS = 8; constexpr auto PERF_TOPDOWN_COUNTERS_L1 = 5; constexpr auto PERF_TOPDOWN_COUNTERS = PERF_TOPDOWN_COUNTERS_L1 + 4; constexpr auto PERF_MAX_COUNTERS = PERF_MAX_FIXED_COUNTERS + PERF_MAX_CUSTOM_COUNTERS + PERF_TOPDOWN_COUNTERS; constexpr auto IA32_DEBUGCTL = 0x1D9; constexpr auto IA32_PMC0 = 0xC1; constexpr auto IA32_PMC1 = IA32_PMC0 + 1; constexpr auto IA32_PMC2 = IA32_PMC0 + 2; constexpr auto IA32_PMC3 = IA32_PMC0 + 3; constexpr auto MSR_OFFCORE_RSP0 = 0x1A6; constexpr auto MSR_OFFCORE_RSP1 = 0x1A7; constexpr auto MSR_LOAD_LATENCY = 0x3F6; constexpr auto MSR_FRONTEND = 0x3F7; /* From Table B-5. of the above mentioned document */ constexpr auto PLATFORM_INFO_ADDR = 0xCE; constexpr auto IA32_TIME_STAMP_COUNTER = 0x10; // Event IDs // Nehalem/Westmere on-core events constexpr auto MEM_LOAD_RETIRED_L3_MISS_EVTNR = 0xCB; constexpr auto MEM_LOAD_RETIRED_L3_MISS_UMASK = 0x10; constexpr auto MEM_LOAD_RETIRED_L3_UNSHAREDHIT_EVTNR = 0xCB; constexpr auto MEM_LOAD_RETIRED_L3_UNSHAREDHIT_UMASK = 0x04; constexpr auto MEM_LOAD_RETIRED_L2_HITM_EVTNR = 0xCB; constexpr auto MEM_LOAD_RETIRED_L2_HITM_UMASK = 0x08; constexpr auto MEM_LOAD_RETIRED_L2_HIT_EVTNR = 0xCB; constexpr auto MEM_LOAD_RETIRED_L2_HIT_UMASK = 0x02; // Sandy Bridge on-core events constexpr auto MEM_LOAD_UOPS_MISC_RETIRED_LLC_MISS_EVTNR = 0xD4; constexpr auto MEM_LOAD_UOPS_MISC_RETIRED_LLC_MISS_UMASK = 0x02; constexpr auto MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_NONE_EVTNR = 0xD2; constexpr auto MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_NONE_UMASK = 0x08; constexpr auto MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_HITM_EVTNR = 0xD2; constexpr auto MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_HITM_UMASK = 0x04; constexpr auto MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_EVTNR = 0xD2; constexpr auto MEM_LOAD_UOPS_LLC_HIT_RETIRED_XSNP_UMASK = 0x07; constexpr auto MEM_LOAD_UOPS_RETIRED_L2_HIT_EVTNR = 0xD1; constexpr auto MEM_LOAD_UOPS_RETIRED_L2_HIT_UMASK = 0x02; // Haswell on-core events constexpr auto HSX_L2_RQSTS_MISS_EVTNR = 0x24; constexpr auto HSX_L2_RQSTS_MISS_UMASK = 0x3f; constexpr auto HSX_L2_RQSTS_REFERENCES_EVTNR = 0x24; constexpr auto HSX_L2_RQSTS_REFERENCES_UMASK = 0xff; // Skylake on-core events #define SKL_MEM_LOAD_RETIRED_L3_MISS_EVTNR (0xD1) #define SKL_MEM_LOAD_RETIRED_L3_MISS_UMASK (0x20) #define SKL_MEM_LOAD_RETIRED_L3_HIT_EVTNR (0xD1) #define SKL_MEM_LOAD_RETIRED_L3_HIT_UMASK (0x04) #define SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR (0xD1) #define SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK (0x10) #define SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR (0xD1) #define SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK (0x02) // Crestmont on-core events constexpr auto CMT_MEM_LOAD_RETIRED_L2_MISS_EVTNR = 0xD1; constexpr auto CMT_MEM_LOAD_RETIRED_L2_MISS_UMASK = 0x80; constexpr auto CMT_MEM_LOAD_RETIRED_L2_HIT_EVTNR = 0xD1; constexpr auto CMT_MEM_LOAD_RETIRED_L2_HIT_UMASK = 0x02; // architectural on-core events constexpr auto ARCH_LLC_REFERENCE_EVTNR = 0x2E; constexpr auto ARCH_LLC_REFERENCE_UMASK = 0x4F; constexpr auto ARCH_LLC_MISS_EVTNR = 0x2E; constexpr auto ARCH_LLC_MISS_UMASK = 0x41; // Atom on-core events constexpr auto ATOM_MEM_LOAD_RETIRED_L2_HIT_EVTNR = 0xCB; constexpr auto ATOM_MEM_LOAD_RETIRED_L2_HIT_UMASK = 0x01; constexpr auto ATOM_MEM_LOAD_RETIRED_L2_MISS_EVTNR = 0xCB; constexpr auto ATOM_MEM_LOAD_RETIRED_L2_MISS_UMASK = 0x02; // Offcore response events constexpr auto OFFCORE_RESPONSE_0_EVTNR = 0xB7; constexpr auto OFFCORE_RESPONSE_1_EVTNR = 0xBB; constexpr auto GLC_OFFCORE_RESPONSE_0_EVTNR = 0x2A; constexpr auto GLC_OFFCORE_RESPONSE_1_EVTNR = 0x2B; constexpr auto OFFCORE_RESPONSE_0_UMASK = 1; constexpr auto OFFCORE_RESPONSE_1_UMASK = 1; constexpr auto LOAD_LATENCY_EVTNR = 0xcd; constexpr auto LOAD_LATENCY_UMASK = 0x01; constexpr auto FRONTEND_EVTNR = 0xC6; constexpr auto FRONTEND_UMASK = 0x01; /* For Nehalem(-EP) processors from Intel(r) 64 and IA-32 Architectures Software Developer's Manual */ // Uncore msrs constexpr auto MSR_UNCORE_PERF_GLOBAL_CTRL_ADDR = 0x391; constexpr auto MSR_UNCORE_PERFEVTSEL0_ADDR = 0x3C0; constexpr auto MSR_UNCORE_PERFEVTSEL1_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 1; constexpr auto MSR_UNCORE_PERFEVTSEL2_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 2; constexpr auto MSR_UNCORE_PERFEVTSEL3_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 3; constexpr auto MSR_UNCORE_PERFEVTSEL4_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 4; constexpr auto MSR_UNCORE_PERFEVTSEL5_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 5; constexpr auto MSR_UNCORE_PERFEVTSEL6_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 6; constexpr auto MSR_UNCORE_PERFEVTSEL7_ADDR = MSR_UNCORE_PERFEVTSEL0_ADDR + 7; constexpr auto MSR_UNCORE_PMC0 = 0x3B0; constexpr auto MSR_UNCORE_PMC1 = MSR_UNCORE_PMC0 + 1; constexpr auto MSR_UNCORE_PMC2 = MSR_UNCORE_PMC0 + 2; constexpr auto MSR_UNCORE_PMC3 = MSR_UNCORE_PMC0 + 3; constexpr auto MSR_UNCORE_PMC4 = MSR_UNCORE_PMC0 + 4; constexpr auto MSR_UNCORE_PMC5 = MSR_UNCORE_PMC0 + 5; constexpr auto MSR_UNCORE_PMC6 = MSR_UNCORE_PMC0 + 6; constexpr auto MSR_UNCORE_PMC7 = MSR_UNCORE_PMC0 + 7; // Uncore event IDs constexpr auto UNC_QMC_WRITES_FULL_ANY_EVTNR = 0x2F; constexpr auto UNC_QMC_WRITES_FULL_ANY_UMASK = 0x07; constexpr auto UNC_QMC_NORMAL_READS_ANY_EVTNR = 0x2C; constexpr auto UNC_QMC_NORMAL_READS_ANY_UMASK = 0x07; constexpr auto UNC_QHL_REQUESTS_EVTNR = 0x20; constexpr auto UNC_QHL_REQUESTS_IOH_READS_UMASK = 0x01; constexpr auto UNC_QHL_REQUESTS_IOH_WRITES_UMASK = 0x02; constexpr auto UNC_QHL_REQUESTS_REMOTE_READS_UMASK = 0x04; constexpr auto UNC_QHL_REQUESTS_REMOTE_WRITES_UMASK = 0x08; constexpr auto UNC_QHL_REQUESTS_LOCAL_READS_UMASK = 0x10; constexpr auto UNC_QHL_REQUESTS_LOCAL_WRITES_UMASK = 0x20; /* From "Intel(r) Xeon(r) Processor 7500 Series Uncore Programming Guide" */ // Beckton uncore event IDs constexpr auto U_MSR_PMON_GLOBAL_CTL = 0x0C00; constexpr auto MB0_MSR_PERF_GLOBAL_CTL = 0x0CA0; constexpr auto MB0_MSR_PMU_CNT_0 = 0x0CB1; constexpr auto MB0_MSR_PMU_CNT_CTL_0 = 0x0CB0; constexpr auto MB0_MSR_PMU_CNT_1 = 0x0CB3; constexpr auto MB0_MSR_PMU_CNT_CTL_1 = 0x0CB2; constexpr auto MB0_MSR_PMU_ZDP_CTL_FVC = 0x0CAB; constexpr auto MB1_MSR_PERF_GLOBAL_CTL = 0x0CE0; constexpr auto MB1_MSR_PMU_CNT_0 = 0x0CF1; constexpr auto MB1_MSR_PMU_CNT_CTL_0 = 0x0CF0; constexpr auto MB1_MSR_PMU_CNT_1 = 0x0CF3; constexpr auto MB1_MSR_PMU_CNT_CTL_1 = 0x0CF2; constexpr auto MB1_MSR_PMU_ZDP_CTL_FVC = 0x0CEB; constexpr auto BB0_MSR_PERF_GLOBAL_CTL = 0x0C20; constexpr auto BB0_MSR_PERF_CNT_1 = 0x0C33; constexpr auto BB0_MSR_PERF_CNT_CTL_1 = 0x0C32; constexpr auto BB1_MSR_PERF_GLOBAL_CTL = 0x0C60; constexpr auto BB1_MSR_PERF_CNT_1 = 0x0C73; constexpr auto BB1_MSR_PERF_CNT_CTL_1 = 0x0C72; constexpr auto R_MSR_PMON_CTL0 = 0x0E10; constexpr auto R_MSR_PMON_CTR0 = 0x0E11; constexpr auto R_MSR_PMON_CTL1 = 0x0E12; constexpr auto R_MSR_PMON_CTR1 = 0x0E13; constexpr auto R_MSR_PMON_CTL2 = 0x0E14; constexpr auto R_MSR_PMON_CTR2 = 0x0E15; constexpr auto R_MSR_PMON_CTL3 = 0x0E16; constexpr auto R_MSR_PMON_CTR3 = 0x0E17; constexpr auto R_MSR_PMON_CTL4 = 0x0E18; constexpr auto R_MSR_PMON_CTR4 = 0x0E19; constexpr auto R_MSR_PMON_CTL5 = 0x0E1A; constexpr auto R_MSR_PMON_CTR5 = 0x0E1B; constexpr auto R_MSR_PMON_CTL6 = 0x0E1C; constexpr auto R_MSR_PMON_CTR6 = 0x0E1D; constexpr auto R_MSR_PMON_CTL7 = 0x0E1E; constexpr auto R_MSR_PMON_CTR7 = 0x0E1F; constexpr auto R_MSR_PMON_CTL8 = 0x0E30; constexpr auto R_MSR_PMON_CTR8 = 0x0E31; constexpr auto R_MSR_PMON_CTL9 = 0x0E32; constexpr auto R_MSR_PMON_CTR9 = 0x0E33; constexpr auto R_MSR_PMON_CTL10 = 0x0E34; constexpr auto R_MSR_PMON_CTR10 = 0x0E35; constexpr auto R_MSR_PMON_CTL11 = 0x0E36; constexpr auto R_MSR_PMON_CTR11 = 0x0E37; constexpr auto R_MSR_PMON_CTL12 = 0x0E38; constexpr auto R_MSR_PMON_CTR12 = 0x0E39; constexpr auto R_MSR_PMON_CTL13 = 0x0E3A; constexpr auto R_MSR_PMON_CTR13 = 0x0E3B; constexpr auto R_MSR_PMON_CTL14 = 0x0E3C; constexpr auto R_MSR_PMON_CTR14 = 0x0E3D; constexpr auto R_MSR_PMON_CTL15 = 0x0E3E; constexpr auto R_MSR_PMON_CTR15 = 0x0E3F; constexpr auto R_MSR_PORT0_IPERF_CFG0 = 0x0E04; constexpr auto R_MSR_PORT1_IPERF_CFG0 = 0x0E05; constexpr auto R_MSR_PORT2_IPERF_CFG0 = 0x0E06; constexpr auto R_MSR_PORT3_IPERF_CFG0 = 0x0E07; constexpr auto R_MSR_PORT4_IPERF_CFG0 = 0x0E08; constexpr auto R_MSR_PORT5_IPERF_CFG0 = 0x0E09; constexpr auto R_MSR_PORT6_IPERF_CFG0 = 0x0E0A; constexpr auto R_MSR_PORT7_IPERF_CFG0 = 0x0E0B; constexpr auto R_MSR_PORT0_IPERF_CFG1 = 0x0E24; constexpr auto R_MSR_PORT1_IPERF_CFG1 = 0x0E25; constexpr auto R_MSR_PORT2_IPERF_CFG1 = 0x0E26; constexpr auto R_MSR_PORT3_IPERF_CFG1 = 0x0E27; constexpr auto R_MSR_PORT4_IPERF_CFG1 = 0x0E28; constexpr auto R_MSR_PORT5_IPERF_CFG1 = 0x0E29; constexpr auto R_MSR_PORT6_IPERF_CFG1 = 0x0E2A; constexpr auto R_MSR_PORT7_IPERF_CFG1 = 0x0E2B; constexpr auto R_MSR_PMON_GLOBAL_CTL_7_0 = 0x0E00; constexpr auto R_MSR_PMON_GLOBAL_CTL_15_8 = 0x0E20; constexpr auto W_MSR_PMON_GLOBAL_CTL = 0xC80; constexpr auto W_MSR_PMON_FIXED_CTR_CTL = 0x395; constexpr auto W_MSR_PMON_FIXED_CTR = 0x394; /* * Platform QoS MSRs */ constexpr auto IA32_PQR_ASSOC = 0xc8f; constexpr auto IA32_QM_EVTSEL = 0xc8d; constexpr auto IA32_QM_CTR = 0xc8e; #ifndef KERNEL constexpr auto PCM_INVALID_QOS_MONITORING_DATA = (std::numeric_limits::max)(); #endif /* \brief Event Select Register format According to "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3B: System Programming Guide, Part 2", Figure 30-6. Layout of IA32_PERFEVTSELx MSRs Supporting Architectural Performance Monitoring Version 3 */ struct EventSelectRegister { union { struct { uint64 event_select : 8; uint64 umask : 8; uint64 usr : 1; uint64 os : 1; uint64 edge : 1; uint64 pin_control : 1; uint64 apic_int : 1; uint64 any_thread : 1; uint64 enable : 1; uint64 invert : 1; uint64 cmask : 8; uint64 in_tx : 1; uint64 in_txcp : 1; uint64 reservedX : 30; } fields; uint64 value; }; EventSelectRegister() : value(0) {} }; /* \brief Fixed Event Control Register format According to "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3B: System Programming Guide, Part 2", Figure 30-7. Layout of IA32_FIXED_CTR_CTRL MSR Supporting Architectural Performance Monitoring Version 3 */ struct FixedEventControlRegister { union { struct { // CTR0 uint64 os0 : 1; uint64 usr0 : 1; uint64 any_thread0 : 1; uint64 enable_pmi0 : 1; // CTR1 uint64 os1 : 1; uint64 usr1 : 1; uint64 any_thread1 : 1; uint64 enable_pmi1 : 1; // CTR2 uint64 os2 : 1; uint64 usr2 : 1; uint64 any_thread2 : 1; uint64 enable_pmi2 : 1; // CTR3 uint64 os3 : 1; uint64 usr3 : 1; uint64 any_thread3 : 1; uint64 enable_pmi3 : 1; uint64 reserved1 : 48; } fields; uint64 value; }; FixedEventControlRegister() : value(0) {} }; #ifndef KERNEL inline std::ostream & operator << (std::ostream & o, const FixedEventControlRegister & reg) { o << "os0\t\t" << reg.fields.os0 << "\n"; o << "usr0\t\t" << reg.fields.usr0 << "\n"; o << "any_thread0\t" << reg.fields.any_thread0 << "\n"; o << "enable_pmi0\t" << reg.fields.enable_pmi0 << "\n"; o << "os1\t\t" << reg.fields.os1 << "\n"; o << "usr1\t\t" << reg.fields.usr1 << "\n"; o << "any_thread1\t" << reg.fields.any_thread1 << "\n"; o << "enable_pmi10\t" << reg.fields.enable_pmi1 << "\n"; o << "os2\t\t" << reg.fields.os2 << "\n"; o << "usr2\t\t" << reg.fields.usr2 << "\n"; o << "any_thread2\t" << reg.fields.any_thread2 << "\n"; o << "enable_pmi2\t" << reg.fields.enable_pmi2 << "\n"; o << "reserved1\t" << reg.fields.reserved1 << "\n"; return o; } #endif // #ifndef KERNEL // UNCORE COUNTER CONTROL /* \brief Uncore Event Select Register Register format According to "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3B: System Programming Guide, Part 2", Figure 30-20. Layout of MSR_UNCORE_PERFEVTSELx MSRs */ struct UncoreEventSelectRegister { union { struct { uint64 event_select : 8; uint64 umask : 8; uint64 reserved1 : 1; uint64 occ_ctr_rst : 1; uint64 edge : 1; uint64 reserved2 : 1; uint64 enable_pmi : 1; uint64 reserved3 : 1; uint64 enable : 1; uint64 invert : 1; uint64 cmask : 8; uint64 reservedx : 32; } fields; uint64 value; }; }; /* \brief Beckton Uncore PMU ZDP FVC Control Register format From "Intel(r) Xeon(r) Processor 7500 Series Uncore Programming Guide" Table 2-80. M_MSR_PMU_ZDP_CTL_FVC Register - Field Definitions */ struct BecktonUncorePMUZDPCTLFVCRegister { union { struct { uint64 fvid : 5; uint64 bcmd : 3; uint64 resp : 3; uint64 evnt0 : 3; uint64 evnt1 : 3; uint64 evnt2 : 3; uint64 evnt3 : 3; uint64 pbox_init_err : 1; } fields; // nehalem-ex version struct { uint64 fvid : 6; uint64 bcmd : 3; uint64 resp : 3; uint64 evnt0 : 3; uint64 evnt1 : 3; uint64 evnt2 : 3; uint64 evnt3 : 3; uint64 pbox_init_err : 1; } fields_wsm; // westmere-ex version uint64 value; }; }; /* \brief Beckton Uncore PMU Counter Control Register format From "Intel(r) Xeon(r) Processor 7500 Series Uncore Programming Guide" Table 2-67. M_MSR_PMU_CNT_CTL{5-0} Register - Field Definitions */ struct BecktonUncorePMUCNTCTLRegister { union { struct { uint64 en : 1; uint64 pmi_en : 1; uint64 count_mode : 2; uint64 storage_mode : 2; uint64 wrap_mode : 1; uint64 flag_mode : 1; uint64 rsv1 : 1; uint64 inc_sel : 5; uint64 rsv2 : 5; uint64 set_flag_sel : 3; } fields; uint64 value; }; }; constexpr auto MSR_SMI_COUNT = 0x34; /* \brief Sandy Bridge energy counters */ constexpr auto MSR_PKG_ENERGY_STATUS = 0x611; constexpr auto MSR_SYS_ENERGY_STATUS = 0x64D; constexpr auto MSR_RAPL_POWER_UNIT = 0x606; constexpr auto MSR_PKG_POWER_INFO = 0x614; constexpr auto PCM_INTEL_PCI_VENDOR_ID = 0x8086; constexpr auto PCM_PCI_VENDOR_ID_OFFSET = 0; // server PCICFG uncore counters constexpr auto JKTIVT_MC0_CH0_REGISTER_DEV_ADDR = 16; constexpr auto JKTIVT_MC0_CH1_REGISTER_DEV_ADDR = 16; constexpr auto JKTIVT_MC0_CH2_REGISTER_DEV_ADDR = 16; constexpr auto JKTIVT_MC0_CH3_REGISTER_DEV_ADDR = 16; constexpr auto JKTIVT_MC0_CH0_REGISTER_FUNC_ADDR = 4; constexpr auto JKTIVT_MC0_CH1_REGISTER_FUNC_ADDR = 5; constexpr auto JKTIVT_MC0_CH2_REGISTER_FUNC_ADDR = 0; constexpr auto JKTIVT_MC0_CH3_REGISTER_FUNC_ADDR = 1; constexpr auto JKTIVT_MC1_CH0_REGISTER_DEV_ADDR = 30; constexpr auto JKTIVT_MC1_CH1_REGISTER_DEV_ADDR = 30; constexpr auto JKTIVT_MC1_CH2_REGISTER_DEV_ADDR = 30; constexpr auto JKTIVT_MC1_CH3_REGISTER_DEV_ADDR = 30; constexpr auto JKTIVT_MC1_CH0_REGISTER_FUNC_ADDR = 4; constexpr auto JKTIVT_MC1_CH1_REGISTER_FUNC_ADDR = 5; constexpr auto JKTIVT_MC1_CH2_REGISTER_FUNC_ADDR = 0; constexpr auto JKTIVT_MC1_CH3_REGISTER_FUNC_ADDR = 1; constexpr auto HSX_MC0_CH0_REGISTER_DEV_ADDR = 20; constexpr auto HSX_MC0_CH1_REGISTER_DEV_ADDR = 20; constexpr auto HSX_MC0_CH2_REGISTER_DEV_ADDR = 21; constexpr auto HSX_MC0_CH3_REGISTER_DEV_ADDR = 21; constexpr auto HSX_MC0_CH0_REGISTER_FUNC_ADDR = 0; constexpr auto HSX_MC0_CH1_REGISTER_FUNC_ADDR = 1; constexpr auto HSX_MC0_CH2_REGISTER_FUNC_ADDR = 0; constexpr auto HSX_MC0_CH3_REGISTER_FUNC_ADDR = 1; constexpr auto HSX_MC1_CH0_REGISTER_DEV_ADDR = 23; constexpr auto HSX_MC1_CH1_REGISTER_DEV_ADDR = 23; constexpr auto HSX_MC1_CH2_REGISTER_DEV_ADDR = 24; constexpr auto HSX_MC1_CH3_REGISTER_DEV_ADDR = 24; constexpr auto HSX_MC1_CH0_REGISTER_FUNC_ADDR = 0; constexpr auto HSX_MC1_CH1_REGISTER_FUNC_ADDR = 1; constexpr auto HSX_MC1_CH2_REGISTER_FUNC_ADDR = 0; constexpr auto HSX_MC1_CH3_REGISTER_FUNC_ADDR = 1; constexpr auto KNL_MC0_CH0_REGISTER_DEV_ADDR = 8; constexpr auto KNL_MC0_CH1_REGISTER_DEV_ADDR = 8; constexpr auto KNL_MC0_CH2_REGISTER_DEV_ADDR = 8; constexpr auto KNL_MC0_CH0_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_MC0_CH1_REGISTER_FUNC_ADDR = 3; constexpr auto KNL_MC0_CH2_REGISTER_FUNC_ADDR = 4; constexpr auto SKX_MC0_CH0_REGISTER_DEV_ADDR = 10; constexpr auto SKX_MC0_CH1_REGISTER_DEV_ADDR = 10; constexpr auto SKX_MC0_CH2_REGISTER_DEV_ADDR = 11; constexpr auto SKX_MC0_CH3_REGISTER_DEV_ADDR = -1; //Does not exist constexpr auto SKX_MC0_CH0_REGISTER_FUNC_ADDR = 2; constexpr auto SKX_MC0_CH1_REGISTER_FUNC_ADDR = 6; constexpr auto SKX_MC0_CH2_REGISTER_FUNC_ADDR = 2; constexpr auto SKX_MC0_CH3_REGISTER_FUNC_ADDR = -1; //Does not exist constexpr auto SKX_MC1_CH0_REGISTER_DEV_ADDR = 12; constexpr auto SKX_MC1_CH1_REGISTER_DEV_ADDR = 12; constexpr auto SKX_MC1_CH2_REGISTER_DEV_ADDR = 13; constexpr auto SKX_MC1_CH3_REGISTER_DEV_ADDR = -1; //Does not exist constexpr auto SKX_MC1_CH0_REGISTER_FUNC_ADDR = 2; constexpr auto SKX_MC1_CH1_REGISTER_FUNC_ADDR = 6; constexpr auto SKX_MC1_CH2_REGISTER_FUNC_ADDR = 2; constexpr auto SKX_MC1_CH3_REGISTER_FUNC_ADDR = -1; //Does not exist constexpr auto SERVER_UBOX0_REGISTER_DEV_ADDR = 0; constexpr auto SERVER_UBOX0_REGISTER_FUNC_ADDR = 1; constexpr auto KNL_MC1_CH0_REGISTER_DEV_ADDR = 9; constexpr auto KNL_MC1_CH1_REGISTER_DEV_ADDR = 9; constexpr auto KNL_MC1_CH2_REGISTER_DEV_ADDR = 9; constexpr auto KNL_MC1_CH0_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_MC1_CH1_REGISTER_FUNC_ADDR = 3; constexpr auto KNL_MC1_CH2_REGISTER_FUNC_ADDR = 4; constexpr auto KNL_EDC0_ECLK_REGISTER_DEV_ADDR = 24; constexpr auto KNL_EDC0_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC1_ECLK_REGISTER_DEV_ADDR = 25; constexpr auto KNL_EDC1_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC2_ECLK_REGISTER_DEV_ADDR = 26; constexpr auto KNL_EDC2_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC3_ECLK_REGISTER_DEV_ADDR = 27; constexpr auto KNL_EDC3_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC4_ECLK_REGISTER_DEV_ADDR = 28; constexpr auto KNL_EDC4_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC5_ECLK_REGISTER_DEV_ADDR = 29; constexpr auto KNL_EDC5_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC6_ECLK_REGISTER_DEV_ADDR = 30; constexpr auto KNL_EDC6_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto KNL_EDC7_ECLK_REGISTER_DEV_ADDR = 31; constexpr auto KNL_EDC7_ECLK_REGISTER_FUNC_ADDR = 2; constexpr auto HSX_HA0_REGISTER_DEV_ADDR = 18; constexpr auto HSX_HA0_REGISTER_FUNC_ADDR = 1; constexpr auto HSX_HA1_REGISTER_DEV_ADDR = 18; constexpr auto HSX_HA1_REGISTER_FUNC_ADDR = 5; constexpr auto XPF_HA_PCI_PMON_BOX_CTL_ADDR = 0xF4; constexpr auto XPF_HA_PCI_PMON_CTL0_ADDR = 0xD8 + 4*0; constexpr auto XPF_HA_PCI_PMON_CTL1_ADDR = 0xD8 + 4*1; constexpr auto XPF_HA_PCI_PMON_CTL2_ADDR = 0xD8 + 4*2; constexpr auto XPF_HA_PCI_PMON_CTL3_ADDR = 0xD8 + 4*3; constexpr auto XPF_HA_PCI_PMON_CTR0_ADDR = 0xA0 + 8*0; constexpr auto XPF_HA_PCI_PMON_CTR1_ADDR = 0xA0 + 8*1; constexpr auto XPF_HA_PCI_PMON_CTR2_ADDR = 0xA0 + 8*2; constexpr auto XPF_HA_PCI_PMON_CTR3_ADDR = 0xA0 + 8*3; constexpr auto BHS_PCIE_GEN5_PCI_PMON_BOX_CTL_ADDR = 0x620; constexpr auto BHS_PCIE_GEN5_PCI_PMON_CTL0_ADDR = 0x630; constexpr auto BHS_PCIE_GEN5_PCI_PMON_CTR0_ADDR = 0x650; /** * XPF_ for Xeons: SNB, IVT, HSX, BDW, etc. * KNX_ for Xeon Phi (Knights *) processors */ constexpr auto XPF_MC_CH_PCI_PMON_BOX_CTL_ADDR = 0x0F4; constexpr auto KNX_MC_CH_PCI_PMON_BOX_CTL_ADDR = 0xB30; constexpr auto KNX_EDC_CH_PCI_PMON_BOX_CTL_ADDR = 0xA30; //! for Xeons constexpr auto XPF_MC_CH_PCI_PMON_FIXED_CTL_ADDR = 0x0F0; constexpr auto XPF_MC_CH_PCI_PMON_CTL3_ADDR = 0x0E4; constexpr auto XPF_MC_CH_PCI_PMON_CTL2_ADDR = 0x0E0; constexpr auto XPF_MC_CH_PCI_PMON_CTL1_ADDR = 0x0DC; constexpr auto XPF_MC_CH_PCI_PMON_CTL0_ADDR = 0x0D8; //! KNL IMC constexpr auto KNX_MC_CH_PCI_PMON_FIXED_CTL_ADDR = 0xB44; constexpr auto KNX_MC_CH_PCI_PMON_CTL3_ADDR = 0xB2C; constexpr auto KNX_MC_CH_PCI_PMON_CTL2_ADDR = 0xB28; constexpr auto KNX_MC_CH_PCI_PMON_CTL1_ADDR = 0xB24; constexpr auto KNX_MC_CH_PCI_PMON_CTL0_ADDR = 0xB20; //! KNL EDC ECLK constexpr auto KNX_EDC_CH_PCI_PMON_FIXED_CTL_ADDR = 0xA44; constexpr auto KNX_EDC_CH_PCI_PMON_CTL3_ADDR = 0xA2C; constexpr auto KNX_EDC_CH_PCI_PMON_CTL2_ADDR = 0xA28; constexpr auto KNX_EDC_CH_PCI_PMON_CTL1_ADDR = 0xA24; constexpr auto KNX_EDC_CH_PCI_PMON_CTL0_ADDR = 0xA20; constexpr auto KNX_EDC_ECLK_PMON_UNIT_CTL_REG = 0xA30; //! for Xeons constexpr auto XPF_MC_CH_PCI_PMON_FIXED_CTR_ADDR = 0x0D0; constexpr auto XPF_MC_CH_PCI_PMON_CTR3_ADDR = 0x0B8; constexpr auto XPF_MC_CH_PCI_PMON_CTR2_ADDR = 0x0B0; constexpr auto XPF_MC_CH_PCI_PMON_CTR1_ADDR = 0x0A8; constexpr auto XPF_MC_CH_PCI_PMON_CTR0_ADDR = 0x0A0; //! for KNL IMC constexpr auto KNX_MC_CH_PCI_PMON_FIXED_CTR_ADDR = 0xB3C; constexpr auto KNX_MC_CH_PCI_PMON_CTR3_ADDR = 0xB18; constexpr auto KNX_MC_CH_PCI_PMON_CTR2_ADDR = 0xB10; constexpr auto KNX_MC_CH_PCI_PMON_CTR1_ADDR = 0xB08; constexpr auto KNX_MC_CH_PCI_PMON_CTR0_ADDR = 0xB00; //! for KNL EDC ECLK constexpr auto KNX_EDC_CH_PCI_PMON_FIXED_CTR_ADDR = 0xA3C; constexpr auto KNX_EDC_CH_PCI_PMON_CTR3_ADDR = 0xA18; constexpr auto KNX_EDC_CH_PCI_PMON_CTR2_ADDR = 0xA10; constexpr auto KNX_EDC_CH_PCI_PMON_CTR1_ADDR = 0xA08; constexpr auto KNX_EDC_CH_PCI_PMON_CTR0_ADDR = 0xA00; constexpr auto SERVER_HBM_CH_PMON_BASE_ADDR = 0x141c00; constexpr auto SERVER_HBM_CH_PMON_STEP = 0x4000; constexpr auto SERVER_HBM_CH_PMON_SIZE = 0x1000; constexpr auto SERVER_HBM_BOX_PMON_STEP = 0x9000; constexpr auto SERVER_MC_CH_PMON_BASE_ADDR = 0x22800; constexpr auto SERVER_MC_CH_PMON_STEP = 0x4000; constexpr auto SERVER_MC_CH_PMON_SIZE = 0x1000; constexpr auto SERVER_MC_CH_PMON_BOX_CTL_OFFSET = 0x00; constexpr auto SERVER_MC_CH_PMON_CTL0_OFFSET = 0x40; constexpr auto SERVER_MC_CH_PMON_CTL1_OFFSET = SERVER_MC_CH_PMON_CTL0_OFFSET + 4*1; constexpr auto SERVER_MC_CH_PMON_CTL2_OFFSET = SERVER_MC_CH_PMON_CTL0_OFFSET + 4*2; constexpr auto SERVER_MC_CH_PMON_CTL3_OFFSET = SERVER_MC_CH_PMON_CTL0_OFFSET + 4*3; constexpr auto SERVER_MC_CH_PMON_CTR0_OFFSET = 0x08; constexpr auto SERVER_MC_CH_PMON_CTR1_OFFSET = SERVER_MC_CH_PMON_CTR0_OFFSET + 8*1; constexpr auto SERVER_MC_CH_PMON_CTR2_OFFSET = SERVER_MC_CH_PMON_CTR0_OFFSET + 8*2; constexpr auto SERVER_MC_CH_PMON_CTR3_OFFSET = SERVER_MC_CH_PMON_CTR0_OFFSET + 8*3; constexpr auto SERVER_MC_CH_PMON_FIXED_CTL_OFFSET = 0x54; constexpr auto SERVER_MC_CH_PMON_FIXED_CTR_OFFSET = 0x38; constexpr auto BHS_MC_CH_PMON_BASE_ADDR = 0x024e800; constexpr auto GNR_D_A_MC_CH_PMON_BASE_ADDR = 0x0104800; constexpr auto GNR_D_B_MC_CH_PMON_BASE_ADDR = 0x0208800; constexpr auto JKTIVT_QPI_PORT0_REGISTER_DEV_ADDR = 8; constexpr auto JKTIVT_QPI_PORT0_REGISTER_FUNC_ADDR = 2; constexpr auto JKTIVT_QPI_PORT1_REGISTER_DEV_ADDR = 9; constexpr auto JKTIVT_QPI_PORT1_REGISTER_FUNC_ADDR = 2; constexpr auto JKTIVT_QPI_PORT2_REGISTER_DEV_ADDR = 24; constexpr auto JKTIVT_QPI_PORT2_REGISTER_FUNC_ADDR = 2; constexpr auto HSX_QPI_PORT0_REGISTER_DEV_ADDR = 8; constexpr auto HSX_QPI_PORT0_REGISTER_FUNC_ADDR = 2; constexpr auto HSX_QPI_PORT1_REGISTER_DEV_ADDR = 9; constexpr auto HSX_QPI_PORT1_REGISTER_FUNC_ADDR = 2; constexpr auto HSX_QPI_PORT2_REGISTER_DEV_ADDR = 10; constexpr auto HSX_QPI_PORT2_REGISTER_FUNC_ADDR = 2; constexpr auto SKX_QPI_PORT0_REGISTER_DEV_ADDR = 14; constexpr auto SKX_QPI_PORT0_REGISTER_FUNC_ADDR = 0; constexpr auto SKX_QPI_PORT1_REGISTER_DEV_ADDR = 15; constexpr auto SKX_QPI_PORT1_REGISTER_FUNC_ADDR = 0; constexpr auto SKX_QPI_PORT2_REGISTER_DEV_ADDR = 16; constexpr auto SKX_QPI_PORT2_REGISTER_FUNC_ADDR = 0; constexpr auto CPX_QPI_PORT3_REGISTER_DEV_ADDR = 14; constexpr auto CPX_QPI_PORT3_REGISTER_FUNC_ADDR = 4; constexpr auto CPX_QPI_PORT4_REGISTER_DEV_ADDR = 15; constexpr auto CPX_QPI_PORT4_REGISTER_FUNC_ADDR = 4; constexpr auto CPX_QPI_PORT5_REGISTER_DEV_ADDR = 16; constexpr auto CPX_QPI_PORT5_REGISTER_FUNC_ADDR = 4; constexpr auto ICX_QPI_PORT0_REGISTER_DEV_ADDR = 2; constexpr auto ICX_QPI_PORT0_REGISTER_FUNC_ADDR = 1; constexpr auto ICX_QPI_PORT1_REGISTER_DEV_ADDR = 3; constexpr auto ICX_QPI_PORT1_REGISTER_FUNC_ADDR = 1; constexpr auto ICX_QPI_PORT2_REGISTER_DEV_ADDR = 4; constexpr auto ICX_QPI_PORT2_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_QPI_PORT0_REGISTER_DEV_ADDR = 1; constexpr auto SPR_QPI_PORT0_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_QPI_PORT1_REGISTER_DEV_ADDR = 2; constexpr auto SPR_QPI_PORT1_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_QPI_PORT2_REGISTER_DEV_ADDR = 3; constexpr auto SPR_QPI_PORT2_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_QPI_PORT3_REGISTER_DEV_ADDR = 4; constexpr auto SPR_QPI_PORT3_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_QPI_PORT0_REGISTER_DEV_ADDR = 16; constexpr auto BHS_QPI_PORT0_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_QPI_PORT1_REGISTER_DEV_ADDR = 17; constexpr auto BHS_QPI_PORT1_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_QPI_PORT2_REGISTER_DEV_ADDR = 18; constexpr auto BHS_QPI_PORT2_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_QPI_PORT3_REGISTER_DEV_ADDR = 19; constexpr auto BHS_QPI_PORT3_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_QPI_PORT4_REGISTER_DEV_ADDR = 20; constexpr auto BHS_QPI_PORT4_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_QPI_PORT5_REGISTER_DEV_ADDR = 21; constexpr auto BHS_QPI_PORT5_REGISTER_FUNC_ADDR = 1; constexpr auto QPI_PORT0_MISC_REGISTER_FUNC_ADDR = 0; constexpr auto QPI_PORT1_MISC_REGISTER_FUNC_ADDR = 0; constexpr auto QPI_PORT2_MISC_REGISTER_FUNC_ADDR = 0; constexpr auto SKX_M3UPI_PORT0_REGISTER_DEV_ADDR = (0x12); constexpr auto SKX_M3UPI_PORT0_REGISTER_FUNC_ADDR = (1); constexpr auto SKX_M3UPI_PORT1_REGISTER_DEV_ADDR = (0x12); constexpr auto SKX_M3UPI_PORT1_REGISTER_FUNC_ADDR = (2); constexpr auto SKX_M3UPI_PORT2_REGISTER_DEV_ADDR = (0x12); constexpr auto SKX_M3UPI_PORT2_REGISTER_FUNC_ADDR = (5); constexpr auto CPX_M3UPI_PORT0_REGISTER_DEV_ADDR = (0x12); constexpr auto CPX_M3UPI_PORT0_REGISTER_FUNC_ADDR = (1); constexpr auto CPX_M3UPI_PORT1_REGISTER_DEV_ADDR = (0x12); constexpr auto CPX_M3UPI_PORT1_REGISTER_FUNC_ADDR = (2); constexpr auto CPX_M3UPI_PORT2_REGISTER_DEV_ADDR = (0x13); constexpr auto CPX_M3UPI_PORT2_REGISTER_FUNC_ADDR = (1); constexpr auto CPX_M3UPI_PORT3_REGISTER_DEV_ADDR = (0x13); constexpr auto CPX_M3UPI_PORT3_REGISTER_FUNC_ADDR = (2); constexpr auto CPX_M3UPI_PORT4_REGISTER_DEV_ADDR = (0x14); constexpr auto CPX_M3UPI_PORT4_REGISTER_FUNC_ADDR = (1); constexpr auto CPX_M3UPI_PORT5_REGISTER_DEV_ADDR = (0x14); constexpr auto CPX_M3UPI_PORT5_REGISTER_FUNC_ADDR = (2); constexpr auto ICX_M3UPI_PORT0_REGISTER_DEV_ADDR = (5); constexpr auto ICX_M3UPI_PORT1_REGISTER_DEV_ADDR = (6); constexpr auto ICX_M3UPI_PORT2_REGISTER_DEV_ADDR = (7); constexpr auto ICX_M3UPI_PORT0_REGISTER_FUNC_ADDR = (1); constexpr auto ICX_M3UPI_PORT1_REGISTER_FUNC_ADDR = (1); constexpr auto ICX_M3UPI_PORT2_REGISTER_FUNC_ADDR = (1); constexpr auto SPR_M3UPI_PORT0_REGISTER_DEV_ADDR = 5; constexpr auto SPR_M3UPI_PORT1_REGISTER_DEV_ADDR = 6; constexpr auto SPR_M3UPI_PORT2_REGISTER_DEV_ADDR = 7; constexpr auto SPR_M3UPI_PORT3_REGISTER_DEV_ADDR = 8; constexpr auto SPR_M3UPI_PORT0_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_M3UPI_PORT1_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_M3UPI_PORT2_REGISTER_FUNC_ADDR = 1; constexpr auto SPR_M3UPI_PORT3_REGISTER_FUNC_ADDR = 1; constexpr auto SKX_M2M_0_REGISTER_DEV_ADDR = 8; constexpr auto SKX_M2M_0_REGISTER_FUNC_ADDR = 0; constexpr auto SKX_M2M_1_REGISTER_DEV_ADDR = 9; constexpr auto SKX_M2M_1_REGISTER_FUNC_ADDR = 0; constexpr auto SERVER_M2M_0_REGISTER_DEV_ADDR = 12; constexpr auto SERVER_M2M_0_REGISTER_FUNC_ADDR = 0; constexpr auto SERVER_M2M_1_REGISTER_DEV_ADDR = 13; constexpr auto SERVER_M2M_1_REGISTER_FUNC_ADDR = 0; constexpr auto SERVER_M2M_2_REGISTER_DEV_ADDR = 14; constexpr auto SERVER_M2M_2_REGISTER_FUNC_ADDR = 0; constexpr auto SERVER_M2M_3_REGISTER_DEV_ADDR = 15; constexpr auto SERVER_M2M_3_REGISTER_FUNC_ADDR = 0; constexpr auto SERVER_HBM_M2M_0_REGISTER_DEV_ADDR = 12; constexpr auto SERVER_HBM_M2M_0_REGISTER_FUNC_ADDR = 1; constexpr auto SERVER_HBM_M2M_1_REGISTER_DEV_ADDR = 13; constexpr auto SERVER_HBM_M2M_1_REGISTER_FUNC_ADDR = 1; constexpr auto SERVER_HBM_M2M_2_REGISTER_DEV_ADDR = 14; constexpr auto SERVER_HBM_M2M_2_REGISTER_FUNC_ADDR = 1; constexpr auto SERVER_HBM_M2M_3_REGISTER_DEV_ADDR = 15; constexpr auto SERVER_HBM_M2M_3_REGISTER_FUNC_ADDR = 1; constexpr auto SERVER_HBM_M2M_4_REGISTER_DEV_ADDR = 12; constexpr auto SERVER_HBM_M2M_4_REGISTER_FUNC_ADDR = 2; constexpr auto SERVER_HBM_M2M_5_REGISTER_DEV_ADDR = 13; constexpr auto SERVER_HBM_M2M_5_REGISTER_FUNC_ADDR = 2; constexpr auto SERVER_HBM_M2M_6_REGISTER_DEV_ADDR = 14; constexpr auto SERVER_HBM_M2M_6_REGISTER_FUNC_ADDR = 2; constexpr auto SERVER_HBM_M2M_7_REGISTER_DEV_ADDR = 15; constexpr auto SERVER_HBM_M2M_7_REGISTER_FUNC_ADDR = 2; constexpr auto SERVER_HBM_M2M_8_REGISTER_DEV_ADDR = 12; constexpr auto SERVER_HBM_M2M_8_REGISTER_FUNC_ADDR = 3; constexpr auto SERVER_HBM_M2M_9_REGISTER_DEV_ADDR = 13; constexpr auto SERVER_HBM_M2M_9_REGISTER_FUNC_ADDR = 3; constexpr auto SERVER_HBM_M2M_10_REGISTER_DEV_ADDR = 14; constexpr auto SERVER_HBM_M2M_10_REGISTER_FUNC_ADDR = 3; constexpr auto SERVER_HBM_M2M_11_REGISTER_DEV_ADDR = 15; constexpr auto SERVER_HBM_M2M_11_REGISTER_FUNC_ADDR = 3; constexpr auto SERVER_HBM_M2M_12_REGISTER_DEV_ADDR = 12; constexpr auto SERVER_HBM_M2M_12_REGISTER_FUNC_ADDR = 4; constexpr auto SERVER_HBM_M2M_13_REGISTER_DEV_ADDR = 13; constexpr auto SERVER_HBM_M2M_13_REGISTER_FUNC_ADDR = 4; constexpr auto SERVER_HBM_M2M_14_REGISTER_DEV_ADDR = 14; constexpr auto SERVER_HBM_M2M_14_REGISTER_FUNC_ADDR = 4; constexpr auto SERVER_HBM_M2M_15_REGISTER_DEV_ADDR = 15; constexpr auto SERVER_HBM_M2M_15_REGISTER_FUNC_ADDR = 4; // BHS B2CMI (M2M) constexpr auto BHS_M2M_0_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_0_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_M2M_1_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_1_REGISTER_FUNC_ADDR = 2; constexpr auto BHS_M2M_2_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_2_REGISTER_FUNC_ADDR = 3; constexpr auto BHS_M2M_3_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_3_REGISTER_FUNC_ADDR = 4; constexpr auto BHS_M2M_4_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_4_REGISTER_FUNC_ADDR = 5; constexpr auto BHS_M2M_5_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_5_REGISTER_FUNC_ADDR = 6; constexpr auto BHS_M2M_6_REGISTER_DEV_ADDR = 5; constexpr auto BHS_M2M_6_REGISTER_FUNC_ADDR = 7; constexpr auto BHS_M2M_7_REGISTER_DEV_ADDR = 6; constexpr auto BHS_M2M_7_REGISTER_FUNC_ADDR = 1; constexpr auto BHS_M2M_8_REGISTER_DEV_ADDR = 6; constexpr auto BHS_M2M_8_REGISTER_FUNC_ADDR = 2; constexpr auto BHS_M2M_9_REGISTER_DEV_ADDR = 6; constexpr auto BHS_M2M_9_REGISTER_FUNC_ADDR = 3; constexpr auto BHS_M2M_10_REGISTER_DEV_ADDR = 6; constexpr auto BHS_M2M_10_REGISTER_FUNC_ADDR = 4; constexpr auto BHS_M2M_11_REGISTER_DEV_ADDR = 6; constexpr auto BHS_M2M_11_REGISTER_FUNC_ADDR = 5; // BHS B2UPI (M3UPI) constexpr auto BHS_M3UPI_PORT0_REGISTER_DEV_ADDR = 24; constexpr auto BHS_M3UPI_PORT1_REGISTER_DEV_ADDR = 25; constexpr auto BHS_M3UPI_PORT2_REGISTER_DEV_ADDR = 26; constexpr auto BHS_M3UPI_PORT3_REGISTER_DEV_ADDR = 27; constexpr auto BHS_M3UPI_PORT4_REGISTER_DEV_ADDR = 28; constexpr auto BHS_M3UPI_PORT5_REGISTER_DEV_ADDR = 29; constexpr auto BHS_M3UPI_PORT0_REGISTER_FUNC_ADDR = 0; constexpr auto BHS_M3UPI_PORT1_REGISTER_FUNC_ADDR = 0; constexpr auto BHS_M3UPI_PORT2_REGISTER_FUNC_ADDR = 0; constexpr auto BHS_M3UPI_PORT3_REGISTER_FUNC_ADDR = 0; constexpr auto BHS_M3UPI_PORT4_REGISTER_FUNC_ADDR = 0; constexpr auto BHS_M3UPI_PORT5_REGISTER_FUNC_ADDR = 0; constexpr auto SKX_M2M_PCI_PMON_BOX_CTL_ADDR = 0x258; constexpr auto SKX_M2M_PCI_PMON_CTL0_ADDR = 0x228; constexpr auto SKX_M2M_PCI_PMON_CTL1_ADDR = 0x230; constexpr auto SKX_M2M_PCI_PMON_CTL2_ADDR = 0x238; constexpr auto SKX_M2M_PCI_PMON_CTL3_ADDR = 0x240; constexpr auto SKX_M2M_PCI_PMON_CTR0_ADDR = 0x200; constexpr auto SKX_M2M_PCI_PMON_CTR1_ADDR = 0x208; constexpr auto SKX_M2M_PCI_PMON_CTR2_ADDR = 0x210; constexpr auto SKX_M2M_PCI_PMON_CTR3_ADDR = 0x218; constexpr auto SERVER_M2M_PCI_PMON_BOX_CTL_ADDR = 0x438; constexpr auto SERVER_M2M_PCI_PMON_CTL0_ADDR = 0x468; constexpr auto SERVER_M2M_PCI_PMON_CTL1_ADDR = SERVER_M2M_PCI_PMON_CTL0_ADDR + 1*8; constexpr auto SERVER_M2M_PCI_PMON_CTL2_ADDR = SERVER_M2M_PCI_PMON_CTL0_ADDR + 2*8; constexpr auto SERVER_M2M_PCI_PMON_CTL3_ADDR = SERVER_M2M_PCI_PMON_CTL0_ADDR + 3*8; constexpr auto SERVER_M2M_PCI_PMON_CTR0_ADDR = 0x440; constexpr auto SERVER_M2M_PCI_PMON_CTR1_ADDR = SERVER_M2M_PCI_PMON_CTR0_ADDR + 1*8; constexpr auto SERVER_M2M_PCI_PMON_CTR2_ADDR = SERVER_M2M_PCI_PMON_CTR0_ADDR + 2*8; constexpr auto SERVER_M2M_PCI_PMON_CTR3_ADDR = SERVER_M2M_PCI_PMON_CTR0_ADDR + 3*8; constexpr auto M3UPI_PCI_PMON_BOX_CTL_ADDR = (0xF4); constexpr auto M3UPI_PCI_PMON_CTL0_ADDR = (0xD8); constexpr auto M3UPI_PCI_PMON_CTL1_ADDR = (0xDC); constexpr auto M3UPI_PCI_PMON_CTL2_ADDR = (0xE0); constexpr auto M3UPI_PCI_PMON_CTR0_ADDR = (0xA0); constexpr auto M3UPI_PCI_PMON_CTR1_ADDR = (0xA8); constexpr auto M3UPI_PCI_PMON_CTR2_ADDR = (0xB0); constexpr auto ICX_M3UPI_PCI_PMON_BOX_CTL_ADDR = (0xA0); constexpr auto ICX_M3UPI_PCI_PMON_CTL0_ADDR = (0xD8); constexpr auto ICX_M3UPI_PCI_PMON_CTL1_ADDR = (0xDC); constexpr auto ICX_M3UPI_PCI_PMON_CTL2_ADDR = (0xE0); constexpr auto ICX_M3UPI_PCI_PMON_CTL3_ADDR = (0xE4); constexpr auto ICX_M3UPI_PCI_PMON_CTR0_ADDR = (0xA8); constexpr auto ICX_M3UPI_PCI_PMON_CTR1_ADDR = (0xB0); constexpr auto ICX_M3UPI_PCI_PMON_CTR2_ADDR = (0xB8); constexpr auto ICX_M3UPI_PCI_PMON_CTR3_ADDR = (0xC0); constexpr auto BHS_M3UPI_PCI_PMON_BOX_CTL_ADDR = (0x408); constexpr auto BHS_M3UPI_PCI_PMON_CTL0_ADDR = (0x430); constexpr auto BHS_M3UPI_PCI_PMON_CTL1_ADDR = (0x438); constexpr auto BHS_M3UPI_PCI_PMON_CTL2_ADDR = (0x440); constexpr auto BHS_M3UPI_PCI_PMON_CTL3_ADDR = (0x448); constexpr auto BHS_M3UPI_PCI_PMON_CTR0_ADDR = (0x410); constexpr auto BHS_M3UPI_PCI_PMON_CTR1_ADDR = (0x418); constexpr auto BHS_M3UPI_PCI_PMON_CTR2_ADDR = (0x420); constexpr auto BHS_M3UPI_PCI_PMON_CTR3_ADDR = (0x428); constexpr auto MSR_UNCORE_PMON_GLOBAL_CTL = 0x700; constexpr auto IVT_MSR_UNCORE_PMON_GLOBAL_CTL = 0x0C00; constexpr auto SPR_MSR_UNCORE_PMON_GLOBAL_CTL = 0x2FF0; constexpr auto PCM_INVALID_DEV_ADDR = ~(uint32)0UL; constexpr auto PCM_INVALID_FUNC_ADDR = ~(uint32)0UL; constexpr auto Q_P_PCI_PMON_BOX_CTL_ADDR = 0x0F4; constexpr auto Q_P_PCI_PMON_CTL3_ADDR = 0x0E4; constexpr auto Q_P_PCI_PMON_CTL2_ADDR = 0x0E0; constexpr auto Q_P_PCI_PMON_CTL1_ADDR = 0x0DC; constexpr auto Q_P_PCI_PMON_CTL0_ADDR = 0x0D8; constexpr auto Q_P_PCI_PMON_CTR3_ADDR = 0x0B8; constexpr auto Q_P_PCI_PMON_CTR2_ADDR = 0x0B0; constexpr auto Q_P_PCI_PMON_CTR1_ADDR = 0x0A8; constexpr auto Q_P_PCI_PMON_CTR0_ADDR = 0x0A0; constexpr auto QPI_RATE_STATUS_ADDR = 0x0D4; constexpr auto U_L_PCI_PMON_BOX_CTL_ADDR = 0x378; constexpr auto U_L_PCI_PMON_CTL3_ADDR = 0x368; constexpr auto U_L_PCI_PMON_CTL2_ADDR = 0x360; constexpr auto U_L_PCI_PMON_CTL1_ADDR = 0x358; constexpr auto U_L_PCI_PMON_CTL0_ADDR = 0x350; constexpr auto U_L_PCI_PMON_CTR3_ADDR = 0x330; constexpr auto U_L_PCI_PMON_CTR2_ADDR = 0x328; constexpr auto U_L_PCI_PMON_CTR1_ADDR = 0x320; constexpr auto U_L_PCI_PMON_CTR0_ADDR = 0x318; constexpr auto ICX_UPI_PCI_PMON_BOX_CTL_ADDR = 0x318; constexpr auto ICX_UPI_PCI_PMON_CTL3_ADDR = 0x368; constexpr auto ICX_UPI_PCI_PMON_CTL2_ADDR = 0x360; constexpr auto ICX_UPI_PCI_PMON_CTL1_ADDR = 0x358; constexpr auto ICX_UPI_PCI_PMON_CTL0_ADDR = 0x350; constexpr auto ICX_UPI_PCI_PMON_CTR3_ADDR = 0x338; constexpr auto ICX_UPI_PCI_PMON_CTR2_ADDR = 0x330; constexpr auto ICX_UPI_PCI_PMON_CTR1_ADDR = 0x328; constexpr auto ICX_UPI_PCI_PMON_CTR0_ADDR = 0x320; constexpr auto SPR_UPI_PCI_PMON_BOX_CTL_ADDR = 0x318; constexpr auto SPR_UPI_PCI_PMON_CTL0_ADDR = 0x350; constexpr auto SPR_UPI_PCI_PMON_CTR0_ADDR = 0x320; constexpr auto UCLK_FIXED_CTR_ADDR = 0x704; constexpr auto UCLK_FIXED_CTL_ADDR = 0x703; constexpr auto UBOX_MSR_PMON_CTL0_ADDR = 0x705; constexpr auto UBOX_MSR_PMON_CTL1_ADDR = 0x706; constexpr auto UBOX_MSR_PMON_CTR0_ADDR = 0x709; constexpr auto UBOX_MSR_PMON_CTR1_ADDR = 0x70a; constexpr auto SPR_UCLK_FIXED_CTR_ADDR = 0x2FDF; constexpr auto SPR_UCLK_FIXED_CTL_ADDR = 0x2FDE; constexpr auto SPR_UBOX_MSR_PMON_BOX_CTL_ADDR = 0x2FD0; constexpr auto SPR_UBOX_MSR_PMON_CTL0_ADDR = 0x2FD2; constexpr auto SPR_UBOX_MSR_PMON_CTL1_ADDR = 0x2FD3; constexpr auto SPR_UBOX_MSR_PMON_CTR0_ADDR = 0X2FD8; constexpr auto SPR_UBOX_MSR_PMON_CTR1_ADDR = 0X2FD9; constexpr auto BHS_UCLK_FIXED_CTR_ADDR = 0x3FFD; constexpr auto BHS_UCLK_FIXED_CTL_ADDR = 0x3FFE; constexpr auto BHS_UBOX_MSR_PMON_BOX_CTL_ADDR = 0x3FF0; constexpr auto BHS_UBOX_MSR_PMON_CTL0_ADDR = 0x3FF2; constexpr auto BHS_UBOX_MSR_PMON_CTL1_ADDR = 0x3FF3; constexpr auto BHS_UBOX_MSR_PMON_CTR0_ADDR = 0x3FF8; constexpr auto BHS_UBOX_MSR_PMON_CTR1_ADDR = 0x3FF9; constexpr auto GRR_UCLK_FIXED_CTR_ADDR = 0x3F5F; constexpr auto GRR_UCLK_FIXED_CTL_ADDR = 0x3F5E; constexpr auto GRR_UBOX_MSR_PMON_BOX_CTL_ADDR = 0x3F50; constexpr auto GRR_UBOX_MSR_PMON_CTL0_ADDR = 0x3F52; constexpr auto GRR_UBOX_MSR_PMON_CTL1_ADDR = 0x3F53; constexpr auto GRR_UBOX_MSR_PMON_CTR0_ADDR = 0x3F58; constexpr auto GRR_UBOX_MSR_PMON_CTR1_ADDR = 0x3F59; constexpr auto GRR_M2IOSF_IIO_UNIT_CTL = 0x2900; constexpr auto GRR_M2IOSF_IIO_CTR0 = 0x2908; constexpr auto GRR_M2IOSF_IIO_CTL0 = 0x2902; constexpr auto GRR_M2IOSF_REG_STEP = 0x10; constexpr auto GRR_M2IOSF_NUM = 3; constexpr auto JKTIVT_UCLK_FIXED_CTR_ADDR = (0x0C09); constexpr auto JKTIVT_UCLK_FIXED_CTL_ADDR = (0x0C08); constexpr auto JKTIVT_UBOX_MSR_PMON_CTL0_ADDR = (0x0C10); constexpr auto JKTIVT_UBOX_MSR_PMON_CTL1_ADDR = (0x0C11); constexpr auto JKTIVT_UBOX_MSR_PMON_CTR0_ADDR = (0x0C16); constexpr auto JKTIVT_UBOX_MSR_PMON_CTR1_ADDR = (0x0C17); #define JKTIVT_PCU_MSR_PMON_CTR3_ADDR (0x0C39) #define JKTIVT_PCU_MSR_PMON_CTR2_ADDR (0x0C38) #define JKTIVT_PCU_MSR_PMON_CTR1_ADDR (0x0C37) #define JKTIVT_PCU_MSR_PMON_CTR0_ADDR (0x0C36) #define JKTIVT_PCU_MSR_PMON_BOX_FILTER_ADDR (0x0C34) #define JKTIVT_PCU_MSR_PMON_CTL3_ADDR (0x0C33) #define JKTIVT_PCU_MSR_PMON_CTL2_ADDR (0x0C32) #define JKTIVT_PCU_MSR_PMON_CTL1_ADDR (0x0C31) #define JKTIVT_PCU_MSR_PMON_CTL0_ADDR (0x0C30) #define JKTIVT_PCU_MSR_PMON_BOX_CTL_ADDR (0x0C24) #define HSX_PCU_MSR_PMON_CTR3_ADDR (0x071A) #define HSX_PCU_MSR_PMON_CTR2_ADDR (0x0719) #define HSX_PCU_MSR_PMON_CTR1_ADDR (0x0718) #define HSX_PCU_MSR_PMON_CTR0_ADDR (0x0717) #define HSX_PCU_MSR_PMON_BOX_FILTER_ADDR (0x0715) #define HSX_PCU_MSR_PMON_CTL3_ADDR (0x0714) #define HSX_PCU_MSR_PMON_CTL2_ADDR (0x0713) #define HSX_PCU_MSR_PMON_CTL1_ADDR (0x0712) #define HSX_PCU_MSR_PMON_CTL0_ADDR (0x0711) #define HSX_PCU_MSR_PMON_BOX_CTL_ADDR (0x0710) #define UNC_PMON_UNIT_CTL_RST_CONTROL (1 << 0) #define UNC_PMON_UNIT_CTL_RST_COUNTERS (1 << 1) #define UNC_PMON_UNIT_CTL_FRZ (1 << 8) #define UNC_PMON_UNIT_CTL_FRZ_EN (1 << 16) #define UNC_PMON_UNIT_CTL_RSV ((1 << 16) + (1 << 17)) #define SPR_UNC_PMON_UNIT_CTL_FRZ (1 << 0) #define SPR_UNC_PMON_UNIT_CTL_RST_CONTROL (1 << 8) #define SPR_UNC_PMON_UNIT_CTL_RST_COUNTERS (1 << 9) #define UNC_PMON_UNIT_CTL_VALID_BITS_MASK ((1 << 17) - 1) #define MC_CH_PCI_PMON_FIXED_CTL_RST (1 << 19) #define MC_CH_PCI_PMON_FIXED_CTL_EN (1 << 22) #define EDC_CH_PCI_PMON_FIXED_CTL_EN (1 << 0) #define MC_CH_PCI_PMON_CTL_EVENT(x) (x << 0) #define MC_CH_PCI_PMON_CTL_UMASK(x) (x << 8) #define MC_CH_PCI_PMON_CTL_RST (1 << 17) #define MC_CH_PCI_PMON_CTL_EDGE_DET (1 << 18) #define MC_CH_PCI_PMON_CTL_EN (1 << 22) #define MC_CH_PCI_PMON_CTL_INVERT (1 << 23) #define MC_CH_PCI_PMON_CTL_THRESH(x) (x << 24UL) #define Q_P_PCI_PMON_CTL_EVENT(x) (x << 0) #define Q_P_PCI_PMON_CTL_UMASK(x) (x << 8) #define Q_P_PCI_PMON_CTL_RST (1 << 17) #define Q_P_PCI_PMON_CTL_EDGE_DET (1 << 18) #define Q_P_PCI_PMON_CTL_EVENT_EXT (1 << 21) #define Q_P_PCI_PMON_CTL_EN (1 << 22) #define Q_P_PCI_PMON_CTL_INVERT (1 << 23) #define Q_P_PCI_PMON_CTL_THRESH(x) (x << 24UL) #define PCU_MSR_PMON_BOX_FILTER_BAND_0(x) (x << 0) #define PCU_MSR_PMON_BOX_FILTER_BAND_1(x) (x << 8) #define PCU_MSR_PMON_BOX_FILTER_BAND_2(x) (x << 16) #define PCU_MSR_PMON_BOX_FILTER_BAND_3(x) (x << 24) #define PCU_MSR_PMON_CTL_EVENT(x) (x << 0) #define PCU_MSR_PMON_CTL_OCC_SEL(x) (x << 14) #define PCU_MSR_PMON_CTL_RST (1 << 17) #define PCU_MSR_PMON_CTL_EDGE_DET (1 << 18) #define PCU_MSR_PMON_CTL_EXTRA_SEL (1 << 21) #define PCU_MSR_PMON_CTL_EN (1 << 22) #define PCU_MSR_PMON_CTL_INVERT (1 << 23) #define PCU_MSR_PMON_CTL_THRESH(x) (x << 24UL) #define PCU_MSR_PMON_CTL_OCC_INVERT (1UL << 30UL) #define PCU_MSR_PMON_CTL_OCC_EDGE_DET (1UL << 31UL) #define JKT_C0_MSR_PMON_CTR3 0x0D19 // CBo 0 PMON Counter 3 #define JKT_C0_MSR_PMON_CTR2 0x0D18 // CBo 0 PMON Counter 2 #define JKT_C0_MSR_PMON_CTR1 0x0D17 // CBo 0 PMON Counter 1 #define JKT_C0_MSR_PMON_CTR0 0x0D16 // CBo 0 PMON Counter 0 #define JKT_C0_MSR_PMON_BOX_FILTER 0x0D14 // CBo 0 PMON Filter #define JKT_C0_MSR_PMON_CTL3 0x0D13 // CBo 0 PMON Control for Counter 3 #define JKT_C0_MSR_PMON_CTL2 0x0D12 // CBo 0 PMON Control for Counter 2 #define JKT_C0_MSR_PMON_CTL1 0x0D11 // CBo 0 PMON Control for Counter 1 #define JKT_C0_MSR_PMON_CTL0 0x0D10 // CBo 0 PMON Control for Counter 0 #define JKT_C0_MSR_PMON_BOX_CTL 0x0D04 // CBo 0 PMON Box-Wide Control #define JKTIVT_CBO_MSR_STEP 0x0020 // CBo MSR Step #define IVT_C0_MSR_PMON_BOX_FILTER1 0x0D1A // CBo 0 PMON Filter 1 #define HSX_C0_MSR_PMON_CTR3 0x0E0B // CBo 0 PMON Counter 3 #define HSX_C0_MSR_PMON_CTR2 0x0E0A // CBo 0 PMON Counter 2 #define HSX_C0_MSR_PMON_CTR1 0x0E09 // CBo 0 PMON Counter 1 #define HSX_C0_MSR_PMON_CTR0 0x0E08 // CBo 0 PMON Counter 0 #define HSX_C0_MSR_PMON_BOX_FILTER1 0x0E06 // CBo 0 PMON Filter1 #define HSX_C0_MSR_PMON_BOX_FILTER 0x0E05 // CBo 0 PMON Filter0 #define HSX_C0_MSR_PMON_CTL3 0x0E04 // CBo 0 PMON Control for Counter 3 #define HSX_C0_MSR_PMON_CTL2 0x0E03 // CBo 0 PMON Control for Counter 2 #define HSX_C0_MSR_PMON_CTL1 0x0E02 // CBo 0 PMON Control for Counter 1 #define HSX_C0_MSR_PMON_CTL0 0x0E01 // CBo 0 PMON Control for Counter 0 #define HSX_C0_MSR_PMON_BOX_STATUS 0x0E07 // CBo 0 PMON Box-Wide Status #define HSX_C0_MSR_PMON_BOX_CTL 0x0E00 // CBo 0 PMON Box-Wide Control #define HSX_CBO_MSR_STEP 0x0010 // CBo MSR Step #define KNL_CHA_MSR_STEP 0x000C // CHA MSR Step #define KNL_CHA0_MSR_PMON_BOX_CTRL 0x0E00 // CHA 0 PMON Control #define KNL_CHA0_MSR_PMON_EVT_SEL0 0x0E01 // CHA 0 PMON Event Select for Counter 0 #define KNL_CHA0_MSR_PMON_EVT_SEL1 0x0E02 // CHA 0 PMON Event Select for Counter 1 #define KNL_CHA0_MSR_PMON_EVT_SEL2 0x0E03 // CHA 0 PMON Event Select for Counter 2 #define KNL_CHA0_MSR_PMON_EVT_SEL3 0x0E04 // CHA 0 PMON Event Select for Counter 3 #define KNL_CHA0_MSR_PMON_BOX_CTL 0x0E05 // PERF_UNIT_CTL_CHA_0 #define KNL_CHA0_MSR_PMON_BOX_CTL1 0x0E06 // PERF_UNIT_CTL_1_CHA_0 #define KNL_CHA0_MSR_PMON_BOX_STATUS 0x0E07 // CHA 0 PMON Status #define KNL_CHA0_MSR_PMON_CTR0 0x0E08 // CHA 0 PMON Counter 0 #define KNL_CHA0_MSR_PMON_CTR1 0x0E09 // CHA 0 PMON Counter 1 #define KNL_CHA0_MSR_PMON_CTR2 0x0E0A // CHA 0 PMON Counter 2 #define KNL_CHA0_MSR_PMON_CTR3 0x0E0B // CHA 0 PMON Counter 3 static const uint32 ICX_CHA_MSR_PMON_BOX_CTL[] = { 0x0E00, 0x0E0E, 0x0E1C, 0x0E2A, 0x0E38, 0x0E46, 0x0E54, 0x0E62, 0x0E70, 0x0E7E, 0x0E8C, 0x0E9A, 0x0EA8, 0x0EB6, 0x0EC4, 0x0ED2, 0x0EE0, 0x0EEE, 0x0F0A, 0x0F18, 0x0F26, 0x0F34, 0x0F42, 0x0F50, 0x0F5E, 0x0F6C, 0x0F7A, 0x0F88, 0x0F96, 0x0FA4, 0x0FB2, 0x0FC0, 0x0FCE, 0x0FDC, 0x0B60, 0x0B6E, 0x0B7C, 0x0B8A, 0x0B98, 0x0BA6, 0x0BB4, 0x0BC2 }; static const uint32 SNR_CHA_MSR_PMON_BOX_CTL[] = { 0x1C00, 0x1C10, 0x1C20, 0x1C30, 0x1C40, 0x1C50 }; #define SERVER_CHA_MSR_PMON_CTL0_OFFSET (1) /* #define SERVER_CHA_MSR_PMON_CTL1_OFFSET (2) #define SERVER_CHA_MSR_PMON_CTL2_OFFSET (3) #define SERVER_CHA_MSR_PMON_CTL3_OFFSET (4) */ #define SERVER_CHA_MSR_PMON_BOX_FILTER_OFFSET (5) #define SERVER_CHA_MSR_PMON_CTR0_OFFSET (8) /* #define SERVER_CHA_MSR_PMON_CTR1_OFFSET (9) #define SERVER_CHA_MSR_PMON_CTR2_OFFSET (10) #define SERVER_CHA_MSR_PMON_CTR3_OFFSET (11) */ constexpr auto SPR_CHA0_MSR_PMON_BOX_CTRL = 0x2000; constexpr auto SPR_CHA0_MSR_PMON_CTL0 = 0x2002; constexpr auto SPR_CHA0_MSR_PMON_CTR0 = 0x2008; constexpr auto SPR_CHA0_MSR_PMON_BOX_FILTER = 0x200E; constexpr auto SPR_CHA_MSR_STEP = 0x10; #define CBO_MSR_PMON_CTL_EVENT(x) (x << 0) #define CBO_MSR_PMON_CTL_UMASK(x) (x << 8) #define CBO_MSR_PMON_CTL_RST (1 << 17) #define CBO_MSR_PMON_CTL_EDGE_DET (1 << 18) #define CBO_MSR_PMON_CTL_TID_EN (1 << 19) #define CBO_MSR_PMON_CTL_EN (1 << 22) #define CBO_MSR_PMON_CTL_INVERT (1 << 23) #define CBO_MSR_PMON_CTL_THRESH(x) (x << 24UL) #define UNC_PMON_CTL_UMASK_EXT(x) (uint64(x) << 32ULL) #define UNC_PMON_CTL_EVENT(x) (x << 0) #define UNC_PMON_CTL_UMASK(x) (x << 8) #define JKT_CBO_MSR_PMON_BOX_FILTER_OPC(x) (x << 23UL) #define IVTHSX_CBO_MSR_PMON_BOX_FILTER1_OPC(x) (x << 20UL) #define BDX_CBO_MSR_PMON_BOX_GET_OPC0(x) ((x >> 20) & 0x3FF) #define BDX_CBO_MSR_PMON_BOX_GET_FLT(x) ((x >> 0x10) & 0x1) #define BDX_CBO_MSR_PMON_BOX_GET_TID(x) ((x >> 0x11) & 0x1) #define SKX_CHA_MSR_PMON_BOX_FILTER1_REM(x) (x << 0UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_LOC(x) (x << 1UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_NM(x) (x << 4UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_NOT_NM(x) (x << 5UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_OPC0(x) ((x) << 9UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_OPC1(x) ((x) << 19UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_NC(x) (x << 30UL) #define SKX_CHA_MSR_PMON_BOX_FILTER1_RSV(x) (x << 2UL) #define SKX_CHA_MSR_PMON_BOX_GET_OPC0(x) ((x >> 9) & 0x3FF) #define SKX_CHA_MSR_PMON_BOX_GET_NC(x) ((x >> 0x1e) & 0x1) #define SKX_CHA_TOR_INSERTS_UMASK_IRQ(x) (x << 0) #define SKX_CHA_TOR_INSERTS_UMASK_PRQ(x) (x << 2) #define SKX_CHA_TOR_INSERTS_UMASK_HIT(x) (x << 4) #define SKX_CHA_TOR_INSERTS_UMASK_MISS(x) (x << 5) /*ICX UmaskExt Filter*/ #define ICX_CHA_UMASK_EXT(x) (x << 32UL) #define SKX_IIO_CBDMA_UNIT_STATUS (0x0A47) #define SKX_IIO_CBDMA_UNIT_CTL (0x0A40) #define SKX_IIO_CBDMA_CTR0 (0x0A41) #define SKX_IIO_CBDMA_CLK (0x0A45) #define SKX_IIO_CBDMA_CTL0 (0x0A48) #define SKX_IIO_PM_REG_STEP (0x0020) #define ICX_IIO_CBDMA_UNIT_STATUS (0x0A57) #define ICX_IIO_CTL_REG_OFFSET (0x0008) #define ICX_IIO_CTR_REG_OFFSET (0x0001) /* * M2IOSF MSRs in order: * M2IOSF0 - PCIe0 stack * M2IOSF1 - PCIe1 stack * M2IOSF2 - MCP stack * M2IOSF3 - PCIe2 stack * M2IOSF4 - PCIe3 stack * M2IOSF5 - CBDMA/DMI stack */ static const uint32 ICX_IIO_UNIT_CTL[] = { 0x0A50, 0x0A70, 0x0A90, 0x0AE0, 0x0B00, 0x0B20 }; static const uint32 GRR_IRP_UNIT_CTL[] = { 0x2A00, 0x2A10, 0x2A20 }; #define GRR_IRP_CTL_REG_OFFSET (0x0002) #define GRR_IRP_CTR_REG_OFFSET (0x0008) static const uint32 BHS_IRP_UNIT_CTL[] = { 0x2A00, 0x2A10, 0x2A20, 0x2A30, 0x2A40, 0x2A50, 0x2A60, 0x2A70, 0x2A80, 0x2A90, 0x2AA0, 0x2AB0, 0x2AC0, 0x2AD0, 0x2AE0, 0x2AF0 }; #define BHS_IRP_CTL_REG_OFFSET (0x0002) #define BHS_IRP_CTR_REG_OFFSET (0x0008) static const uint32 SPR_IRP_UNIT_CTL[] = { 0x3400, 0x3410, 0x3420, 0x3430, 0x3440, 0x3450, 0x3460, 0x3470, 0x3480, 0x3490, 0x34A0, 0x34B0 }; #define SPR_IRP_CTL_REG_OFFSET (0x0002) #define SPR_IRP_CTR_REG_OFFSET (0x0008) static const uint32 ICX_IRP_UNIT_CTL[] = { 0x0A4A, 0x0A6A, 0x0A8A, 0x0ADA, 0x0AFA, 0x0B1A }; #define ICX_IRP_CTL_REG_OFFSET (0x0003) #define ICX_IRP_CTR_REG_OFFSET (0x0001) static const uint32 SNR_IRP_UNIT_CTL[] = { 0x1EA0, 0x1EB0, 0x1EC0, 0x1ED0, 0x1EE0 }; #define SNR_IRP_CTL_REG_OFFSET (0x0008) #define SNR_IRP_CTR_REG_OFFSET (0x0001) static const uint32 SKX_IRP_UNIT_CTL[] = { 0x0A58, 0x0A78, 0x0A98, 0x0AB8, 0x0AD8, 0x0AF8 }; #define SKX_IRP_CTL_REG_OFFSET (0x0003) #define SKX_IRP_CTR_REG_OFFSET (0x0001) #define SNR_IIO_CBDMA_UNIT_STATUS (0x1E07) #define SNR_IIO_CBDMA_UNIT_CTL (0x1E00) #define SNR_IIO_CBDMA_CTR0 (0x1E01) #define SNR_IIO_CBDMA_CTL0 (0x1E08) #define SNR_IIO_PM_REG_STEP (0x0010) constexpr auto SPR_M2IOSF_IIO_UNIT_CTL = 0x3000; constexpr auto SPR_M2IOSF_IIO_CTR0 = 0x3008; constexpr auto SPR_M2IOSF_IIO_CTL0 = 0x3002; constexpr auto SPR_M2IOSF_REG_STEP = 0x10; constexpr auto SPR_M2IOSF_NUM = 12; constexpr auto BHS_M2IOSF_IIO_UNIT_CTL = 0x2900; constexpr auto BHS_M2IOSF_IIO_CTR0 = 0x2908; constexpr auto BHS_M2IOSF_IIO_CTL0 = 0x2902; constexpr auto BHS_M2IOSF_REG_STEP = 0x10; constexpr auto BHS_M2IOSF_NUM = 16; constexpr auto CXL_PMON_SIZE = 0x1000; #define IIO_MSR_PMON_CTL_EVENT(x) ((x) << 0) #define IIO_MSR_PMON_CTL_UMASK(x) ((x) << 8) #define IIO_MSR_PMON_CTL_RST (1 << 17) #define IIO_MSR_PMON_CTL_EDGE_DET (1 << 18) #define IIO_MSR_PMON_CTL_OV_EN (1 << 20) #define IIO_MSR_PMON_CTL_EN (1 << 22) #define IIO_MSR_PMON_CTL_INVERT (1 << 23) #define IIO_MSR_PMON_CTL_THRESH(x) ((x) << 24ULL) #define IIO_MSR_PMON_CTL_CH_MASK(x) ((x) << 36ULL) #define IIO_MSR_PMON_CTL_FC_MASK(x) ((x) << 44ULL) #define ICX_IIO_MSR_PMON_CTL_EVENT(x) ((x) << 0) #define ICX_IIO_MSR_PMON_CTL_UMASK(x) ((x) << 8) #define ICX_IIO_MSR_PMON_CTL_RST (1 << 17) #define ICX_IIO_MSR_PMON_CTL_EDGE_DET (1 << 18) #define ICX_IIO_MSR_PMON_CTL_OV_EN (1 << 20) #define ICX_IIO_MSR_PMON_CTL_EN (1 << 22) #define ICX_IIO_MSR_PMON_CTL_INVERT (1 << 23) #define ICX_IIO_MSR_PMON_CTL_THRESH(x) ((x) << 24ULL) #define ICX_IIO_MSR_PMON_CTL_CH_MASK(x) ((x) << 36ULL) #define ICX_IIO_MSR_PMON_CTL_FC_MASK(x) ((x) << 48ULL) #define M2M_PCI_PMON_CTL_EVENT(x) ((x) << 0) #define M2M_PCI_PMON_CTL_UMASK(x) ((x) << 8) #define M2M_PCI_PMON_CTL_RST (1 << 17) #define M2M_PCI_PMON_CTL_EDGE_DET (1 << 18) #define M2M_PCI_PMON_CTL_OV_EN (1 << 20) #define M2M_PCI_PMON_CTL_EN (1 << 22) #define M2M_PCI_PMON_CTL_INVERT (1 << 23) #define M2M_PCI_PMON_CTL_THRESH(x) ((x) << 24ULL) #define HA_PCI_PMON_CTL_EVENT(x) ((x) << 0) #define HA_PCI_PMON_CTL_UMASK(x) ((x) << 8) #define HA_PCI_PMON_CTL_RST (1 << 17) #define HA_PCI_PMON_CTL_EDGE_DET (1 << 18) #define HA_PCI_PMON_CTL_OV_EN (1 << 20) #define HA_PCI_PMON_CTL_EN (1 << 22) #define HA_PCI_PMON_CTL_INVERT (1 << 23) #define HA_PCI_PMON_CTL_THRESH(x) ((x) << 24ULL) #define UCLK_FIXED_CTL_OV_EN (1 << 20) #define UCLK_FIXED_CTL_EN (1 << 22) /* \brief IIO Performance Monitoring Control Register format IIOn_MSR_PMON_CTL{3-0} Register- Field Definitions */ struct IIOPMUCNTCTLRegister { union { struct { uint64 event_select : 8; uint64 umask : 8; uint64 reserved1 : 1; uint64 reset : 1; uint64 edge_det : 1; uint64 ignored : 1; uint64 overflow_enable : 1; uint64 reserved2 : 1; uint64 enable : 1; uint64 invert : 1; uint64 thresh : 12; uint64 ch_mask : 8; uint64 fc_mask : 3; uint64 reservedX : 17; } fields; uint64 value; }; IIOPMUCNTCTLRegister() : value(0) { } IIOPMUCNTCTLRegister(const uint64 v) : value(v) { } operator uint64() { return value; } }; struct ICX_IIOPMUCNTCTLRegister { union { struct { uint64 event_select : 8; uint64 umask : 8; uint64 reserved1 : 1; uint64 reset : 1; uint64 edge_det : 1; uint64 ignored : 1; uint64 overflow_enable : 1; uint64 reserved2 : 1; uint64 enable : 1; uint64 invert : 1; uint64 thresh : 12; uint64 ch_mask : 12; uint64 fc_mask : 3; uint64 reservedX : 13; } fields; uint64 value; }; ICX_IIOPMUCNTCTLRegister() : value(0) { } }; constexpr auto MSR_PACKAGE_THERM_STATUS = 0x01B1; constexpr auto MSR_IA32_THERM_STATUS = 0x019C; #ifndef KERNEL constexpr auto PCM_INVALID_THERMAL_HEADROOM = (std::numeric_limits::min)(); #endif constexpr auto MSR_IA32_BIOS_SIGN_ID = 0x8B; constexpr auto MSR_DRAM_ENERGY_STATUS = 0x0619; constexpr auto MSR_PP0_ENERGY_STATUS = 0x639; constexpr auto MSR_PP1_ENERGY_STATUS = 0x641; constexpr auto MSR_PKG_C2_RESIDENCY = 0x60D; constexpr auto MSR_PKG_C3_RESIDENCY = 0x3F8; constexpr auto MSR_PKG_C6_RESIDENCY = 0x3F9; constexpr auto MSR_PKG_C7_RESIDENCY = 0x3FA; constexpr auto MSR_CORE_C3_RESIDENCY = 0x3FC; constexpr auto MSR_CORE_C6_RESIDENCY = 0x3FD; constexpr auto MSR_CORE_C7_RESIDENCY = 0x3FE; constexpr auto MSR_PERF_GLOBAL_INUSE = 0x392; constexpr auto MSR_IA32_SPEC_CTRL = 0x48; constexpr auto MSR_IA32_ARCH_CAPABILITIES = 0x10A; constexpr auto MSR_TSX_FORCE_ABORT = 0x10f; constexpr auto MSR_PERF_CAPABILITIES = 0x345; // data structure for converting two uint32s <-> uin64 union cvt_ds { #ifndef _MSC_VER typedef uint64 UINT64; typedef uint32 DWORD; #endif UINT64 ui64; struct { DWORD low; DWORD high; } ui32; }; #ifndef KERNEL struct MCFGRecord { unsigned long long baseAddress; unsigned short PCISegmentGroupNumber; unsigned char startBusNumber; unsigned char endBusNumber; char reserved[4]; MCFGRecord() : baseAddress(0), PCISegmentGroupNumber(0), startBusNumber(0), endBusNumber(0) { std::fill(reserved, reserved + 4, 0); } void print() { std::cout << "BaseAddress=" << (std::hex) << "0x" << baseAddress << " PCISegmentGroupNumber=0x" << PCISegmentGroupNumber << " startBusNumber=0x" << (unsigned)startBusNumber << " endBusNumber=0x" << (unsigned)endBusNumber << "\n" << std::dec; } }; struct MCFGHeader { char signature[4]; unsigned length; unsigned char revision; unsigned char checksum; char OEMID[6]; char OEMTableID[8]; unsigned OEMRevision; unsigned creatorID; unsigned creatorRevision; char reserved[8]; unsigned nrecords() const { return (length - sizeof(MCFGHeader)) / sizeof(MCFGRecord); } void print() { std::cout << "Header: length=" << length << " nrecords=" << nrecords() << "\n"; } }; #endif // #ifndef KERNEL inline uint32 build_bit_ui(uint32 beg, uint32 end) { assert(end <= 31); uint32 myll = 0; if (end > 31) { end = 31; } if (beg > 31) { return 0; } if (end == 31) { myll = (uint32)(-1); } else { myll = (1 << (end + 1)) - 1; } myll = myll >> beg; return myll; } inline uint32 extract_bits_32(uint32 myin, uint32 beg, uint32 end) { uint32 myll = 0; uint32 beg1, end1; // Let the user reverse the order of beg & end. if (beg <= end) { beg1 = beg; end1 = end; } else { beg1 = end; end1 = beg; } myll = myin >> beg1; myll = myll & build_bit_ui(beg1, end1); return myll; } inline uint64 build_bit(uint32 beg, uint32 end) { uint64 myll = 0; if (end > 63) { end = 63; } if (end == 63) { myll = static_cast(-1); } else { myll = (1LL << (end + 1)) - 1; } myll = myll >> beg; return myll; } inline uint64 extract_bits(uint64 myin, uint32 beg, uint32 end) { uint64 myll = 0; uint32 beg1, end1; // Let the user reverse the order of beg & end. if (beg <= end) { beg1 = beg; end1 = end; } else { beg1 = end; end1 = beg; } myll = myin >> beg1; myll = myll & build_bit(beg1, end1); return myll; } inline uint64 extract_bits_64(uint64 myin, uint32 beg, uint32 end) { return extract_bits(myin, beg, end); } union PCM_CPUID_INFO { int array[4]; struct { unsigned int eax, ebx, ecx, edx; } reg; }; inline void pcm_cpuid(int leaf, PCM_CPUID_INFO& info) { #ifdef _MSC_VER // version for Windows __cpuid(info.array, leaf); #else __asm__ __volatile__("cpuid" : \ "=a" (info.reg.eax), "=b" (info.reg.ebx), "=c" (info.reg.ecx), "=d" (info.reg.edx) : "a" (leaf)); #endif } /* Adding the new version of cpuid with leaf and subleaf as an input */ inline void pcm_cpuid(const unsigned leaf, const unsigned subleaf, PCM_CPUID_INFO & info) { #ifdef _MSC_VER __cpuidex(info.array, leaf, subleaf); #else __asm__ __volatile__ ("cpuid" : \ "=a" (info.reg.eax), "=b" (info.reg.ebx), "=c" (info.reg.ecx), "=d" (info.reg.edx) : "a" (leaf), "c" (subleaf)); #endif } //IDX accel device/func number(PCIe). //The device/function number from SPR register guide. #define SPR_IDX_IAA_REGISTER_DEV_ADDR (2) #define SPR_IDX_IAA_REGISTER_FUNC_ADDR (0) #define SPR_IDX_DSA_REGISTER_DEV_ADDR (1) #define SPR_IDX_DSA_REGISTER_FUNC_ADDR (0) #define SPR_IDX_QAT_REGISTER_DEV_ADDR (0) #define SPR_IDX_QAT_REGISTER_FUNC_ADDR (0) //IDX accel perfmon register offset //The offset of register from DSA external architecture spec(intel-data-streaming-accelerator-preliminary-architecture-specification). #define SPR_IDX_ACCEL_PCICMD_OFFSET (0x4) #define SPR_IDX_ACCEL_BAR0_OFFSET (0x10) #define SPR_IDX_ACCEL_BAR0_SIZE (0x10000) #define SPR_IDX_ACCEL_TABLE_OFFSET (0x60) #define SPR_IDX_ACCEL_PMON_BASE_OFFSET (0x68) #define SPR_IDX_ACCEL_PMON_BASE_MASK (0xFFFF) #define SPR_IDX_ACCEL_PMON_BASE_RATIO (0x100) #define SPR_IDX_ACCEL_PMCSR_OFFSET (0x94) #define SPR_IDX_PMON_RESET_CTL_OFFSET (0x10) #define SPR_IDX_PMON_FREEZE_CTL_OFFSET (0x20) #define SPR_IDX_PMON_CTL_OFFSET(x) (0x100 + ((x)*8)) #define SPR_IDX_PMON_CTR_OFFSET(x) (0x200 + ((x)*8)) #define SPR_IDX_PMON_FILTER_WQ_OFFSET(x) (0x300 + ((x)*32)) #define SPR_IDX_PMON_FILTER_TC_OFFSET(x) (0x304 + ((x)*32)) #define SPR_IDX_PMON_FILTER_PGSZ_OFFSET(x) (0x308 + ((x)*32)) #define SPR_IDX_PMON_FILTER_XFERSZ_OFFSET(x) (0x30C + ((x)*32)) #define SPR_IDX_PMON_FILTER_ENG_OFFSET(x) (0x310 + ((x)*32)) //MSM device/func number and register offset from SPR register guide. constexpr auto SPR_MSM_DEV_ID = 0x09a6; constexpr auto SPR_MSM_DEV_ADDR = 0x03; constexpr auto SPR_MSM_FUNC_ADDR = 0x00; constexpr auto SPR_MSM_REG_CPUBUSNO_VALID_OFFSET = 0x1a0; constexpr auto SPR_MSM_REG_CPUBUSNO0_OFFSET = 0x190; constexpr auto SPR_MSM_REG_CPUBUSNO4_OFFSET = 0x1c0; constexpr auto SPR_MSM_CPUBUSNO_MAX = 32; //SAD register offset from SPR register guide. constexpr auto SPR_SAD_REG_CTL_CFG_OFFSET = 0x3F4; } // namespace pcm #endif pcm-202502/src/uncore_pmu_discovery.cpp000066400000000000000000000050471475730356400201470ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2021-2022, Intel Corporation #include "uncore_pmu_discovery.h" #include "pci.h" #include "mmio.h" #include "iostream" #include "utils.h" namespace pcm { UncorePMUDiscovery::UncorePMUDiscovery() { if (safe_getenv("PCM_NO_UNCORE_PMU_DISCOVERY") == std::string("1")) { return; } auto processTables = [this](const uint64 bar, const VSEC &) { constexpr size_t UncoreDiscoverySize = 3UL; union UncoreGlobalDiscovery { GlobalPMU pmu; uint64 table[UncoreDiscoverySize]; }; UncoreGlobalDiscovery global; mmio_memcpy(global.table, bar, UncoreDiscoverySize * sizeof(uint64), true); globalPMUs.push_back(global.pmu); union UncoreUnitDiscovery { BoxPMU pmu; uint64 table[UncoreDiscoverySize]; }; UncoreUnitDiscovery unit; const auto step = global.pmu.stride * 8; BoxPMUMap boxPMUMap; for (size_t u = 0; u < global.pmu.maxUnits; ++u) { mmio_memcpy(unit.table, bar + (u+1) * step, UncoreDiscoverySize * sizeof(uint64), true); if (unit.table[0] == 0 && unit.table[1] == 0) { // invalid entry continue; } // unit.pmu.print(); boxPMUMap[unit.pmu.boxType].push_back(unit.pmu); } boxPMUs.push_back(boxPMUMap); }; try { processDVSEC([](const VSEC & vsec) { return vsec.fields.cap_id == 0x23 // UNCORE_EXT_CAP_ID_DISCOVERY && vsec.fields.entryID == 1; // UNCORE_DISCOVERY_DVSEC_ID_PMON }, processTables); } catch (...) { std::cerr << "WARNING: enumeration of devices in UncorePMUDiscovery failed\n"; } if (safe_getenv("PCM_PRINT_UNCORE_PMU_DISCOVERY") == std::string("1")) { for (size_t s = 0; s < boxPMUs.size(); ++s) { std::cout << "Socket " << s << " global PMU:\n"; std::cout << " "; globalPMUs[s].print(); std::cout << "Socket " << s << " unit PMUs:\n"; for (const auto & pmuType : boxPMUs[s]) { const auto n = pmuType.second.size(); std::cout << " PMU type " << pmuType.first << " (" << n << " boxes)"<< "\n"; for (size_t i = 0; i < n ; ++i) { std::cout << " "; pmuType.second[i].print(); } } } } } } // namespace pcm pcm-202502/src/uncore_pmu_discovery.h000066400000000000000000000146331475730356400176150ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2021-2022, Intel Corporation #pragma once #include #include #include #include "types.h" namespace pcm { constexpr auto SPR_PCU_BOX_TYPE = 4U; constexpr auto SPR_IMC_BOX_TYPE = 6U; constexpr auto SPR_UPILL_BOX_TYPE = 8U; constexpr auto SPR_MDF_BOX_TYPE = 11U; constexpr auto SPR_CXLCM_BOX_TYPE = 12U; constexpr auto SPR_CXLDP_BOX_TYPE = 13U; constexpr auto BHS_MDF_BOX_TYPE = 20U; constexpr auto BHS_PCIE_GEN5x16_TYPE = 21U; constexpr auto BHS_PCIE_GEN5x8_TYPE = 22U; class UncorePMUDiscovery { public: enum accessTypeEnum { MSR = 0, MMIO = 1, PCICFG = 2, unknownAccessType = 255 }; static const char * accessTypeStr(const uint64 t) { switch (t) { case MSR: return "MSR"; case MMIO: return "MMIO"; case PCICFG: return "PCICFG"; } return "unknown"; }; union PCICFGAddress { uint64 raw; struct { uint64 offset:12; uint64 function:3; uint64 device:5; uint64 bus:8; } fields; std::string getStr() const { std::ostringstream out(std::ostringstream::out); out << std::hex << fields.bus << ":" << fields.device << "." << fields.function << "@" << fields.offset; out << std::dec; return out.str(); } }; static void printHelper(const accessTypeEnum accessType, const uint64 addr) { if (accessType == PCICFG) { PCICFGAddress Addr; Addr.raw = addr; std::cout << " (" << Addr.getStr() << ")"; } else { std::cout << " (-)"; } std::cout << " with access type " << std::dec << accessTypeStr(accessType); } protected: struct GlobalPMU { uint64 type:8; uint64 stride:8; uint64 maxUnits:10; uint64 __reserved1:36; uint64 accessType:2; uint64 globalCtrlAddr; uint64 statusOffset:8; uint64 numStatus:16; uint64 __reserved2:40; void print() const { std::cout << "global PMU " << " of type " << std::dec << type << " globalCtrl: 0x" << std::hex << globalCtrlAddr; UncorePMUDiscovery::printHelper((accessTypeEnum)accessType, globalCtrlAddr); std::cout << " stride: " << std::dec << stride << "\n"; } }; struct BoxPMU { uint64 numRegs : 8; uint64 ctrlOffset : 8; uint64 bitWidth : 8; uint64 ctrOffset : 8; uint64 statusOffset : 8; uint64 __reserved1 : 22; uint64 accessType : 2; uint64 boxCtrlAddr; uint64 boxType : 16; uint64 boxID : 16; uint64 __reserved2 : 32; void print() const { std::cout << "unit PMU " << " of type " << std::dec << boxType << " ID " << boxID << " box ctrl: 0x" << std::hex << boxCtrlAddr; UncorePMUDiscovery::printHelper((accessTypeEnum)accessType, boxCtrlAddr); std::cout << " width " << bitWidth << " numRegs " << numRegs << " ctrlOffset " << ctrlOffset << " ctrOffset " << ctrOffset << "\n"; } }; typedef std::vector BoxPMUs; typedef std::unordered_map BoxPMUMap; // boxType -> BoxPMUs std::vector boxPMUs; std::vector globalPMUs; bool validBox(const size_t boxType, const size_t socket, const size_t pos) { return socket < boxPMUs.size() && pos < boxPMUs[socket][boxType].size(); } size_t registerStep(const size_t boxType, const size_t socket, const size_t pos) { const auto width = boxPMUs[socket][boxType][pos].bitWidth; switch (boxPMUs[socket][boxType][pos].accessType) { case MSR: if (width <= 64) { return 1; } break; case PCICFG: case MMIO: if (width <= 8) { return 1; } else if (width <= 16) { return 2; } else if (width <= 32) { return 4; } else if (width <= 64) { return 8; } break; } return 0; } public: UncorePMUDiscovery(); size_t getNumBoxes(const size_t boxType, const size_t socket) { if (socket < boxPMUs.size()) { return boxPMUs[socket][boxType].size(); } return 0; } uint64 getBoxCtlAddr(const size_t boxType, const size_t socket, const size_t pos) { if (validBox(boxType, socket, pos)) { return boxPMUs[socket][boxType][pos].boxCtrlAddr; } return 0; } uint64 getBoxCtlAddr(const size_t boxType, const size_t socket, const size_t pos, const size_t c) { if (validBox(boxType, socket, pos) && c < boxPMUs[socket][boxType][pos].numRegs) { const size_t step = (boxType == SPR_IMC_BOX_TYPE) ? 4 : registerStep(boxType, socket, pos); return boxPMUs[socket][boxType][pos].boxCtrlAddr + boxPMUs[socket][boxType][pos].ctrlOffset + c * step; } return 0; } uint64 getBoxCtrAddr(const size_t boxType, const size_t socket, const size_t pos, const size_t c) { if (validBox(boxType, socket, pos) && c < boxPMUs[socket][boxType][pos].numRegs) { return boxPMUs[socket][boxType][pos].boxCtrlAddr + boxPMUs[socket][boxType][pos].ctrOffset + c * registerStep(boxType, socket, pos); } return 0; } accessTypeEnum getBoxAccessType(const size_t boxType, const size_t socket, const size_t pos) { if (validBox(boxType, socket, pos)) { return static_cast(boxPMUs[socket][boxType][pos].accessType); } return unknownAccessType; } uint64 getBoxNumRegs(const size_t boxType, const size_t socket, const size_t pos) { if (validBox(boxType, socket, pos)) { return boxPMUs[socket][boxType][pos].numRegs; } return 0; } }; } // namespace pcm pcm-202502/src/utils.cpp000066400000000000000000001334671475730356400150540ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Andrey Semin and many others #include #include #include #include #include #ifdef _MSC_VER #include #include #include #include #include #include #else #include // for waitpid() #include // for ::sleep #endif #include "utils.h" #include "cpucounters.h" #include #ifndef _MSC_VER #include extern char ** environ; #endif #ifdef __linux__ #include #endif #include "debug.h" namespace pcm { bool startsWithPCM(const StringType& varName) { const StringType prefix = PCM_STRING("PCM_"); return varName.compare(0, prefix.size(), prefix) == 0; } bool isInKeepList(const StringType& varName, const std::vector& keepList) { for (const auto& keepVar : keepList) { if (varName == keepVar) { return true; } } return false; } void setDefaultDebugLevel() { auto strDebugLevel = pcm::safe_getenv("PCM_DEBUG_LEVEL"); if (strDebugLevel.empty() == false) { auto intDebugLevel = std::stoi(strDebugLevel); debug::dyn_debug_level(intDebugLevel); DBG(0, "Debug level set to ", intDebugLevel); } } void getMCFGRecords(std::vector& mcfg) { #ifdef __linux__ mcfg = PciHandleMM::getMCFGRecords(); #else MCFGRecord segment; segment.startBusNumber = 0; segment.endBusNumber = 0xff; auto maxSegments = 1; #if defined (_MSC_VER) || defined(__FreeBSD__) || defined(__DragonFly__) switch (PCM::getCPUFamilyModelFromCPUID()) { case PCM::GNR: maxSegments = 4; break; } #endif for (segment.PCISegmentGroupNumber = 0; segment.PCISegmentGroupNumber < maxSegments; ++(segment.PCISegmentGroupNumber)) { mcfg.push_back(segment); } #endif } #if defined(_MSC_VER) void eraseEnvironmentVariables(const std::vector& keepList) { // Get a snapshot of the current environment block LPWCH envBlock = GetEnvironmentStrings(); if (!envBlock) { std::cerr << "Error getting environment strings." << std::endl; return; } // Iterate over the environment block for (LPWCH var = envBlock; *var != 0; var += std::wcslen(var) + 1) { std::wstring varName(var); size_t pos = varName.find('='); if (pos != std::string::npos) { varName = varName.substr(0, pos); if (!startsWithPCM(varName) && !isInKeepList(varName, keepList)) { SetEnvironmentVariable(varName.c_str(), NULL); } } } // Free the environment block FreeEnvironmentStrings(envBlock); } #else void eraseEnvironmentVariables(const std::vector& keepList) { std::vector varsToDelete; // Collect all the variables that need to be deleted for (char **env = environ; *env != nullptr; ++env) { std::string envEntry(*env); size_t pos = envEntry.find('='); if (pos != std::string::npos) { std::string varName = envEntry.substr(0, pos); if (!startsWithPCM(varName) && !isInKeepList(varName, keepList)) { varsToDelete.push_back(varName); } } } // Delete the collected variables for (const auto& varName : varsToDelete) { unsetenv(varName.c_str()); } } #endif void (*post_cleanup_callback)(void) = NULL; //! \brief handler of exit() call void exit_cleanup(void) { std::cout << std::flush; restore_signal_handlers(); // this replaces same call in cleanup() from util.h if (PCM::isInitialized()) PCM::getInstance()->cleanup(); // this replaces same call in cleanup() from util.h //TODO: delete other shared objects.... if any. if(post_cleanup_callback != NULL) { post_cleanup_callback(); } } #ifdef _MSC_VER bool colorEnabled = false; #else bool colorEnabled = true; #endif void setColorEnabled(bool value) { colorEnabled = value; } const char * setColor (const char * colorStr) { return colorEnabled ? colorStr : ""; } template constexpr auto make_array(N&&... args) -> std::array { return {std::forward(args)...}; } constexpr auto colorTable{make_array( ASCII_GREEN, ASCII_YELLOW, ASCII_MAGENTA, ASCII_CYAN, ASCII_BRIGHT_GREEN, ASCII_BRIGHT_YELLOW, ASCII_BRIGHT_BLUE, ASCII_BRIGHT_MAGENTA, ASCII_BRIGHT_CYAN, ASCII_BRIGHT_WHITE )}; size_t currentColor = 0; const char * setNextColor() { const auto result = setColor(colorTable[currentColor++]); if (currentColor == colorTable.size()) { currentColor = 0; } return result; } const char * resetColor() { currentColor = 0; return setColor(ASCII_RESET_COLOR); } void print_cpu_details() { const auto m = PCM::getInstance(); std::cerr << "\nDetected " << m->getCPUBrandString() << " \"Intel(r) microarchitecture codename " << m->getUArchCodename() << "\" stepping " << m->getCPUStepping(); const auto ucode_level = m->getCPUMicrocodeLevel(); if (ucode_level >= 0) { std::cerr << " microcode level 0x" << std::hex << ucode_level << std::dec; } std::cerr << "\n"; } #ifdef __linux__ std::vector findPathsFromPattern(const char* pattern) { std::vector result; glob_t glob_result; memset(&glob_result, 0, sizeof(glob_result)); if (glob(pattern, GLOB_TILDE, nullptr, &glob_result) == 0) { for (size_t i = 0; i < glob_result.gl_pathc; ++i) { result.push_back(glob_result.gl_pathv[i]); } } globfree(&glob_result); return result; }; #endif #ifdef _MSC_VER ThreadGroupTempAffinity::ThreadGroupTempAffinity(uint32 core_id, bool checkStatus, const bool restore_) : restore(restore_) { GROUP_AFFINITY NewGroupAffinity; SecureZeroMemory(&NewGroupAffinity, sizeof(GROUP_AFFINITY)); SecureZeroMemory(&PreviousGroupAffinity, sizeof(GROUP_AFFINITY)); DWORD currentGroupSize = 0; while ((DWORD)core_id >= (currentGroupSize = GetActiveProcessorCount(NewGroupAffinity.Group))) { if (currentGroupSize == 0) { std::cerr << "ERROR: GetActiveProcessorCount for core " << core_id << " failed with error " << GetLastError() << "\n"; throw std::exception(); } core_id -= (uint32)currentGroupSize; ++NewGroupAffinity.Group; } NewGroupAffinity.Mask = 1ULL << core_id; if (GetThreadGroupAffinity(GetCurrentThread(), &PreviousGroupAffinity) && (std::memcmp(&NewGroupAffinity, &PreviousGroupAffinity, sizeof(GROUP_AFFINITY)) == 0)) { restore = false; return; } const auto res = SetThreadGroupAffinity(GetCurrentThread(), &NewGroupAffinity, &PreviousGroupAffinity); if (res == FALSE && checkStatus) { std::cerr << "ERROR: SetThreadGroupAffinity for core " << core_id << " failed with error " << GetLastError() << "\n"; throw std::exception(); } } ThreadGroupTempAffinity::~ThreadGroupTempAffinity() { if (restore) SetThreadGroupAffinity(GetCurrentThread(), &PreviousGroupAffinity, NULL); } LONG unhandled_exception_handler(LPEXCEPTION_POINTERS p) { std::cerr << "DEBUG: Unhandled Exception event\n"; exit(EXIT_FAILURE); } /** * \brief version of interrupt handled for Windows */ BOOL sigINT_handler(DWORD fdwCtrlType) { // output for DEBUG only std::cerr << "DEBUG: caught signal to interrupt: "; switch (fdwCtrlType) { // Handle the CTRL-C signal. case CTRL_C_EVENT: std::cerr << "Ctrl-C event\n"; break; // CTRL-CLOSE: confirm that the user wants to exit. case CTRL_CLOSE_EVENT: std::cerr << "Ctrl-Close event\n"; break; // Pass other signals to the next handler. case CTRL_BREAK_EVENT: std::cerr << "Ctrl-Break event\n"; break; case CTRL_LOGOFF_EVENT: std::cerr << "Ctrl-Logoff event\n"; break; case CTRL_SHUTDOWN_EVENT: std::cerr << "Ctrl-Shutdown event\n"; break; default: std::cerr << "Unknown event\n"; break; } // TODO: dump summary, if needed // in case PCM is blocked just return and summary will be dumped in // calling function, if needed if (PCM::isInitialized() && PCM::getInstance()->isBlocked()) { return FALSE; } else { exit_cleanup(); _exit(EXIT_SUCCESS); return FALSE; // to prevent Warning } } /** * \brief started in a separate thread and blocks waiting for child application to exit. * After child app exits: -> print Child's termination status and terminates PCM */ void waitForChild(void * proc_id) { intptr_t procHandle = (intptr_t)proc_id; int termstat; _cwait(&termstat, procHandle, _WAIT_CHILD); std::cerr << "Program exited with status " << termstat << "\n"; exit(EXIT_SUCCESS); } #else /** * \brief handles signals that lead to termination of the program * such as SIGINT, SIGQUIT, SIGABRT, SIGSEGV, SIGTERM, SIGCHLD * this function specifically works when the client application launched * by pcm -- terminates */ void sigINT_handler(int signum) { // output for DEBUG only std::cerr << "DEBUG: caught signal to interrupt (" << strsignal(signum) << ").\n"; // TODO: dump summary, if needed // in case PCM is blocked just return and summary will be dumped in // calling function, if needed if (PCM::isInitialized() && PCM::getInstance()->isBlocked()) { return; } else { exit_cleanup(); if (signum == SIGABRT || signum == SIGSEGV) { _exit(EXIT_FAILURE); } else { _exit(EXIT_SUCCESS); } } } constexpr auto BACKTRACE_MAX_STACK_FRAME = 30; void printBacktrace() { void* backtrace_buffer[BACKTRACE_MAX_STACK_FRAME] = { 0 }; char** backtrace_strings = NULL; size_t backtrace_size = 0; backtrace_size = backtrace(backtrace_buffer, BACKTRACE_MAX_STACK_FRAME); backtrace_strings = backtrace_symbols(backtrace_buffer, backtrace_size); if (backtrace_strings == NULL) { std::cerr << "Debug: backtrace empty. \n"; } else { std::cerr << "Debug: backtrace dump(" << backtrace_size << " stack frames).\n"; for (size_t i = 0; i < backtrace_size; i++) { std::cerr << backtrace_strings[i] << "\n"; } freeAndNullify(backtrace_strings); } } /** * \brief handles SIGSEGV signals that lead to termination of the program * this function specifically works when the client application launched * by pcm -- terminates */ void sigSEGV_handler(int signum) { printBacktrace(); sigINT_handler(signum); } /** * \brief handles signals that lead to restart the application * such as SIGHUP. * for example to re-read environment variables controlling PCM execution */ void sigHUP_handler(int /*signum*/) { // output for DEBUG only std::cerr << "DEBUG: caught signal to hangup. Reloading configuration and continue...\n"; // TODO: restart; so far do nothing return; // continue program execution } /** * \brief handles signals that lead to update of configuration * such as SIGUSR1 and SIGUSR2. * for the future extensions */ void sigUSR_handler(int /*signum*/) { std::cerr << "DEBUG: caught USR signal. Continue.\n"; // TODO: reload configurationa, reset accumulative counters; return; } /** * \brief handles signals that lead to update of configuration * such as SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU */ void sigSTOP_handler(int /*signum*/) { PCM * m = PCM::getInstance(); int runState = m->getRunState(); std::string state = (runState == 1 ? "suspend" : "continue"); std::cerr << "DEBUG: caught signal to " << state << " execution.\n"; // debug of signals only if (runState == 1) { // stop counters and sleep... almost forever; m->setRunState(0); sleep(INT_MAX); } else { // resume m->setRunState(1); alarm(1); } return; } /** * \brief handles signals that lead to update of configuration * such as SIGCONT */ void sigCONT_handler(int /*signum*/) { std::cout << "DEBUG: caught signal to continue execution.\n"; // debug of signals only // TODO: clear counters, resume counting. return; } #endif // ifdef _MSC_VER //! \brief install various handlers for system signals void set_signal_handlers(void) { if (atexit(exit_cleanup) != 0) { std::cerr << "ERROR: Failed to install exit handler.\n"; return; } #ifdef _MSC_VER BOOL handlerStatus; // Increase the priority a bit to improve context switching delays on Windows SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); // to fix Cygwin/BASH setting Ctrl+C handler need first to restore the default one handlerStatus = SetConsoleCtrlHandler(NULL, FALSE); // restores normal processing of CTRL+C input if (handlerStatus == 0) { tcerr << "Failed to set Ctrl+C handler. Error code: " << GetLastError() << " "; const TCHAR * errorStr = _com_error(GetLastError()).ErrorMessage(); if (errorStr) tcerr << errorStr; tcerr << "\n"; _exit(EXIT_FAILURE); } handlerStatus = SetConsoleCtrlHandler((PHANDLER_ROUTINE)sigINT_handler, TRUE); if (handlerStatus == 0) { tcerr << "Failed to set Ctrl+C handler. Error code: " << GetLastError() << " "; const TCHAR * errorStr = _com_error(GetLastError()).ErrorMessage(); if (errorStr) tcerr << errorStr; tcerr << "\n"; _exit(EXIT_FAILURE); } SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)&unhandled_exception_handler); char *envPath; if (_dupenv_s(&envPath, NULL, "_")) { std::cerr << "\nPCM ERROR: _dupenv_s failed.\n"; _exit(EXIT_FAILURE); } if (envPath) { std::cerr << "\nPCM ERROR: Detected cygwin/mingw environment which does not allow to setup PMU clean-up handlers on Ctrl-C and other termination signals.\n"; std::cerr << "See https://www.mail-archive.com/cygwin@cygwin.com/msg74817.html\n"; std::cerr << "As a workaround please run pcm directly from a native windows shell (e.g. cmd).\n"; std::cerr << "Exiting...\n\n"; freeAndNullify(envPath); _exit(EXIT_FAILURE); } freeAndNullify(envPath); std::cerr << "DEBUG: Setting Ctrl+C done.\n"; #else struct sigaction saINT, saHUP, saUSR, saSTOP, saCONT; // install handlers that interrupt execution saINT.sa_handler = sigINT_handler; sigemptyset(&saINT.sa_mask); saINT.sa_flags = SA_RESTART; sigaction(SIGINT, &saINT, NULL); sigaction(SIGQUIT, &saINT, NULL); sigaction(SIGABRT, &saINT, NULL); sigaction(SIGTERM, &saINT, NULL); saINT.sa_flags = SA_RESTART | SA_NOCLDSTOP; sigaction(SIGCHLD, &saINT, NULL); // get there is our child exits. do nothing if it stopped/continued saINT.sa_handler = sigSEGV_handler; sigemptyset(&saINT.sa_mask); saINT.sa_flags = SA_RESTART; sigaction(SIGSEGV, &saINT, NULL); // install SIGHUP handler to restart saHUP.sa_handler = sigHUP_handler; sigemptyset(&saHUP.sa_mask); saHUP.sa_flags = SA_RESTART; sigaction(SIGHUP, &saHUP, NULL); // install SIGHUP handler to restart saUSR.sa_handler = sigUSR_handler; sigemptyset(&saUSR.sa_mask); saUSR.sa_flags = SA_RESTART; sigaction(SIGUSR1, &saUSR, NULL); sigaction(SIGUSR2, &saUSR, NULL); // install SIGSTOP handler: pause/resume saSTOP.sa_handler = sigSTOP_handler; sigemptyset(&saSTOP.sa_mask); saSTOP.sa_flags = SA_RESTART; sigaction(SIGSTOP, &saSTOP, NULL); sigaction(SIGTSTP, &saSTOP, NULL); sigaction(SIGTTIN, &saSTOP, NULL); sigaction(SIGTTOU, &saSTOP, NULL); // install SIGCONT & SIGALRM handler saCONT.sa_handler = sigCONT_handler; sigemptyset(&saCONT.sa_mask); saCONT.sa_flags = SA_RESTART; sigaction(SIGCONT, &saCONT, NULL); sigaction(SIGALRM, &saCONT, NULL); #endif return; } //! \brief Restores default signal handlers under Linux/UNIX void restore_signal_handlers(void) { #ifndef _MSC_VER struct sigaction action; action.sa_handler = SIG_DFL; action.sa_flags = 0; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, NULL); sigaction(SIGQUIT, &action, NULL); sigaction(SIGABRT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGSEGV, &action, NULL); sigaction(SIGCHLD, &action, NULL); // restore SIGHUP handler to restart sigaction(SIGHUP, &action, NULL); // restore SIGHUP handler to restart sigaction(SIGUSR1, &action, NULL); sigaction(SIGUSR2, &action, NULL); // restore SIGSTOP handler: pause/resume // sigaction(SIGSTOP, &action, NULL); // cannot catch this // handle SUSP character: normally C-z) sigaction(SIGTSTP, &action, NULL); sigaction(SIGTTIN, &action, NULL); sigaction(SIGTTOU, &action, NULL); // restore SIGCONT & SIGALRM handler sigaction(SIGCONT, &action, NULL); sigaction(SIGALRM, &action, NULL); #endif return; } void set_real_time_priority(const bool & silent) { if (!silent) { std::cerr << "Setting real time priority for the process\n"; } #ifdef _MSC_VER if (!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS)) { std::cerr << "ERROR: SetPriorityClass with REALTIME_PRIORITY_CLASS failed with error " << GetLastError() << "\n"; } if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) { std::cerr << "ERROR: SetThreadPriority with THREAD_PRIORITY_TIME_CRITICAL failed with error " << GetLastError() << "\n"; } #elif __linux__ const auto priority = sched_get_priority_max(SCHED_RR); if (priority == -1) { std::cerr << "ERROR: Could not get SCHED_RR max priority: " << strerror(errno) << "\n"; } else { struct sched_param sp = { .sched_priority = priority }; if (sched_setscheduler(0, SCHED_RR, &sp) == -1) { const auto errnosave = errno; std::cerr << "ERROR: Could not set scheduler to realtime! Errno: " << errnosave << " Error message: \"" << strerror(errnosave) << "\"\n"; } else { if (!silent) { std::cerr << "Scheduler changed to SCHED_RR and priority to " << priority << "\n"; } } } #else std::cerr << "Setting real time priority for the process not implemented on your OS.\n"; #endif } void set_post_cleanup_callback(void(*cb)(void)) { post_cleanup_callback = cb; } //!\brief launches external program in a separate process void MySystem(char * sysCmd, char ** sysArgv) { if (sysCmd == NULL) { assert("No program provided. NULL pointer"); exit(EXIT_FAILURE); } std::cerr << "\nExecuting \""; std::cerr << sysCmd; std::cerr << "\" command:\n"; #ifdef _MSC_VER intptr_t ret; char cbuf[128]; if (PCM::getInstance()->isBlocked()) { // synchronous start: wait for child process completion // in case PCM should be blocked waiting for the child application to end // 1. returns and ret = -1 in case of error creating process is encountered // 2. ret = _spawnvp(_P_WAIT, sysCmd, sysArgv); if (ret == -1) { // process creation failed. strerror_s(cbuf, 128, errno); std::cerr << "Failed to start program \"" << sysCmd << "\". " << cbuf << "\n"; exit(EXIT_FAILURE); } else { // process created, worked, and completed with exist code in ret. ret=0 -> Success std::cerr << "Program exited with status " << ret << "\n"; } } else { // async start: PCM works in parallel with the child process, and exits when ret = _spawnvp(_P_NOWAIT, sysCmd, sysArgv); if (ret == -1) { strerror_s(cbuf, 128, errno); std::cerr << "Failed to start program \"" << sysCmd << "\". " << cbuf << "\n"; exit(EXIT_FAILURE); } else { // ret here is the new process handle. // start new thread which will wait for child completion, and continue PCM's execution if (_beginthread(waitForChild, 0, (void *)ret) == -1L) { strerror_s(cbuf, 128, errno); std::cerr << "WARNING: Failed to set waitForChild. PCM will continue infinitely: finish it manually! " << cbuf << "\n"; } } } #else pid_t child_pid = fork(); if (child_pid == 0) { execvp(sysCmd, sysArgv); std::cerr << "Failed to start program \"" << sysCmd << "\"\n"; exit(EXIT_FAILURE); } else { if (PCM::getInstance()->isBlocked()) { int res; waitpid(child_pid, &res, 0); std::cerr << "Program " << sysCmd << " launched with PID: " << std::dec << child_pid << "\n"; if (WIFEXITED(res)) { std::cerr << "Program exited with status " << WEXITSTATUS(res) << "\n"; } else if (WIFSIGNALED(res)) { std::cerr << "Process " << child_pid << " was terminated with status " << WTERMSIG(res) << "\n"; } } } #endif } #ifdef _MSC_VER #define HORIZONTAL char(196) #define VERTICAL char(179) #define DOWN_AND_RIGHT char(218) #define DOWN_AND_LEFT char(191) #define UP_AND_RIGHT char(192) #define UP_AND_LEFT char(217) #else #define HORIZONTAL u8"\u2500" #define VERTICAL u8"\u2502" #define DOWN_AND_RIGHT u8"\u250C" #define DOWN_AND_LEFT u8"\u2510" #define UP_AND_RIGHT u8"\u2514" #define UP_AND_LEFT u8"\u2518" #endif template void drawBar(const int nempty, const T & first, const int width, const T & last) { for (int c = 0; c < nempty; ++c) { std::cout << ' '; } std::cout << first; for (int c = 0; c < width; ++c) { std::cout << HORIZONTAL; } std::cout << last << '\n'; } void drawStackedBar(const std::string & label, std::vector & h, const int width) { int real_width = 0; auto scale = [&width](double fraction) { return int(round(fraction * double(width))); }; for (const auto & i : h) { real_width += scale(i.fraction); } if (real_width > 2*width) { std::cout << "ERROR: sum of fractions > 2 ("<< real_width << " > " << width << ")\n"; return; } drawBar((int)label.length(), DOWN_AND_RIGHT, real_width, DOWN_AND_LEFT); std::cout << label << VERTICAL; for (const auto & i : h) { const int c_width = scale(i.fraction); for (int c = 0; c < c_width; ++c) { std::cout << i.fill; } } std::cout << VERTICAL << "\n"; drawBar((int)label.length(), UP_AND_RIGHT, real_width, UP_AND_LEFT); } bool CheckAndForceRTMAbortMode(const char * arg, PCM * m) { if (check_argument_equals(arg, {"-force-rtm-abort-mode"})) { if (nullptr == m) { m = PCM::getInstance(); assert(m); } m->enableForceRTMAbortMode(); return true; } return false; } std::vector split(const std::string & str, const char delim) { std::string token; std::vector result; std::istringstream strstr(str); while (std::getline(strstr, token, delim)) { result.push_back(token); } return result; } uint64 read_number(const char* str) { std::istringstream stream(str); if (strstr(str, "x")) stream >> std::hex; uint64 result = 0; stream >> result; return result; } // emulates scanf %i for hex 0x prefix otherwise assumes dec (no oct support) bool match(const std::string& subtoken, const std::string& sname, uint64* result) { if (pcm_sscanf(subtoken) >> s_expect(sname + "0x") >> std::hex >> *result) return true; if (pcm_sscanf(subtoken) >> s_expect(sname) >> std::dec >> *result) return true; return false; } #define PCM_CALIBRATION_INTERVAL 50 // calibrate clock only every 50th iteration int calibratedSleep(const double delay, const char* sysCmd, const MainLoop& mainLoop, PCM* m) { static uint64 TimeAfterSleep = 0; int delay_ms = int(delay * 1000); if (TimeAfterSleep) delay_ms -= (int)(m->getTickCount() - TimeAfterSleep); if (delay_ms < 0) delay_ms = 0; if (sysCmd == NULL || mainLoop.getNumberOfIterations() != 0 || m->isBlocked() == false) { if (delay_ms > 0) { DBG(1, "sleeping for " , std::dec , delay_ms , " ms..."); MySleepMs(delay_ms); } } TimeAfterSleep = m->getTickCount(); return delay_ms; }; void print_help_force_rtm_abort_mode(const int alignment, const char * separator) { if (PCM::isForceRTMAbortModeAvailable() == false) { return; } try { const auto m = PCM::getInstance(); if (m->getMaxCustomCoreEvents() < 4) { std::cout << " -force-rtm-abort-mode"; for (int i = 0; i < (alignment - 23); ++i) { std::cout << " "; } assert(separator); std::cout << separator << " force RTM transaction abort mode to enable more programmable counters\n"; } } catch (std::exception & e) { std::cerr << "ERROR: " << e.what() << "\n"; } catch (...) { std::cerr << "ERROR: Unknown exception caught in print_help_force_rtm_abort_mode\n"; } } #ifdef _MSC_VER std::string safe_getenv(const char* env) { char * buffer; std::string result; if (_dupenv_s(&buffer, NULL, env) == 0 && buffer != nullptr) { result = buffer; freeAndNullify(buffer); } return result; } #else std::string safe_getenv(const char* env) { const auto getenvResult = std::getenv(env); return getenvResult ? std::string(getenvResult) : std::string(""); } #endif void print_pid_collection_message(int pid) { if (pid != -1) { std::cerr << "Collecting core metrics for process ID " << std::dec << pid << "\n"; } } double parse_delay(const char *arg, const std::string& progname, print_usage_func print_usage_func) { // any other options positional that is a floating point number is treated as , // while the other options are ignored with a warning issues to stderr double delay_input = 0.0; std::istringstream is_str_stream(arg); is_str_stream >> std::noskipws >> delay_input; if(is_str_stream.eof() && !is_str_stream.fail()) { if (delay_input < 0) { std::cerr << "Invalid delay specified: \"" << *arg << "\". Delay should be positive.\n"; if(print_usage_func) { print_usage_func(progname); } exit(EXIT_FAILURE); } return delay_input; } else { std::cerr << "WARNING: unknown command-line option: \"" << *arg << "\". Ignoring it.\n"; if(print_usage_func) { print_usage_func(progname); } exit(EXIT_FAILURE); } } std::list extract_integer_list(const char *optarg){ const char *pstr = optarg; std::list corelist; std::string snum1, snum2; std::string *pnow = &snum1; char nchar = ','; while(*pstr != '\0' || nchar != ','){ nchar = ','; if (*pstr != '\0'){ nchar = *pstr; pstr++; } //printf("c=%c\n",nchar); if (nchar=='-' && pnow == &snum1 && snum1.size()>0){ pnow = &snum2; }else if (nchar == ','){ if (!snum1.empty() && !snum2.empty()){ int num1 = atoi(snum1.c_str()), num2 =atoi(snum2.c_str()); if (num2 < num1) std::swap(num1,num2); if (num1 < 0) num1 = 0; for (int ix=num1; ix <= num2; ix++){ corelist.push_back(ix); } }else if (!snum1.empty()){ int num1 = atoi(snum1.c_str()); corelist.push_back(num1); } snum1.clear(); snum2.clear(); pnow = &snum1; }else if (nchar != ' '){ pnow->push_back(nchar); } } return(corelist); } bool extract_argument_value(const char* arg, std::initializer_list arg_names, std::string& value) { const auto arg_len = strlen(arg); for (const auto& arg_name: arg_names) { const auto arg_name_len = strlen(arg_name); if (arg_len > arg_name_len && strncmp(arg, arg_name, arg_name_len) == 0 && arg[arg_name_len] == '=') { value = arg + arg_name_len + 1; const auto last_pos = value.find_last_not_of("\""); if (last_pos != std::string::npos) { value.erase(last_pos + 1); } const auto first_pos = value.find_first_not_of("\""); if (first_pos != std::string::npos) { value.erase(0, first_pos); } return true; } } return false; } bool check_argument_equals(const char* arg, std::initializer_list arg_names) { const auto arg_len = strlen(arg); for (const auto& arg_name: arg_names) { if (arg_len == strlen(arg_name) && strncmp(arg, arg_name, arg_len) == 0) { return true; } } return false; } void check_and_set_silent(int argc, char * argv[], null_stream &nullStream2) { if (argc > 1) do { argv++; argc--; if (check_argument_equals(*argv, {"--help", "-h", "/h"}) || check_argument_equals(*argv, {"-silent", "/silent"})) { std::cerr.rdbuf(&nullStream2); return; } } while (argc > 1); } bool check_for_injections(const std::string & str) { const std::array symbols = {'=', '+', '-', '@'}; if (std::find(std::begin(symbols), std::end(symbols), str[0]) != std::end(symbols)) { std::cerr << "ERROR: First letter in event name: " << str << " cannot be \"" << str[0] << "\" , please use escape \"\\\" or remove it\n"; return true; } return false; } void print_enforce_flush_option_help() { std::cout << " -f | /f => enforce flushing output\n"; } bool print_version(int argc, char * argv[]) { if (argc > 1) do { argv++; argc--; if (check_argument_equals(*argv, {"--version"})) { std::cout << "version: " << PCM_VERSION << "\n"; return true; } } while (argc > 1); return false; } std::string dos2unix(std::string in) { if (in.length() > 0 && int(in[in.length() - 1]) == 13) { in.erase(in.length() - 1); } return in; } bool isRegisterEvent(const std::string & pmu) { if (pmu == "mmio" || pmu == "pcicfg" || pmu == "pmt" || pmu == "tpmi" || pmu == "package_msr" || pmu == "thread_msr") { return true; } return false; } std::string a_title(const std::string &init, const std::string &name) { char begin = init[0]; std::string row = init; row += name; return row + begin; } std::string a_data (std::string init, struct data d) { char begin = init[0]; std::string row = init; std::string str_d = unit_format(d.value); row += str_d; if (str_d.size() > d.width) throw std::length_error("counter value > event_name length"); row += std::string(d.width - str_d.size(), ' '); return row + begin; } std::string build_line(std::string init, std::string name, bool last_char = true, char this_char = '_') { char begin = init[0]; std::string row = init; row += std::string(name.size(), this_char); if (last_char == true) row += begin; return row; } std::string a_header_footer (std::string init, std::string name) { return build_line(init, name); } std::string build_csv_row(const std::vector& chunks, const std::string& delimiter) { return std::accumulate(chunks.begin(), chunks.end(), std::string(""), [delimiter](const std::string &left, const std::string &right){ return left.empty() ? right : left + delimiter + right; }); } std::vector prepare_data(const std::vector &values, const std::vector &headers) { std::vector v; uint32_t idx = 0; for (std::vector::const_iterator iunit = std::next(headers.begin()); iunit != headers.end() && idx < values.size(); ++iunit, idx++) { struct data d; d.width = (uint32_t)iunit->size(); d.value = values[idx]; v.push_back(d); } return v; } void display(const std::vector &buff, std::ostream& stream) { for (std::vector::const_iterator iunit = buff.begin(); iunit != buff.end(); ++iunit) stream << *iunit << "\n"; stream << std::flush; } void print_nameMap(std::map>>& nameMap) { for (std::map>>::const_iterator iunit = nameMap.begin(); iunit != nameMap.end(); ++iunit) { std::string h_name = iunit->first; std::pair> value = iunit->second; uint32_t hid = value.first; std::map vMap = value.second; std::cout << "H name: " << h_name << " id =" << hid << " vMap size:" << vMap.size() << "\n"; for (std::map::const_iterator junit = vMap.begin(); junit != vMap.end(); ++junit) { std::string v_name = junit->first; uint32_t vid = junit->second; std::cout << "V name: " << v_name << " id =" << vid << "\n"; } } } //! \brief load_events: parse the evt config file. //! \param fn:event config file name. //! \param ofm: operation field map struct. //! \param pfn_evtcb: see below. //! \param evtcb_ctx: pointer of the callback context(user define). //! \param nameMap: human readable metrics names. //! \return -1 means fail, 0 means success. //! \brief pfn_evtcb: call back func of event config file processing, app should provide it. //! \param void *: pointer of the callback context. //! \param counter &: common base counter struct. //! \param std::map &: operation field map struct. //! \param std::string: event field name. //! \param uint64: event field value. //! \return -1 means fail with app exit, 0 means success or fail with continue. int load_events(const std::string &fn, std::map &ofm, int (*pfn_evtcb)(evt_cb_type, void *, counter &, std::map &, std::string, uint64), void *evtcb_ctx, std::map>> &nameMap) { struct counter ctr; std::ifstream in(fn); std::string line, item; if (!in.is_open()) { const auto alt_fn = getInstallPathPrefix() + fn; in.open(alt_fn); if (!in.is_open()) { in.close(); const auto err_msg = std::string("event config file ") + fn + " or " + alt_fn + " is not available, you can try to manually copy it from PCM source package."; throw std::invalid_argument(err_msg); } } while (std::getline(in, line)) { //TODO: substring until #, if len == 0, skip, else parse normally //Set default value if the item is NOT available in cfg file. ctr.h_event_name = "INVALID"; ctr.v_event_name = "INVALID"; ctr.ccr = 0; ctr.idx = 0; ctr.multiplier = 1; ctr.divider = 1; ctr.h_id = 0; ctr.v_id = 0; if (pfn_evtcb(EVT_LINE_START, evtcb_ctx, ctr, ofm, "", 0)) { in.close(); const auto err_msg = std::string("event line processing(start) fault.\n"); throw std::invalid_argument(err_msg); } /* Ignore anyline with # */ if (line.find("#") != std::string::npos) continue; /* If line does not have any deliminator, we ignore it as well */ if (line.find("=") == std::string::npos) continue; std::string h_name, v_name; std::istringstream iss(line); while (std::getline(iss, item, ',')) { std::string key, value; uint64 numValue; /* assume the token has the format = */ key = item.substr(0,item.find("=")); value = item.substr(item.find("=")+1); if (key.empty() || value.empty()) continue; //skip the item if the token invalid std::istringstream iss2(value); iss2 >> std::setbase(0) >> numValue; switch (ofm[key]) { case PCM::H_EVENT_NAME: h_name = dos2unix(value); ctr.h_event_name = h_name; if (nameMap.find(h_name) == nameMap.end()) { /* It's a new horizontal event name */ uint32_t next_h_id = (uint32_t)nameMap.size(); std::pair> nameMap_value(next_h_id, std::map()); nameMap[h_name] = nameMap_value; } ctr.h_id = (uint32_t)nameMap.size() - 1; DBG(2, "h_name:" , ctr.h_event_name , "h_id: ", ctr.h_id); break; case PCM::V_EVENT_NAME: { v_name = dos2unix(value); ctr.v_event_name = v_name; //XXX: If h_name comes after v_name, we'll have a problem. //XXX: It's very weird, I forgot to assign nameMap[h_name] = nameMap_value earlier (:298), but this part still works? std::map &v_nameMap = nameMap[h_name].second; if (v_nameMap.find(v_name) == v_nameMap.end()) { v_nameMap[v_name] = (unsigned int)v_nameMap.size() - 1; DBG(2, "v_name(" , v_name , ")=", v_nameMap[v_name]); } else { in.close(); const auto err_msg = std::string("Detect duplicated v_name:") + v_name + "\n"; throw std::invalid_argument(err_msg); } ctr.v_id = (uint32_t)v_nameMap.size() - 1; DBG(2, "h_name:" , ctr.h_event_name , ",hid=" , ctr.h_id , ",v_name:" , ctr.v_event_name , ",v_id: ", ctr.v_id); break; } //TODO: double type for multiplier. drop divider variable case PCM::MULTIPLIER: ctr.multiplier = (int)numValue; break; case PCM::DIVIDER: ctr.divider = (int)numValue; break; case PCM::COUNTER_INDEX: ctr.idx = (int)numValue; break; default: if (pfn_evtcb(EVT_LINE_FIELD, evtcb_ctx, ctr, ofm, key, numValue)) { in.close(); const auto err_msg = std::string("event line processing(field) fault.\n"); throw std::invalid_argument(err_msg); } break; } } DBG(2, "Finished parsing: " , line); if (pfn_evtcb(EVT_LINE_COMPLETE, evtcb_ctx, ctr, ofm, "", 0)) { in.close(); const auto err_msg = std::string("event line processing(end) fault.\n"); throw std::invalid_argument(err_msg); } } //print_nameMap(nameMap); //DEBUG purpose in.close(); return 0; } int load_events(const std::string &fn, std::map &ofm, int (*pfn_evtcb)(evt_cb_type, void *, counter &, std::map &, std::string, uint64), void *evtcb_ctx) { std::map>> nm; return load_events(fn, ofm, pfn_evtcb, evtcb_ctx, nm); } bool get_cpu_bus(uint32 msmDomain, uint32 msmBus, uint32 msmDev, uint32 msmFunc, uint32 &cpuBusValid, std::vector &cpuBusNo, int &cpuPackageId) { DBG(2, "get_cpu_bus: d=" , std::hex , msmDomain , ",b=" , msmBus , ",d=" , msmDev , ",f=" , msmFunc , std::dec ); try { PciHandleType h(msmDomain, msmBus, msmDev, msmFunc); h.read32(SPR_MSM_REG_CPUBUSNO_VALID_OFFSET, &cpuBusValid); if (cpuBusValid == (std::numeric_limits::max)()) { std::cerr << "Failed to read CPUBUSNO_VALID" << std::endl; return false; } cpuBusNo.resize(8); for (int i = 0; i < 4; ++i) { h.read32(SPR_MSM_REG_CPUBUSNO0_OFFSET + i * 4, &cpuBusNo[i]); h.read32(SPR_MSM_REG_CPUBUSNO4_OFFSET + i * 4, &cpuBusNo[i + 4]); if (cpuBusNo[i] == (std::numeric_limits::max)() || cpuBusNo[i + 4] == (std::numeric_limits::max)()) { std::cerr << "Failed to read CPUBUSNO registers" << std::endl; return false; } } /* * It's possible to have not enabled first stack that's why * need to find the first valid bus to read CSR */ int firstValidBusId = 0; while (!((cpuBusValid >> firstValidBusId) & 0x1)) firstValidBusId++; int cpuBusNo0 = (cpuBusNo[(int)(firstValidBusId / 4)] >> ((firstValidBusId % 4) * 8)) & 0xff; uint32 sadControlCfg = 0x0; PciHandleType sad_cfg_handler(msmDomain, cpuBusNo0, 0, 0); sad_cfg_handler.read32(SPR_SAD_REG_CTL_CFG_OFFSET, &sadControlCfg); if (sadControlCfg == (std::numeric_limits::max)()) { std::cerr << "Failed to read SAD_CONTROL_CFG" << std::endl; return false; } cpuPackageId = sadControlCfg & 0xf; return true; } catch (...) { std::cerr << "Warning: unable to enumerate CPU Buses" << std::endl; return false; } } std::pair parseBitsParameter(const char * param) { std::pair bits{-1, -1}; const auto bitsArray = pcm::split(std::string(param),':'); assert(bitsArray.size() == 2); bits.first = (int64)read_number(bitsArray[0].c_str()); bits.second = (int64)read_number(bitsArray[1].c_str()); assert(bits.first >= 0); assert(bits.second >= 0); assert(bits.first < 64); assert(bits.second < 64); if (bits.first > bits.second) { std::swap(bits.first, bits.second); } return bits; } #ifdef __linux__ FILE * tryOpen(const char * path, const char * mode) { FILE * f = fopen(path, mode); if (!f) { f = fopen((std::string("/pcm") + path).c_str(), mode); } return f; } std::string readSysFS(const char * path, bool silent) { FILE * f = tryOpen(path, "r"); if (!f) { if (silent == false) std::cerr << "ERROR: Can not open " << path << " file.\n"; return std::string(); } char buffer[1024]; if(NULL == fgets(buffer, 1024, f)) { if (silent == false) std::cerr << "ERROR: Can not read from " << path << ".\n"; fclose(f); return std::string(); } fclose(f); return std::string(buffer); } bool writeSysFS(const char * path, const std::string & value, bool silent) { FILE * f = tryOpen(path, "w"); if (!f) { if (silent == false) std::cerr << "ERROR: Can not open " << path << " file.\n"; return false; } if (fputs(value.c_str(), f) < 0) { if (silent == false) std::cerr << "ERROR: Can not write to " << path << ".\n"; fclose(f); return false; } fclose(f); return true; } int readMaxFromSysFS(const char * path) { std::string content = readSysFS(path); const char * buffer = content.c_str(); int result = -1; pcm_sscanf(buffer) >> s_expect("0-") >> result; if(result == -1) { pcm_sscanf(buffer) >> result; } return result; } bool readMapFromSysFS(const char * path, std::unordered_map &result, bool silent) { FILE * f = tryOpen(path, "r"); if (!f) { if (silent == false) std::cerr << "ERROR: Can not open " << path << " file.\n"; return false; } char buffer[1024]; while(fgets(buffer, 1024, f) != NULL) { std::string key, value, item; uint32 numValue = 0; item = std::string(buffer); key = item.substr(0,item.find(" ")); value = item.substr(item.find(" ")+1); if (key.empty() || value.empty()) continue; //skip the item if the token invalid std::istringstream iss2(value); iss2 >> std::setbase(0) >> numValue; result.insert(std::pair(key, numValue)); DBG(3, "readMapFromSysFS:" , key , "=" , numValue , "."); } fclose(f); return true; } #endif #ifdef _MSC_VER //! restrict usage of driver to system (SY) and builtin admins (BA) void restrictDriverAccessNative(LPCTSTR path) { PSECURITY_DESCRIPTOR pSD = nullptr; if (!ConvertStringSecurityDescriptorToSecurityDescriptor( _T("O:BAG:SYD:(A;;FA;;;SY)(A;;FA;;;BA)"), SDDL_REVISION_1, &pSD, nullptr)) { _tprintf(TEXT("Error in ConvertStringSecurityDescriptorToSecurityDescriptor: %d\n"), GetLastError()); return; } if (SetFileSecurity(path, DACL_SECURITY_INFORMATION, pSD)) { // _tprintf(TEXT("Successfully restricted access for %s\n"), path); } else { _tprintf(TEXT("Error in SetFileSecurity for %s. Error %d\n"), path, GetLastError()); } LocalFree(pSD); } #endif } // namespace pcm pcm-202502/src/utils.h000066400000000000000000000577041475730356400145200ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev /*! \file utils.h \brief Some common utility routines */ #pragma once #include #include #include #include #include "types.h" #include "debug.h" #include #include #include #include #include #if defined(__FreeBSD__) || (defined(__DragonFly__) && __DragonFly_version >= 400707) #include #include #include #endif #ifndef _MSC_VER #include #include #include #include #else #include #endif #include #include #ifdef __linux__ #include #endif namespace pcm { template inline void deleteAndNullify(T & p) { if (p) { delete p; p = nullptr; } } template inline void deleteAndNullifyArray(T & p) { if (p) { delete [] p; p = nullptr; } } template inline void freeAndNullify(T & p) { if (p) { free(p); p = nullptr; } } std::string safe_getenv(const char* env); #ifdef _MSC_VER typedef std::wstring StringType; #define PCM_STRING(x) (L ## x) #else typedef std::string StringType; #define PCM_STRING(x) (x) #endif void eraseEnvironmentVariables(const std::vector& keepList); void setDefaultDebugLevel(); } #ifdef _MSC_VER #define PCM_SET_DLL_DIR SetDllDirectory(_T("")); #else #define PCM_SET_DLL_DIR #endif #define PCM_MAIN_NOTHROW \ int mainThrows(int argc, char * argv[]); \ int main(int argc, char * argv[]) \ { \ try { \ eraseEnvironmentVariables({PCM_STRING("POSIXLY_CORRECT")}); \ } catch(const std::exception & e) \ { \ std::cerr << "PCM ERROR. Exception in eraseEnvironmentVariables: " << e.what() << "\n"; \ return -1; \ } \ PCM_SET_DLL_DIR \ if (pcm::safe_getenv("PCM_NO_MAIN_EXCEPTION_HANDLER") == std::string("1")) return mainThrows(argc, argv); \ try { \ setDefaultDebugLevel(); \ return mainThrows(argc, argv); \ } catch(const std::runtime_error & e) \ { \ std::cerr << "PCM ERROR. Exception " << e.what() << "\n"; \ } catch(const std::exception & e) \ { \ std::cerr << "PCM ERROR. Exception " << e.what() << "\n"; \ } catch (...) \ { \ std::cerr << "PCM ERROR. Exception detected (no further details available).\n"; \ } \ return -1; \ } namespace pcm { #ifdef _MSC_VER using tstring = std::basic_string; #ifdef UNICODE static auto& tcerr = std::wcerr; #else static auto& tcerr = std::cerr; #endif #endif // _MSC_VER typedef void (* print_usage_func)(const std::string & progname); std::list extract_integer_list(const char *optarg); double parse_delay(const char * arg, const std::string & progname, print_usage_func print_usage_func); bool extract_argument_value(const char * arg, std::initializer_list arg_names, std::string & value); bool check_argument_equals(const char * arg, std::initializer_list arg_names); bool check_for_injections(const std::string & str); void exit_cleanup(void); void set_signal_handlers(void); void set_real_time_priority(const bool & silent); void restore_signal_handlers(void); #ifndef _MSC_VER void printBacktrace(); void sigINT_handler(int signum); void sigHUP_handler(int signum); void sigUSR_handler(int signum); void sigSTOP_handler(int signum); void sigCONT_handler(int signum); #endif void set_post_cleanup_callback(void(*cb)(void)); inline void MySleep(int delay) { #ifdef _MSC_VER if (delay) Sleep(delay * 1000); #else ::sleep(delay); #endif } inline void MySleepMs(int delay_ms) { #ifdef _MSC_VER if (delay_ms) Sleep((DWORD)delay_ms); #else struct timespec sleep_intrval; double complete_seconds; sleep_intrval.tv_nsec = static_cast(1000000000.0 * (::modf(delay_ms / 1000.0, &complete_seconds))); sleep_intrval.tv_sec = static_cast(complete_seconds); ::nanosleep(&sleep_intrval, NULL); #endif } void MySystem(char * sysCmd, char ** argc); #ifdef _MSC_VER #pragma warning (disable : 4068 ) // disable unknown pragma warning #endif #ifdef __GCC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverloaded-virtual" #elif defined __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Woverloaded-virtual" #endif struct null_stream : public std::streambuf { int_type overflow(int_type) override { return {}; } }; #ifdef __GCC__ #pragma GCC diagnostic pop #elif defined __clang__ #pragma clang diagnostic pop #endif constexpr const char* ASCII_BLACK = "\033[0;30m"; constexpr const char* ASCII_RED = "\033[0;31m"; constexpr const char* ASCII_GREEN = "\033[0;32m"; constexpr const char* ASCII_YELLOW = "\033[0;33m"; constexpr const char* ASCII_BLUE = "\033[0;34m"; constexpr const char* ASCII_MAGENTA = "\033[0;35m"; constexpr const char* ASCII_CYAN = "\033[0;36m"; constexpr const char* ASCII_WHITE = "\033[0;37m"; constexpr const char* ASCII_BRIGHT_BLACK = "\033[1;30m"; constexpr const char* ASCII_BRIGHT_RED = "\033[1;31m"; constexpr const char* ASCII_BRIGHT_GREEN = "\033[1;32m"; constexpr const char* ASCII_BRIGHT_YELLOW = "\033[1;33m"; constexpr const char* ASCII_BRIGHT_BLUE = "\033[1;34m"; constexpr const char* ASCII_BRIGHT_MAGENTA = "\033[1;35m"; constexpr const char* ASCII_BRIGHT_CYAN = "\033[1;36m"; constexpr const char* ASCII_BRIGHT_WHITE = "\033[1;37m"; constexpr const char* ASCII_RESET_COLOR = "\033[0m"; void setColorEnabled(bool value = true); const char * setColor(const char * colorStr); const char * setNextColor(); const char * resetColor(); template inline std::string unit_format(IntType n) { char buffer[1024]; if (n <= 9999ULL) { snprintf(buffer, 1024, "%4d ", int32(n)); return std::string{buffer}; } if (n <= 9999999ULL) { snprintf(buffer, 1024, "%4d K", int32(n / 1000ULL)); return std::string{buffer}; } if (n <= 9999999999ULL) { snprintf(buffer, 1024, "%4d M", int32(n / 1000000ULL)); return std::string{buffer}; } if (n <= 9999999999999ULL) { snprintf(buffer, 1024, "%4d G", int32(n / 1000000000ULL)); return std::string{buffer}; } snprintf(buffer, 1024, "%4d T", int32(n / (1000000000ULL * 1000ULL))); return std::string{buffer}; } void print_cpu_details(); inline void printDebugCallstack() { #ifndef _MSC_VER if (safe_getenv("PCM_PRINT_DEBUG_CALLSTACK") == "1") { printBacktrace(); } #endif } template inline void warnAlignment(const char* call, const bool silent, const uint64 offset) { if (silent == false && (offset % Bytes) != 0) { std::cerr << "PCM Warning: " << call << " offset " << offset << " is not " << Bytes << "-byte aligned\n"; printDebugCallstack(); } } #define PCM_UNUSED(x) (void)(x) #define PCM_COMPILE_ASSERT(condition) \ typedef char pcm_compile_assert_failed[(condition) ? 1 : -1]; \ pcm_compile_assert_failed pcm_compile_assert_failed_; \ PCM_UNUSED(pcm_compile_assert_failed_); #ifdef _MSC_VER class ThreadGroupTempAffinity { GROUP_AFFINITY PreviousGroupAffinity; bool restore; ThreadGroupTempAffinity(); // forbidden ThreadGroupTempAffinity(const ThreadGroupTempAffinity &); // forbidden ThreadGroupTempAffinity & operator = (const ThreadGroupTempAffinity &); // forbidden public: ThreadGroupTempAffinity(uint32 core_id, bool checkStatus = true, const bool restore_ = false); ~ThreadGroupTempAffinity(); }; #endif class checked_uint64 // uint64 with checking for overflows when computing differences { uint64 data; uint64 overflows; public: checked_uint64() : data(0), overflows(0) {} checked_uint64(const uint64 d, const uint64 o) : data(d), overflows(o) {} const checked_uint64& operator += (const checked_uint64& o) { data += o.data; overflows += o.overflows; return *this; } uint64 operator - (const checked_uint64& o) const { // computing data - o.data constexpr uint64 counter_width = 48; return data + overflows * (1ULL << counter_width) - o.data; } uint64 getRawData_NoOverflowProtection() const { return data; } }; // a secure (but partial) alternative for sscanf // see example usage in pcm-core.cpp typedef std::istringstream pcm_sscanf; class s_expect : public std::string { public: explicit s_expect(const char * s) : std::string(s) {} explicit s_expect(const std::string & s) : std::string(s) {} friend std::istream & operator >> (std::istream & istr, s_expect && s); friend std::istream & operator >> (std::istream && istr, s_expect && s); private: void match(std::istream & istr) const { istr >> std::noskipws; const auto len = length(); char * buffer = new char[len + 2]; buffer[0] = 0; istr.get(buffer, len+1); if (*this != std::string(buffer)) { istr.setstate(std::ios_base::failbit); } deleteAndNullifyArray(buffer); } }; inline std::istream & operator >> (std::istream & istr, s_expect && s) { s.match(istr); return istr; } inline std::istream & operator >> (std::istream && istr, s_expect && s) { s.match(istr); return istr; } inline std::pair pcm_localtime() // returns { const auto durationSinceEpoch = std::chrono::system_clock::now().time_since_epoch(); const auto durationSinceEpochInSeconds = std::chrono::duration_cast(durationSinceEpoch); time_t now = durationSinceEpochInSeconds.count(); tm result; #ifdef _MSC_VER localtime_s(&result, &now); #else localtime_r(&now, &result); #endif return std::make_pair(result, std::chrono::duration_cast(durationSinceEpoch- durationSinceEpochInSeconds).count()); } enum CsvOutputType { Header1, Header2, Data, Header21, // merged headers 2 and 1 Json }; template inline void choose(const CsvOutputType outputType, H1 h1Func, H2 h2Func, D dataFunc) { switch (outputType) { case Header1: case Header21: h1Func(); break; case Header2: h2Func(); break; case Data: case Json: dataFunc(); break; default: std::cerr << "PCM internal error: wrong CSvOutputType\n"; } } inline void printDateForCSV(const CsvOutputType outputType, std::string separator = std::string(",")) { choose(outputType, [&separator]() { std::cout << separator << separator; // Time }, [&separator]() { std::cout << "Date" << separator << "Time" << separator; }, [&separator]() { std::pair tt{ pcm_localtime() }; std::cout.precision(3); char old_fill = std::cout.fill('0'); std::cout << std::setw(4) << 1900 + tt.first.tm_year << '-' << std::setw(2) << 1 + tt.first.tm_mon << '-' << std::setw(2) << tt.first.tm_mday << separator << std::setw(2) << tt.first.tm_hour << ':' << std::setw(2) << tt.first.tm_min << ':' << std::setw(2) << tt.first.tm_sec << '.' << std::setw(3) << tt.second << separator; // milliseconds std::cout.fill(old_fill); std::cout.setf(std::ios::fixed); std::cout.precision(2); }); } inline void printDateForJson(const std::string& separator, const std::string &jsonSeparator) { std::pair tt{ pcm_localtime() }; std::cout.precision(3); char old_fill = std::cout.fill('0'); std::cout << "Date" << jsonSeparator << "\"" << std::setw(4) << 1900 + tt.first.tm_year << '-' << std::setw(2) << 1 + tt.first.tm_mon << '-' << std::setw(2) << tt.first.tm_mday << "\"" << separator << "Time" << jsonSeparator << "\"" << std::setw(2) << tt.first.tm_hour << ':' << std::setw(2) << tt.first.tm_min << ':' << std::setw(2) << tt.first.tm_sec << '.' << std::setw(3) << tt.second << "\"" << separator; // milliseconds std::cout.fill(old_fill); std::cout.setf(std::ios::fixed); std::cout.precision(2); } std::vector split(const std::string & str, const char delim); class PCM; bool CheckAndForceRTMAbortMode(const char * argv, PCM * m); void print_help_force_rtm_abort_mode(const int alignment, const char * separator = "=>"); template void parseParam(int argc, char* argv[], const char* param, F f) { if (argc > 1) do { argv++; argc--; if ((std::string("-") + param == *argv) || (std::string("/") + param == *argv)) { argv++; argc--; if (argc == 0) { std::cerr << "ERROR: no parameter provided for option " << param << "\n"; exit(EXIT_FAILURE); } f(*argv); continue; } } while (argc > 1); // end of command line parsing loop } class MainLoop { unsigned numberOfIterations = 0; public: MainLoop() {} bool parseArg(const char * arg) { std::string arg_value; if (extract_argument_value(arg, {"-i", "/i"}, arg_value)) { numberOfIterations = (unsigned int)atoi(arg_value.c_str()); return true; } return false; } unsigned getNumberOfIterations() const { return numberOfIterations; } template void operator ()(const Body & body) { unsigned int i = 1; DBG(1, "numberOfIterations: " , numberOfIterations); while ((i <= numberOfIterations) || (numberOfIterations == 0)) { if (body() == false) { break; } ++i; } } }; #ifdef __linux__ FILE * tryOpen(const char * path, const char * mode); std::string readSysFS(const char * path, bool silent); bool writeSysFS(const char * path, const std::string & value, bool silent); #endif int calibratedSleep(const double delay, const char* sysCmd, const MainLoop& mainLoop, PCM* m); struct StackedBarItem { double fraction{0.0}; std::string label{""}; // not used currently char fill{'0'}; StackedBarItem() {} StackedBarItem(double fraction_, const std::string & label_, char fill_) : fraction(fraction_), label(label_), fill(fill_) {} }; void drawStackedBar(const std::string & label, std::vector & h, const int width = 80); // emulates scanf %i for hex 0x prefix otherwise assumes dec (no oct support) bool match(const std::string& subtoken, const std::string& sname, uint64* result); uint64 read_number(const char* str); inline void clear_screen() { #ifdef _MSC_VER system("cls"); #else std::cout << "\033[2J\033[0;0H"; #endif } #ifdef _MSC_VER #define PCM_MSR_DRV_NAME TEXT("\\\\.\\RDMSR") inline HANDLE openMSRDriver() { return CreateFile(PCM_MSR_DRV_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); } #endif #define PCM_ENFORCE_FLUSH_OPTION else if (check_argument_equals(*argv, { "-f", "/f" })) { enforceFlush = true; continue; } void print_enforce_flush_option_help(); // called before everything else to read '-s' arg and // silence all following err output void check_and_set_silent(int argc, char * argv[], null_stream &nullStream2); void print_pid_collection_message(int pid); bool print_version(int argc, char * argv[]); inline bool isPIDOption(char * argv []) { return check_argument_equals(*argv, {"-pid", "/pid"}); } inline void parsePID(int argc, char* argv[], int& pid) { parseParam(argc, argv, "pid", [&pid](const char* p) { if (p) pid = atoi(p); }); } struct counter { std::string h_event_name = ""; std::string v_event_name = ""; uint64_t ccr = 0; int idx = 0; /* Some counters need to be placed in specific index */ int multiplier = 0; int divider = 0; uint32_t h_id = 0; uint32_t v_id = 0; }; struct data{ uint32_t width; uint64_t value; }; typedef enum{ EVT_LINE_START, EVT_LINE_FIELD, EVT_LINE_COMPLETE }evt_cb_type; void getMCFGRecords(std::vector& mcfg); std::string dos2unix(std::string in); bool isRegisterEvent(const std::string & pmu); std::string a_title (const std::string &init, const std::string &name); std::string a_data (std::string init, struct data d); std::string a_header_footer(std::string init, std::string name); std::string build_line(std::string init, std::string name, bool last_char, char this_char); std::string build_csv_row(const std::vector& chunks, const std::string& delimiter); std::vector prepare_data(const std::vector &values, const std::vector &headers); void display(const std::vector &buff, std::ostream& stream); void print_nameMap(std::map>>& nameMap); int load_events(const std::string &fn, std::map &ofm, int (*p_fn_evtcb)(evt_cb_type, void *, counter &, std::map &, std::string, uint64), void *evtcb_ctx, std::map>> &nameMap); int load_events(const std::string &fn, std::map &ofm, int (*pfn_evtcb)(evt_cb_type, void *, counter &, std::map &, std::string, uint64), void *evtcb_ctx); bool get_cpu_bus(uint32 msmDomain, uint32 msmBus, uint32 msmDev, uint32 msmFunc, uint32 &cpuBusValid, std::vector &cpuBusNo, int &cpuPackageId); #ifdef __linux__ FILE * tryOpen(const char * path, const char * mode); std::string readSysFS(const char * path, bool silent = false); bool writeSysFS(const char * path, const std::string & value, bool silent = false); int readMaxFromSysFS(const char * path); bool readMapFromSysFS(const char * path, std::unordered_map &result, bool silent = false); #endif inline uint64 insertBits(uint64 input, const uint64 value, const int64_t position, const uint64 width) { const uint64 mask = (width == 64) ? (~0ULL) : ((1ULL << width) - 1ULL); // 1 -> 1b, 2 -> 11b, 3 -> 111b input &= ~(mask << position); // clear input |= (value & mask) << position; return input; } inline uint64 roundDownTo4K(uint64 number) { return number & ~0xFFFULL; // Mask the lower 12 bits to round down to 4K } inline uint64 roundUpTo4K(uint64 number) { if (number % 4096ULL == 0ULL) { // Already a multiple of 4K return number; } else { // Round up to the next multiple of 4K return ((number / 4096ULL) + 1ULL) * 4096ULL; } } #define PCM_STRINGIFY(x) #x #define PCM_TOSTRING(x) PCM_STRINGIFY(x) inline std::string getInstallPathPrefix() { #if defined (CMAKE_INSTALL_PREFIX) const std::string prefix{ PCM_TOSTRING(CMAKE_INSTALL_PREFIX) }; #else const std::string prefix{ "/usr" }; #endif return prefix + "/share/pcm/"; } std::pair parseBitsParameter(const char * param); template inline bool readOldValueHelper(const std::pair & bits, T & value, const bool & write, R readValue) { if (bits.first >= 0 && write) { // to write bits need to read the old value first T old_value = 0; if (!readValue(old_value)) { return false; } value = insertBits(old_value, value, bits.first, bits.second - bits.first + 1); } return true; } template inline void extractBitsPrintHelper(const std::pair & bits, T & value, const bool & dec) { std::cout << " Read "; if (bits.first >= 0) { std::cout << "bits "<< std::dec << bits.first << ":" << bits.second << " "; if (!dec) std::cout << std::hex << std::showbase; value = extract_bits(value, bits.first, bits.second); } std::cout << "value " << value; } #ifdef _MSC_VER void restrictDriverAccessNative(LPCTSTR path); #endif #ifdef __linux__ std::vector findPathsFromPattern(const char* pattern); #endif class TemporalThreadAffinity { TemporalThreadAffinity(); // forbidden #if defined(__FreeBSD__) || (defined(__DragonFly__) && __DragonFly_version >= 400707) cpu_set_t old_affinity; bool restore; public: TemporalThreadAffinity(uint32 core_id, bool checkStatus = true, const bool restore_ = true) : restore(restore_) { assert(core_id < 1024); auto res = pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &old_affinity); if (res != 0) { std::cerr << "ERROR: pthread_getaffinity_np for core " << core_id << " failed with code " << res << "\n"; throw std::exception(); } cpu_set_t new_affinity; CPU_ZERO(&new_affinity); CPU_SET(core_id, &new_affinity); // CPU_CMP() returns true if old_affinity is NOT equal to new_affinity if (!(CPU_CMP(&old_affinity, &new_affinity))) { restore = false; return; // the same affinity => return } res = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &new_affinity); if (res != 0 && checkStatus) { std::cerr << "ERROR: pthread_setaffinity_np for core " << core_id << " failed with code " << res << "\n"; throw std::exception(); } } ~TemporalThreadAffinity() { if (restore) pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &old_affinity); } bool supported() const { return true; } #elif defined(__linux__) cpu_set_t* old_affinity = nullptr; static constexpr auto maxCPUs = 8192; const size_t set_size; bool restore; public: TemporalThreadAffinity(const uint32 core_id, bool checkStatus = true, const bool restore_ = true) : set_size(CPU_ALLOC_SIZE(maxCPUs)), restore(restore_) { assert(core_id < maxCPUs); old_affinity = CPU_ALLOC(maxCPUs); assert(old_affinity); auto res = pthread_getaffinity_np(pthread_self(), set_size, old_affinity); if (res != 0) { std::cerr << "ERROR: pthread_getaffinity_np for core " << core_id << " failed with code " << res << "\n"; CPU_FREE(old_affinity); old_affinity = nullptr; throw std::runtime_error("pthread_getaffinity_np failed"); } cpu_set_t* new_affinity = CPU_ALLOC(maxCPUs); assert(new_affinity); CPU_ZERO_S(set_size, new_affinity); CPU_SET_S(core_id, set_size, new_affinity); if (CPU_EQUAL_S(set_size, old_affinity, new_affinity)) { CPU_FREE(new_affinity); restore = false; return; } res = pthread_setaffinity_np(pthread_self(), set_size, new_affinity); CPU_FREE(new_affinity); if (res != 0 && checkStatus) { std::cerr << "ERROR: pthread_setaffinity_np for core " << core_id << " failed with code " << res << "\n"; CPU_FREE(old_affinity); old_affinity = nullptr; throw std::runtime_error("pthread_setaffinity_np failed"); } } ~TemporalThreadAffinity() { if (restore) pthread_setaffinity_np(pthread_self(), set_size, old_affinity); CPU_FREE(old_affinity); old_affinity = nullptr; } bool supported() const { return true; } #elif defined(_MSC_VER) ThreadGroupTempAffinity affinity; public: TemporalThreadAffinity(uint32 core, bool checkStatus = true, const bool restore = true) : affinity(core, checkStatus, restore) { } bool supported() const { return true; } #else // not implemented for os x public: TemporalThreadAffinity(uint32) { } TemporalThreadAffinity(uint32, bool) {} bool supported() const { return false; } #endif }; } // namespace pcm pcm-202502/src/version.h000066400000000000000000000000761475730356400150330ustar00rootroot00000000000000#define PCM_VERSION "(2025-02-25 10:03:48 +0100 ID=5cb70ffd)" pcm-202502/src/width_extender.h000066400000000000000000000132241475730356400163620ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // written by Roman Dementiev // Austen Ott #ifndef WIDTH_EXTENDER_HEADER_ #define WIDTH_EXTENDER_HEADER_ /*! \file width_extender.h \brief Provides 64-bit "virtual" counters from underlying 32-bit HW counters */ #include #include "cpucounters.h" #include "utils.h" #include "bw.h" #include "mutex.h" #include #ifndef _MSC_VER // the header can not be included into code using CLR #include #else namespace std { class thread; } #endif namespace pcm { class CounterWidthExtender { public: struct AbstractRawCounter { virtual uint64 operator () () = 0; virtual ~AbstractRawCounter() { } }; struct MsrHandleCounter : public AbstractRawCounter { std::shared_ptr msr; uint64 msr_addr; uint64 msr_mask; MsrHandleCounter( std::shared_ptr msr_, const uint64 msr_addr_, const uint64 msr_mask_ = ~uint64(0ULL)) : msr(msr_), msr_addr(msr_addr_), msr_mask(msr_mask_) { } uint64 operator () () { uint64 value = 0; msr->read(msr_addr, &value); return value & msr_mask; } }; template struct ClientImcCounter : public AbstractRawCounter { std::shared_ptr clientBW; ClientImcCounter(std::shared_ptr clientBW_) : clientBW(clientBW_) { } uint64 operator () () { return (clientBW.get()->*F)(); } }; typedef ClientImcCounter<&FreeRunningBWCounters::getImcReads> ClientImcReadsCounter; typedef ClientImcCounter<&FreeRunningBWCounters::getImcWrites> ClientImcWritesCounter; typedef ClientImcCounter<&FreeRunningBWCounters::getGtRequests> ClientGtRequestsCounter; typedef ClientImcCounter<&FreeRunningBWCounters::getIaRequests> ClientIaRequestsCounter; typedef ClientImcCounter<&FreeRunningBWCounters::getIoRequests> ClientIoRequestsCounter; struct MBLCounter : public AbstractRawCounter { std::shared_ptr msr; MBLCounter(std::shared_ptr msr_) : msr(msr_) { } uint64 operator () () { msr->lock(); uint64 event = 3; // L3 Local External Bandwidth uint64 msr_qm_evtsel = 0, value = 0; msr->read(IA32_QM_EVTSEL, &msr_qm_evtsel); //std::cout << "MBLCounter reading IA32_QM_EVTSEL 0x"<< std::hex << msr_qm_evtsel << std::dec << "\n"; msr_qm_evtsel &= 0xfffffffffffffff0ULL; msr_qm_evtsel |= event & ((1ULL << 8) - 1ULL); //std::cout << "MBL event " << msr_qm_evtsel << "\n"; //std::cout << "MBLCounter writing IA32_QM_EVTSEL 0x"<< std::hex << msr_qm_evtsel << std::dec << "\n"; msr->write(IA32_QM_EVTSEL, msr_qm_evtsel); msr->read(IA32_QM_CTR, &value); //std::cout << "MBLCounter reading IA32_QM_CTR "<< std::dec << value << std::dec << "\n"; msr->unlock(); return value; } }; struct MBTCounter : public AbstractRawCounter { std::shared_ptr msr; MBTCounter(std::shared_ptr msr_) : msr(msr_) { } uint64 operator () () { msr->lock(); uint64 event = 2; // L3 Total External Bandwidth uint64 msr_qm_evtsel = 0, value = 0; msr->read(IA32_QM_EVTSEL, &msr_qm_evtsel); //std::cout << "MBTCounter reading IA32_QM_EVTSEL 0x"<< std::hex << msr_qm_evtsel << std::dec << "\n"; msr_qm_evtsel &= 0xfffffffffffffff0ULL; msr_qm_evtsel |= event & ((1ULL << 8) - 1ULL); //std::cout << "MBR event " << msr_qm_evtsel << "\n"; //std::cout << "MBTCounter writing IA32_QM_EVTSEL 0x"<< std::hex << msr_qm_evtsel << std::dec << "\n"; msr->write(IA32_QM_EVTSEL, msr_qm_evtsel); msr->read(IA32_QM_CTR, &value); //std::cout << "MBTCounter reading IA32_QM_CTR "<< std::dec << value << std::dec << "\n"; msr->unlock(); return value; } }; private: std::thread * UpdateThread; Mutex CounterMutex; AbstractRawCounter * raw_counter; uint64 extended_value; uint64 last_raw_value; uint64 counter_width; uint32 watchdog_delay_ms; CounterWidthExtender(); // forbidden CounterWidthExtender(CounterWidthExtender &); // forbidden CounterWidthExtender & operator = (const CounterWidthExtender &); // forbidden uint64 internal_read() { uint64 result = 0, new_raw_value = 0; CounterMutex.lock(); new_raw_value = (*raw_counter)(); if (new_raw_value < last_raw_value) { extended_value += ((1ULL << counter_width) - last_raw_value) + new_raw_value; } else { extended_value += (new_raw_value - last_raw_value); } last_raw_value = new_raw_value; result = extended_value; CounterMutex.unlock(); return result; } public: CounterWidthExtender(AbstractRawCounter * raw_counter_, uint64 counter_width_, uint32 watchdog_delay_ms_); virtual ~CounterWidthExtender(); uint64 read() // read extended value { return internal_read(); } void reset() { CounterMutex.lock(); extended_value = last_raw_value = (*raw_counter)(); CounterMutex.unlock(); } }; } // namespace pcm #endif pcm-202502/src/windows/000077500000000000000000000000001475730356400146645ustar00rootroot00000000000000pcm-202502/src/windows/AssemblyInfo.cpp000066400000000000000000000024721475730356400177700ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman */ using namespace System; using namespace System::Reflection; using namespace System::Runtime::CompilerServices; using namespace System::Runtime::InteropServices; using namespace System::Security::Permissions; // // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. // [assembly:AssemblyTitleAttribute("PCMService")]; [assembly:AssemblyDescriptionAttribute("")]; [assembly:AssemblyConfigurationAttribute("")]; [assembly:AssemblyCompanyAttribute("Intel Corp")]; [assembly:AssemblyProductAttribute("PCMService")]; [assembly:AssemblyCopyrightAttribute("Copyright (c) Intel Corp 2010-2022")]; [assembly:AssemblyTrademarkAttribute("")]; [assembly:AssemblyCultureAttribute("")]; // // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the value or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly:AssemblyVersionAttribute("1.0.*")]; [assembly:ComVisible(false)]; [assembly:CLSCompliantAttribute(true)]; pcm-202502/src/windows/PCM-Service.exe.config000066400000000000000000000003431475730356400206500ustar00rootroot00000000000000 pcm-202502/src/windows/PCMInstaller.cpp000066400000000000000000000002261475730356400176650ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman */ #include "PCMInstaller.h" pcm-202502/src/windows/PCMInstaller.h000066400000000000000000000052131475730356400173330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman */ #pragma once using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Configuration::Install; namespace PMUService { [RunInstaller(true)] /// /// Summary for ProjectInstaller /// public ref class ProjectInstaller : public System::Configuration::Install::Installer { public: ProjectInstaller(void) { InitializeComponent(); // //TODO: Add the constructor code here // } protected: /// /// Clean up any resources being used. /// ~ProjectInstaller() { if (components) { delete components; } } private: System::ServiceProcess::ServiceProcessInstaller^ serviceProcessInstaller1; protected: private: System::ServiceProcess::ServiceInstaller^ serviceInstaller1; private: /// /// Required designer variable. /// System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// void InitializeComponent(void) { this->serviceProcessInstaller1 = (gcnew System::ServiceProcess::ServiceProcessInstaller()); this->serviceInstaller1 = (gcnew System::ServiceProcess::ServiceInstaller()); // // serviceProcessInstaller1 // this->serviceProcessInstaller1->Account = System::ServiceProcess::ServiceAccount::LocalSystem; this->serviceProcessInstaller1->Password = nullptr; this->serviceProcessInstaller1->Username = nullptr; // // serviceInstaller1 // this->serviceInstaller1->Description = L"This service provides performance counters for perfmon to show hardware events ov" L"er time such as Clockticks, Instruction Retired, Cache Misses and Memory Bandwi" L"dth."; this->serviceInstaller1->DisplayName = L"Intel(r) Performance Counter Monitor Service"; this->serviceInstaller1->ServiceName = L"PCMService"; this->serviceInstaller1->StartType = System::ServiceProcess::ServiceStartMode::Automatic; // // PCMInstaller // this->Installers->AddRange(gcnew cli::array< System::Configuration::Install::Installer^ >(2) {this->serviceProcessInstaller1, this->serviceInstaller1}); } #pragma endregion }; } pcm-202502/src/windows/PCMService.cpp000066400000000000000000000040251475730356400173310ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman and Roman Dementiev */ // PCMService.cpp : main Windows Service project file. #include "PCMService.h" #include #include #include using namespace PCMServiceNS; using namespace System::Text; using namespace System::Security::Policy; using namespace System::Reflection; //To install/uninstall the service, type: "PCM-Service.exe [-Install/-Uninstall]" int _tmain(int argc, _TCHAR* argv[]) { PCM_SET_DLL_DIR if (argc >= 2) { if (argv[1][0] == _T('/')) { argv[1][0] = _T('-'); } if (_tcsicmp(argv[1], _T("-Install")) == 0) { array^ myargs = System::Environment::GetCommandLineArgs(); array^ args = gcnew array(myargs->Length - 1); // Set args[0] with the full path to the assembly, Assembly^ assem = Assembly::GetExecutingAssembly(); args[0] = assem->Location; Array::Copy(myargs, 2, args, 1, args->Length - 1); AppDomain^ dom = AppDomain::CreateDomain(L"execDom"); Type^ type = System::Object::typeid; String^ path = type->Assembly->Location; StringBuilder^ sb = gcnew StringBuilder(path->Substring(0, path->LastIndexOf(L"\\"))); sb->Append(L"\\InstallUtil.exe"); dom->ExecuteAssembly(sb->ToString(), args); } else if (_tcsicmp(argv[1], _T("-Uninstall")) == 0) { array^ myargs = System::Environment::GetCommandLineArgs(); array^ args = gcnew array(2); args[0] = L"-u"; // Set args[0] with the full path to the assembly, Assembly^ assem = Assembly::GetExecutingAssembly(); args[1] = assem->Location; AppDomain^ dom = AppDomain::CreateDomain(L"execDom"); Type^ type = System::Object::typeid; String^ path = type->Assembly->Location; StringBuilder^ sb = gcnew StringBuilder(path->Substring(0, path->LastIndexOf(L"\\"))); sb->Append(L"\\InstallUtil.exe"); dom->ExecuteAssembly(sb->ToString(), args); } } else { ServiceBase::Run(gcnew PCMService); } } pcm-202502/src/windows/PCMService.h000077500000000000000000001560741475730356400170150ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman, Roman Dementiev */ #pragma once #pragma unmanaged #include "pcm-lib.h" #include "windriver.h" #include #ifndef UNICODE #include #include #endif #pragma managed using namespace pcm; using namespace System; using namespace System::Collections; using namespace System::ServiceProcess; using namespace System::ComponentModel; using namespace System::Diagnostics; using namespace System::Threading; using namespace System::Runtime::InteropServices; namespace PCMServiceNS { ref struct Globals { static initonly String^ ServiceName = gcnew String(L"PCMService"); }; ref struct CollectionInformation { CollectionInformation() { core = true; socket = true; qpi = true; } CollectionInformation(const CollectionInformation^ ©able) { core = copyable->core; socket = copyable->socket; qpi = copyable->qpi; } bool core; bool socket; bool qpi; }; ref class MeasureThread { public: MeasureThread(System::Diagnostics::EventLog^ log, int sampleRate, CollectionInformation^ collectionInformation) : log_(log), sampleRate_(sampleRate), collectionInformation_(collectionInformation) { // Get a Monitor instance which sets up the PMU, it also figures out the number of cores and sockets which we need later on to create the performance counters m_ = PCM::getInstance(); if ( !m_->good() ) { log_->WriteEntry(Globals::ServiceName, "Monitor Instance could not be created.", EventLogEntryType::Error); m_->cleanup(); String^ s = gcnew String(m_->getErrorMessage().c_str()); throw gcnew Exception(s); } log_->WriteEntry(Globals::ServiceName, "PCM: Number of cores detected: " + UInt32(m_->getNumCores()).ToString()); m_->program(); log_->WriteEntry(Globals::ServiceName, "PMU Programmed."); CountersQpi = gcnew String(L"PCM " + gcnew System::String(m_->xPI()) + L" Counters"); MetricQpiBand = gcnew String(gcnew System::String(m_->xPI()) + L" Link Bandwidth"); // This here will only create the necessary registry entries, the actual counters are created later. // New unified category if (PerformanceCounterCategory::Exists(CountersCore)) { PerformanceCounterCategory::Delete(CountersCore); } if (PerformanceCounterCategory::Exists(CountersSocket)) { PerformanceCounterCategory::Delete(CountersSocket); } if (PerformanceCounterCategory::Exists(CountersQpi)) { PerformanceCounterCategory::Delete(CountersQpi); } log_->WriteEntry(Globals::ServiceName, "Old categories deleted."); // First create the collection, then add counters to it so we add them all at once CounterCreationDataCollection^ counterCollection = gcnew CounterCreationDataCollection; // Here we add the counters one by one, need list of counters currently collected. // This is a stub: copy and paste when new counters are added to ipcustat "library". // CounterCreationData^ counter = gcnew CounterCreationData( "counter", "helptext for counter", PerformanceCounterType::NumberOfItems64 ); // counterCollection->Add( counter ); CounterCreationData^ counter; if (collectionInformation_->core) { counter = gcnew CounterCreationData(MetricCoreClocktick, "Displays the number of clockticks elapsed since previous measurement.", PerformanceCounterType::CounterDelta64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreRetired, "Displays the number of instructions retired since previous measurement.", PerformanceCounterType::CounterDelta64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreMissL2, "Displays the L2 Cache Misses caused by this core.", PerformanceCounterType::CounterDelta64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreMissL3, "Displays the L3 Cache Misses caused by this core.", PerformanceCounterType::CounterDelta64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreIpc, "Displays the instructions per clocktick executed for this core.", PerformanceCounterType::AverageCount64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreBaseIpc, "Not visible", PerformanceCounterType::AverageBase); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreFreqRel, "Displays the current frequency of the core to its rated frequency in percent.", PerformanceCounterType::SampleFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreFreqNom, "Not visible", PerformanceCounterType::SampleBase); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreHeadroom, "Displays temperature reading in 1 degree Celsius relative to the TjMax temperature. 0 corresponds to the max temperature.", PerformanceCounterType::NumberOfItems64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC0, "Displays the residency of core or socket in core C0-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC0Base, "", PerformanceCounterType::RawBase); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC1, "Displays the residency of core or socket in core C1-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC1Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricCoreResC3, "Displays the residency of core or socket in core C3-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC3Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricCoreResC6, "Displays the residency of core or socket in core C6-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC6Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricCoreResC7, "Displays the residency of core or socket in core C7-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricCoreResC7Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); PerformanceCounterCategory::Create(CountersCore, "Intel(r) Performance Counter Monitor", PerformanceCounterCategoryType::MultiInstance, counterCollection); } if (collectionInformation_->socket) { counterCollection->Clear(); counter = gcnew CounterCreationData(MetricSocketBandRead, "Displays the memory read bandwidth in bytes/s of this socket.", PerformanceCounterType::NumberOfItems64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketBandWrite, "Displays the memory write bandwidth in bytes/s of this socket.", PerformanceCounterType::NumberOfItems64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketEnergyPack, "Displays the energy in Joules consumed by this socket.", PerformanceCounterType::NumberOfItems64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketEnergyDram, "Displays the energy in Joules consumed by DRAM memory attached to the memory controller of this socket.", PerformanceCounterType::NumberOfItems64); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC0, "Displays the residency of socket in package C0-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC0Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC2, "Displays the residency of socket in package C2-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC2Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC3, "Displays the residency of socket in package C3-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC3Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC6, "Displays the residency of socket in package C6-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC6Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC7, "Displays the residency of socket in package C7-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC7Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC8, "Displays the residency of socket in package C8-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC8Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC9, "Displays the residency of socket in package C9-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC9Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); counter = gcnew CounterCreationData(MetricSocketResC10, "Displays the residency of socket in package C10-state in percent.", PerformanceCounterType::RawFraction); counterCollection->Add( counter ); counter = gcnew CounterCreationData(MetricSocketResC10Base, "", PerformanceCounterType::RawBase); counterCollection->Add(counter); PerformanceCounterCategory::Create(CountersSocket, "Intel(r) Performance Counter Monitor", PerformanceCounterCategoryType::MultiInstance, counterCollection); } if (collectionInformation_->qpi) { counterCollection->Clear(); counter = gcnew CounterCreationData(MetricQpiBand, L"Displays the incoming bandwidth in bytes/s of this " + gcnew System::String(m_->xPI()) + L" link", PerformanceCounterType::CounterDelta64); counterCollection->Add( counter ); PerformanceCounterCategory::Create(CountersQpi, "Intel(r) Performance Counter Monitor", PerformanceCounterCategoryType::MultiInstance, counterCollection); } log_->WriteEntry(Globals::ServiceName, "New categories added."); // Registry entries created, now we need to create the programmatic counters. For some things you may want one instance for every core/thread/socket/qpilink so create in a loop. // PerformanceCounter^ pc1 = gcnew PerformanceCounter( "SomeCounterName", "nameOfCounterAsEnteredInTheRegistry", "instanceNameOfCounterAsANumber" ); // Create #processors instances of the core specific performance counters String^ s; // Used for creating the instance name and the string to search for in the hashtable PerformanceCounter^ pc; for ( unsigned int i = 0; i < m_->getNumCores(); ++i ) { s = UInt32(i).ToString(); // For core counters we use just the number of the core if (collectionInformation_->core) { ticksHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreClocktick, s, false)); instRetHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreRetired, s, false)); l2CacheMissHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreMissL2, s, false)); l3CacheMissHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreMissL3, s, false)); ipcHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreIpc, s, false)); baseTicksForIpcHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreBaseIpc, s, false)); relFreqHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreFreqRel, s, false)); baseTicksForRelFreqHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreFreqNom, s, false)); thermalHeadroomHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreHeadroom, s, false)); CoreC0StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC0, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC0Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC1StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC1, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC1Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC3StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC3, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC3Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC6StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC6, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC6Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC7StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC7, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC7Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); } } // Create socket instances of the common core counters, names are Socket+number for ( unsigned int i=0; igetNumSockets(); ++i ) { s = "Socket"+UInt32(i).ToString(); if (collectionInformation_->core) { ticksHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreClocktick, s, false)); instRetHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreRetired, s, false)); l2CacheMissHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreMissL2, s, false)); l3CacheMissHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreMissL3, s, false)); ipcHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreIpc, s, false)); baseTicksForIpcHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreBaseIpc, s, false)); relFreqHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreFreqRel, s, false)); baseTicksForRelFreqHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreFreqNom, s, false)); thermalHeadroomHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreHeadroom, s, false)); CoreC0StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC0, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC0Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC1StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC1, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC1Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC3StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC3, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC3Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC6StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC6, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC6Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC7StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC7, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC7Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); } if (collectionInformation_->socket) { mrbHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketBandRead, s, false)); mwbHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketBandWrite, s, false)); packageEnergyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketEnergyPack, s, false)); DRAMEnergyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketEnergyDram, s, false)); PackageC0StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC0, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC0Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC2StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC2, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC2Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC3StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC3, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC3Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC6StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC6, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC6Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC7StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC7, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC7Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC8StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC8, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC8Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC9StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC9, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC9Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC10StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC10, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC10Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); } if (collectionInformation_->qpi) { qpiHash_.Add(s, gcnew PerformanceCounter(CountersQpi, MetricQpiBand, s, false)); // Socket aggregate String^ t; for ( unsigned int j=0; jgetQPILinksPerSocket(); ++j ) { t = s + "_Link" + UInt32(j).ToString(); qpiHash_.Add(t, gcnew PerformanceCounter(CountersQpi, MetricQpiBand, t, false)); } } } // Create #system instances of the system specific performance counters, just kidding, there is only one system so 1 instance s = "Total_"; if (collectionInformation_->core) { ticksHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreClocktick, s, false)); instRetHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreRetired, s, false)); l2CacheMissHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreMissL2, s, false)); l3CacheMissHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreMissL3, s, false)); ipcHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreIpc, s, false)); baseTicksForIpcHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreBaseIpc, s, false)); relFreqHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreFreqRel, s, false)); baseTicksForRelFreqHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreFreqNom, s, false)); thermalHeadroomHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreHeadroom, s, false)); CoreC0StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC0, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC0Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC1StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC1, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC1Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC3StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC3, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC3Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC6StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC6, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC6Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); CoreC7StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersCore, MetricCoreResC7, s, false)); pc = gcnew PerformanceCounter(CountersCore, MetricCoreResC7Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); } if (collectionInformation_->socket) { mrbHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketBandRead, s, false)); mwbHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketBandWrite, s, false)); packageEnergyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketEnergyPack, s, false)); DRAMEnergyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketEnergyDram, s, false)); PackageC0StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC0, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC0Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC2StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC2, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC2Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC3StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC3, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC3Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC6StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC6, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC6Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC7StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC7, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC7Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC8StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC8, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC8Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC9StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC9, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC9Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); PackageC10StateResidencyHash_.Add(s, gcnew PerformanceCounter(CountersSocket, MetricSocketResC10, s, false)); pc = gcnew PerformanceCounter(CountersSocket, MetricSocketResC10Base, s, false); pc->RawValue = 1000; baseArrayList_.Add(pc); } if (collectionInformation_->qpi) { qpiHash_.Add(s, gcnew PerformanceCounter(CountersQpi, MetricQpiBand, s, false)); } log_->WriteEntry(Globals::ServiceName, "All instances of the performance counter categories have been created."); } void doMeasurements( void ) { // FIXME: Do we care about hot swappability of CPUs? const size_t numSockets = m_->getNumSockets(); const size_t numCores = m_->getNumCores(); const size_t numQpiLinks = (size_t) m_->getQPILinksPerSocket(); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); // The structures SystemCounterState oldSystemState; std::vector oldSocketStates; std::vector oldCoreStates; SystemCounterState systemState; std::vector socketStates; std::vector coreStates; ULONGLONG BeforeTime = 0, AfterTime = 0; m_->getAllCounterStates(oldSystemState, oldSocketStates, oldCoreStates); BeforeTime = GetTickCount64(); // labmda functions are not allowed in managed code, using a macro #define toBW(val) (val * 1000ULL / (AfterTime - BeforeTime)) try { while (true) { Thread::Sleep(sampleRate_); // Fetch counter data here and store in the PerformanceCounter instances m_->getAllCounterStates(systemState, socketStates, coreStates); AfterTime = GetTickCount64(); // Set system performance counters String^ s = "Total_"; if (collectionInformation_->core) { __int64 totalTicks = getCycles(systemState); __int64 totalRefTicks = m_->getNominalFrequency() * numCores; __int64 totalInstr = getInstructionsRetired(systemState); ((PerformanceCounter^)ticksHash_[s])->RawValue = totalTicks; ((PerformanceCounter^)instRetHash_[s])->RawValue = totalInstr; ((PerformanceCounter^)l2CacheMissHash_[s])->IncrementBy(getL2CacheMisses(oldSystemState, systemState)); ((PerformanceCounter^)l3CacheMissHash_[s])->IncrementBy(getL3CacheMisses(oldSystemState, systemState)); ((PerformanceCounter^)ipcHash_[s])->RawValue = totalInstr >> 17; ((PerformanceCounter^)baseTicksForIpcHash_[s])->RawValue = totalTicks >> 17; ((PerformanceCounter^)relFreqHash_[s])->RawValue = totalTicks >> 17; ((PerformanceCounter^)baseTicksForRelFreqHash_[s])->IncrementBy(totalRefTicks >> 17); ((PerformanceCounter^)thermalHeadroomHash_[s])->RawValue = systemState.getThermalHeadroom(); ((PerformanceCounter^)CoreC0StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(0,oldSystemState, systemState)); ((PerformanceCounter^)CoreC1StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(1,oldSystemState, systemState)); ((PerformanceCounter^)CoreC3StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(3,oldSystemState, systemState)); ((PerformanceCounter^)CoreC6StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(6,oldSystemState, systemState)); ((PerformanceCounter^)CoreC7StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(7,oldSystemState, systemState)); //log_->WriteEntry(Globals::ServiceName, "Std: " + UInt64(totalTicks).ToString()); //log_->WriteEntry(Globals::ServiceName, "Ref: " + UInt64(totalRefTicks).ToString()); } if (collectionInformation_->socket) { ((PerformanceCounter^)mrbHash_[s])->RawValue = toBW(getBytesReadFromMC(oldSystemState, systemState)); ((PerformanceCounter^)mwbHash_[s])->RawValue = toBW(getBytesWrittenToMC(oldSystemState, systemState)); ((PerformanceCounter^)packageEnergyHash_[s])->RawValue = (__int64)getConsumedJoules(oldSystemState, systemState); ((PerformanceCounter^)DRAMEnergyHash_[s])->RawValue = (__int64)getDRAMConsumedJoules(oldSystemState, systemState); ((PerformanceCounter^)PackageC0StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 0, oldSystemState, systemState)); ((PerformanceCounter^)PackageC2StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 2, oldSystemState, systemState)); ((PerformanceCounter^)PackageC3StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 3, oldSystemState, systemState)); ((PerformanceCounter^)PackageC6StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 6, oldSystemState, systemState)); ((PerformanceCounter^)PackageC7StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 7, oldSystemState, systemState)); ((PerformanceCounter^)PackageC8StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 8, oldSystemState, systemState)); ((PerformanceCounter^)PackageC9StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 9, oldSystemState, systemState)); ((PerformanceCounter^)PackageC10StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency(10, oldSystemState, systemState)); } if (collectionInformation_->qpi) { ((PerformanceCounter^)qpiHash_[s])->RawValue = getAllIncomingQPILinkBytes(systemState); } // Set socket performance counters for ( unsigned int i = 0; i < numSockets; ++i ) { s = "Socket"+UInt32(i).ToString(); const SocketCounterState & socketState = socketStates[i]; if (collectionInformation_->core) { __int64 socketTicks = getCycles(socketState); __int64 socketRefTicks = m_->getNominalFrequency()* numCores / numSockets; __int64 socketInstr = getInstructionsRetired(socketState); ((PerformanceCounter^)instRetHash_[s])->RawValue = socketInstr; ((PerformanceCounter^)ipcHash_[s])->RawValue = socketInstr >> 17; ((PerformanceCounter^)l2CacheMissHash_[s])->IncrementBy(getL2CacheMisses(oldSocketStates[i], socketState)); ((PerformanceCounter^)l3CacheMissHash_[s])->IncrementBy(getL3CacheMisses(oldSocketStates[i], socketState)); ((PerformanceCounter^)ticksHash_[s])->RawValue = socketTicks; ((PerformanceCounter^)baseTicksForIpcHash_[s])->RawValue = socketTicks >> 17; ((PerformanceCounter^)relFreqHash_[s])->RawValue = socketTicks >> 17; ((PerformanceCounter^)baseTicksForRelFreqHash_[s])->IncrementBy(socketRefTicks >> 17); ((PerformanceCounter^)thermalHeadroomHash_[s])->RawValue = socketState.getThermalHeadroom(); ((PerformanceCounter^)CoreC0StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(0, oldSocketStates[i], socketState)); ((PerformanceCounter^)CoreC1StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(1, oldSocketStates[i], socketState)); ((PerformanceCounter^)CoreC3StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(3, oldSocketStates[i], socketState)); ((PerformanceCounter^)CoreC6StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(6, oldSocketStates[i], socketState)); ((PerformanceCounter^)CoreC7StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(7, oldSocketStates[i], socketState)); } if (collectionInformation_->socket) { ((PerformanceCounter^)mrbHash_[s])->RawValue = toBW(getBytesReadFromMC(oldSocketStates[i], socketState)); ((PerformanceCounter^)mwbHash_[s])->RawValue = toBW(getBytesWrittenToMC(oldSocketStates[i], socketState)); ((PerformanceCounter^)packageEnergyHash_[s])->RawValue = (__int64)getConsumedJoules(oldSocketStates[i], socketState); ((PerformanceCounter^)DRAMEnergyHash_[s])->RawValue = (__int64)getDRAMConsumedJoules(oldSocketStates[i], socketState); ((PerformanceCounter^)PackageC0StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 0,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC2StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 2,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC3StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 3,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC6StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 6,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC7StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 7,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC8StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 8,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC9StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency( 9,oldSocketStates[i], socketState)); ((PerformanceCounter^)PackageC10StateResidencyHash_[s])->RawValue = __int64(1000. * getPackageCStateResidency(10,oldSocketStates[i], socketState)); } if (collectionInformation_->qpi) { ((PerformanceCounter^)qpiHash_[s])->RawValue = toBW(getSocketIncomingQPILinkBytes(i, systemState)); String^ t; // and qpi link counters per socket for ( unsigned int j=0; jRawValue = toBW(getIncomingQPILinkBytes(i, j, systemState)); } } } // Set core performance counters for ( unsigned int i = 0; i < numCores; ++i ) { s = UInt32(i).ToString(); const CoreCounterState & coreState = coreStates[i]; if (collectionInformation_->core) { __int64 ticks = getCycles(coreState); __int64 refTicks = m_->getNominalFrequency(); __int64 instr = getInstructionsRetired(coreState); ((PerformanceCounter^)instRetHash_[s])->RawValue = instr; ((PerformanceCounter^)ipcHash_[s])->RawValue = instr >> 17; ((PerformanceCounter^)l2CacheMissHash_[s])->IncrementBy(getL2CacheMisses(oldCoreStates[i], coreState)); ((PerformanceCounter^)l3CacheMissHash_[s])->IncrementBy(getL3CacheMisses(oldCoreStates[i], coreState)); ((PerformanceCounter^)ticksHash_[s])->RawValue = ticks; ((PerformanceCounter^)baseTicksForIpcHash_[s])->RawValue = ticks >> 17; ((PerformanceCounter^)relFreqHash_[s])->RawValue = ticks >> 17; ((PerformanceCounter^)baseTicksForRelFreqHash_[s])->IncrementBy(refTicks >> 17); ((PerformanceCounter^)thermalHeadroomHash_[s])->RawValue = coreState.getThermalHeadroom(); ((PerformanceCounter^)CoreC0StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(0,oldCoreStates[i], coreState)); ((PerformanceCounter^)CoreC1StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(1,oldCoreStates[i], coreState)); ((PerformanceCounter^)CoreC3StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(3,oldCoreStates[i], coreState)); ((PerformanceCounter^)CoreC6StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(6,oldCoreStates[i], coreState)); ((PerformanceCounter^)CoreC7StateResidencyHash_[s])->RawValue = __int64(1000. * getCoreCStateResidency(7,oldCoreStates[i], coreState)); } } std::swap(oldSystemState, systemState); std::swap(oldSocketStates, socketStates); std::swap(oldCoreStates, coreStates); std::swap(BeforeTime, AfterTime); } } catch( ThreadAbortException^ ) { // We get here when for instance the service gets stopped or something bad happens. // In order to do our cleanup, like unprogram the MSRs, close the driver and such, in case we get stopped we want to execute normally after this // so this resets the abort and allows a normal exit Thread::ResetAbort(); } // Here we now have the chance to do cleanup after catching the ThreadAbortException because of the ResetAbort m_->cleanup(); } private: // Core counter hashtables System::Collections::Hashtable ticksHash_; System::Collections::Hashtable instRetHash_; System::Collections::Hashtable ipcHash_; System::Collections::Hashtable baseTicksForIpcHash_; System::Collections::Hashtable relFreqHash_; System::Collections::Hashtable baseTicksForRelFreqHash_; System::Collections::Hashtable l3CacheMissHash_; System::Collections::Hashtable l2CacheMissHash_; // Socket counter hashtables System::Collections::Hashtable mrbHash_; System::Collections::Hashtable mwbHash_; // QPI counter hashtables System::Collections::Hashtable qpiHash_; // Energy counters System::Collections::Hashtable packageEnergyHash_; System::Collections::Hashtable DRAMEnergyHash_; // Thermal headroom System::Collections::Hashtable thermalHeadroomHash_; // C-state Residencies System::Collections::Hashtable CoreC0StateResidencyHash_; System::Collections::Hashtable CoreC1StateResidencyHash_; System::Collections::Hashtable CoreC3StateResidencyHash_; System::Collections::Hashtable CoreC6StateResidencyHash_; System::Collections::Hashtable CoreC7StateResidencyHash_; System::Collections::Hashtable PackageC0StateResidencyHash_; System::Collections::Hashtable PackageC2StateResidencyHash_; System::Collections::Hashtable PackageC3StateResidencyHash_; System::Collections::Hashtable PackageC6StateResidencyHash_; System::Collections::Hashtable PackageC7StateResidencyHash_; System::Collections::Hashtable PackageC8StateResidencyHash_; System::Collections::Hashtable PackageC9StateResidencyHash_; System::Collections::Hashtable PackageC10StateResidencyHash_; System::Collections::ArrayList baseArrayList_; System::Diagnostics::EventLog^ log_; PCM* m_; // Counter variable names initonly String^ CountersCore = gcnew String(L"PCM Core Counters"); initonly String^ CountersSocket = gcnew String(L"PCM Socket Counters"); initonly String^ CountersQpi; initonly String^ MetricCoreClocktick = gcnew String(L"Clockticks"); initonly String^ MetricCoreRetired = gcnew String(L"Instructions Retired"); initonly String^ MetricCoreMissL2 = gcnew String(L"L2 Cache Misses"); initonly String^ MetricCoreMissL3 = gcnew String(L"L3 Cache Misses"); initonly String^ MetricCoreIpc = gcnew String(L"Instructions Per Clocktick (IPC)"); initonly String^ MetricCoreBaseIpc = gcnew String(L"Base ticks IPC"); initonly String^ MetricCoreFreqRel = gcnew String(L"Relative Frequency (%)"); initonly String^ MetricCoreFreqNom = gcnew String(L"Nominal Frequency"); initonly String^ MetricCoreHeadroom = gcnew String(L"Thermal Headroom below TjMax"); initonly String^ MetricCoreResC0 = gcnew String(L"core C0-state residency (%)"); initonly String^ MetricCoreResC1 = gcnew String(L"core C1-state residency (%)"); initonly String^ MetricCoreResC3 = gcnew String(L"core C3-state residency (%)"); initonly String^ MetricCoreResC6 = gcnew String(L"core C6-state residency (%)"); initonly String^ MetricCoreResC7 = gcnew String(L"core C7-state residency (%)"); initonly String^ MetricCoreResC0Base = gcnew String(L"core C0-state base"); initonly String^ MetricCoreResC1Base = gcnew String(L"core C1-state base"); initonly String^ MetricCoreResC3Base = gcnew String(L"core C3-state base"); initonly String^ MetricCoreResC6Base = gcnew String(L"core C6-state base"); initonly String^ MetricCoreResC7Base = gcnew String(L"core C7-state base"); initonly String^ MetricSocketBandRead = gcnew String(L"Memory Read Bandwidth"); initonly String^ MetricSocketBandWrite = gcnew String(L"Memory Write Bandwidth"); initonly String^ MetricSocketEnergyPack = gcnew String(L"Package/Socket Consumed Energy"); initonly String^ MetricSocketEnergyDram = gcnew String(L"DRAM/Memory Consumed Energy"); initonly String^ MetricSocketResC0 = gcnew String(L"package C0-state residency (%)"); initonly String^ MetricSocketResC2 = gcnew String(L"package C2-state residency (%)"); initonly String^ MetricSocketResC3 = gcnew String(L"package C3-state residency (%)"); initonly String^ MetricSocketResC6 = gcnew String(L"package C6-state residency (%)"); initonly String^ MetricSocketResC7 = gcnew String(L"package C7-state residency (%)"); initonly String^ MetricSocketResC8 = gcnew String(L"package C8-state residency (%)"); initonly String^ MetricSocketResC9 = gcnew String(L"package C9-state residency (%)"); initonly String^ MetricSocketResC10 = gcnew String(L"package C10-state residency (%)"); initonly String^ MetricSocketResC0Base = gcnew String(L"package C0-state base"); initonly String^ MetricSocketResC2Base = gcnew String(L"package C2-state base"); initonly String^ MetricSocketResC3Base = gcnew String(L"package C3-state base"); initonly String^ MetricSocketResC6Base = gcnew String(L"package C6-state base"); initonly String^ MetricSocketResC7Base = gcnew String(L"package C7-state base"); initonly String^ MetricSocketResC8Base = gcnew String(L"package C8-state base"); initonly String^ MetricSocketResC9Base = gcnew String(L"package C9-state base"); initonly String^ MetricSocketResC10Base = gcnew String(L"package C10-state base"); initonly String^ MetricQpiBand; // Configuration values const int sampleRate_; const CollectionInformation^ collectionInformation_; }; /// /// Summary for PMCService /// /// /// WARNING: If you change the name of this class, you will need to change the /// 'Resource File Name' property for the managed resource compiler tool /// associated with all .resx files this class depends on. Otherwise, /// the designers will not be able to interact properly with localized /// resources associated with this form. public ref class PCMService : public System::ServiceProcess::ServiceBase { [DllImport ("advapi32.dll")] static bool SetServiceStatus (IntPtr hServiceStatus, LPSERVICE_STATUS lpServiceStatus); private: void SetServiceFail (int ErrorCode) { SERVICE_STATUS ServiceStatus_; ServiceStatus_.dwCurrentState = (int)SERVICE_STOPPED; ServiceStatus_.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ServiceStatus_.dwWaitHint = 0; ServiceStatus_.dwWin32ExitCode = ErrorCode; ServiceStatus_.dwServiceSpecificExitCode = 0; ServiceStatus_.dwCheckPoint = 0; ServiceStatus_.dwControlsAccepted = 0 | (this->CanStop ? (int) SERVICE_ACCEPT_STOP : 0) | (this->CanShutdown ? (int) SERVICE_ACCEPT_SHUTDOWN : 0) | (this->CanPauseAndContinue ? (int) SERVICE_ACCEPT_PAUSE_CONTINUE : 0) | (this->CanHandleSessionChangeEvent ? (int) SERVICE_ACCEPT_SESSIONCHANGE : 0) | (this->CanHandlePowerEvent ? (int) SERVICE_ACCEPT_POWEREVENT : 0); SetServiceStatus (this->ServiceHandle, &ServiceStatus_); } public: PCMService() { InitializeComponent(); // //TODO: Add the constructor code here // } protected: /// /// Clean up any resources being used. /// ~PCMService() { if (components) { delete components; } } /// /// Set things in motion so your service can do its work. /// virtual void OnStart(array^ args) override { PCM* m_ = PCM::getInstance(); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); // Default values for configuration int sampleRate = 1000; CollectionInformation^ collectionInformation = gcnew CollectionInformation(); // Read configuration values from registry HKEY hkey; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\pcm\\service"), NULL, KEY_READ, &hkey)) { DWORD regDWORD = static_cast(REG_DWORD); DWORD lenDWORD = 32; DWORD sampleRateRead(0); if (ERROR_SUCCESS == RegQueryValueEx(hkey, TEXT("SampleRate"), NULL, NULL, reinterpret_cast(&sampleRateRead), &lenDWORD)) { sampleRate = (int)sampleRateRead; } DWORD collectCoreRead(0); if (ERROR_SUCCESS == RegQueryValueEx(hkey, TEXT("CollectCore"), NULL, NULL, reinterpret_cast(&collectCoreRead), &lenDWORD)) { collectionInformation->core = (int)collectCoreRead > 0; } DWORD collectSocketRead(0); if (ERROR_SUCCESS == RegQueryValueEx(hkey, TEXT("CollectSocket"), NULL, NULL, reinterpret_cast(&collectSocketRead), &lenDWORD)) { collectionInformation->socket = (int)collectSocketRead > 0; } DWORD collectQpiRead(0); if (ERROR_SUCCESS == RegQueryValueEx(hkey, TEXT("CollectQpi"), NULL, NULL, reinterpret_cast(&collectQpiRead), &lenDWORD)) { collectionInformation->qpi = (int)collectQpiRead > 0; } RegCloseKey(hkey); } this->RequestAdditionalTime(4000); // We should open the driver here EventLog->WriteEntry(Globals::ServiceName, "Trying to start the driver...", EventLogEntryType::Information); drv_ = new Driver; if (!drv_->start()) { #ifdef UNICODE const auto& driverPath = drv_->driverPath(); #else std::wstring_convert> char_to_wide; std::wstring driverPath = char_to_wide.from_bytes(drv_->driverPath().c_str()); #endif String^ s = gcnew String((L"Cannot open the driver.\nYou must have a signed driver at " + driverPath + L" and have administrator rights to run this program.\n\n").c_str()); EventLog->WriteEntry(Globals::ServiceName, s, EventLogEntryType::Error); SetServiceFail(ERROR_FILE_NOT_FOUND); throw gcnew Exception(s); } // TODO: Add code here to start your service. MeasureThread^ mt; EventLog->WriteEntry(Globals::ServiceName, "Trying to create the measure thread...", EventLogEntryType::Information); try { mt = gcnew MeasureThread(EventLog, sampleRate, collectionInformation); } catch (Exception^ e) { EventLog->WriteEntry(Globals::ServiceName, "Could not create MeasureThread, aborting", EventLogEntryType::Error); EventLog->WriteEntry(Globals::ServiceName, e->Message, EventLogEntryType::Error); SetServiceFail(0x80886); throw e; } // Create thread, pretty obvious comment here workerThread_ = gcnew Thread( gcnew ThreadStart( mt, &MeasureThread::doMeasurements ) ); // Start timer/thread to read out registers and fill performance counter structures workerThread_->Start(); // EventLog->WriteEntry("PCMService", System::DateTime::Now.ToLongTimeString() + " Monitor could not initialize PMU, aborting.", EventLogEntryType::Error); } /// /// Stop this service. /// virtual void OnStop() override { // TODO: Add code here to perform any tear-down necessary to stop your service. this->RequestAdditionalTime(4000); // Stop timer/thread // doMeasurements will do cleanup itself, might have to do some sanity checks here workerThread_->Abort(); drv_->stop(); } private: /// /// Required designer variable. /// System::ComponentModel::Container ^components; System::Threading::Thread^ workerThread_; Driver* drv_; #pragma region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// void InitializeComponent(void) { // // PCMService // this->CanPauseAndContinue = true; this->ServiceName = Globals::ServiceName; } #pragma endregion }; } pcm-202502/src/windows/PCM_cpp_ReadMe.txt000066400000000000000000000023411475730356400201230ustar00rootroot00000000000000======================================================================== CONSOLE APPLICATION : pcm Project Overview ======================================================================== AppWizard has created this pcm application for you. This file contains a summary of what you will find in each of the files that make up your pcm application. pcm.vcproj This is the main project file for VC++ projects generated using an Application Wizard. It contains information about the version of Visual C++ that generated the file, and information about the platforms, configurations, and project features selected with the Application Wizard. pcm.cpp This is the main application source file. ///////////////////////////////////////////////////////////////////////////// Other standard files: StdAfx.h, StdAfx.cpp These files are used to build a precompiled header (PCH) file named pcm.pch and a precompiled types file named StdAfx.obj. ///////////////////////////////////////////////////////////////////////////// Other notes: AppWizard uses "TODO:" comments to indicate parts of the source code you should add to or customize. ///////////////////////////////////////////////////////////////////////////// pcm-202502/src/windows/ReadMe_PCMService.txt000066400000000000000000000022301475730356400205770ustar00rootroot00000000000000======================================================================== APPLICATION : PMU Service Project Overview ======================================================================== Windows Service Wizard has created this PMU Service Application for you. This file contains a summary of what you will find in each of the files that make up your PMU Service application. PMU Service.vcproj This is the main project file for VC++ projects generated using a Windows Service Wizard. It contains information about the version of Visual C++ that generated the file, and information about the platforms, configurations. PMU ServiceWinService.cpp This is the main application source file. AssemblyInfo.cpp Contains custom attributes for modifying assembly metadata. ///////////////////////////////////////////////////////////////////////////// Other notes: Windows Service Wizard uses "TODO:" to indicate parts of the source code you should add to or customize. ///////////////////////////////////////////////////////////////////////////// To run your service: 1. Build the project 2. From the command line, run: PMU Service.exe -Install pcm-202502/src/windows/dllmain.cpp000066400000000000000000000010061475730356400170050ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman */ // dllmain.cpp : Defines the entry point for the DLL application. #include BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } pcm-202502/src/windows/pcm-core-win.cpp000066400000000000000000000001771475730356400176750ustar00rootroot00000000000000// pcm-core-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-core.cpp" pcm-202502/src/windows/pcm-iio-win.cpp000066400000000000000000000001751475730356400175230ustar00rootroot00000000000000// pcm-iio-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-iio.cpp" pcm-202502/src/windows/pcm-latency-win.cpp000066400000000000000000000002051475730356400203740ustar00rootroot00000000000000// pcm-latency-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-latency.cpp" pcm-202502/src/windows/pcm-lib.cpp000066400000000000000000000003421475730356400167120ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman */ // pcm-lib.cpp : Defines the exported functions for the DLL application. // #include "pcm-lib.h" pcm-202502/src/windows/pcm-lib.h000066400000000000000000000013451475730356400163630ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* ** Written by Otto Bruggeman */ // The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the PCM_EXPORTS // symbol defined on the command line. this symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // PCM_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef PCM_EXPORTS #define PCM_API __declspec(dllexport) #else #define PCM_API __declspec(dllimport) #endif #include "..\cpucounters.h" pcm-202502/src/windows/pcm-lspci-win.cpp000066400000000000000000000002011475730356400200430ustar00rootroot00000000000000// pcm-lspci-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-lspci.cpp" pcm-202502/src/windows/pcm-memory-win.cpp000066400000000000000000000002031475730356400202430ustar00rootroot00000000000000// pcm-memory-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-memory.cpp" pcm-202502/src/windows/pcm-mmio-win.cpp000066400000000000000000000001771475730356400177060ustar00rootroot00000000000000// pcm-mmio-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-mmio.cpp" pcm-202502/src/windows/pcm-msr-win.cpp000066400000000000000000000001751475730356400175440ustar00rootroot00000000000000// pcm-msr-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-msr.cpp" pcm-202502/src/windows/pcm-numa-win.cpp000066400000000000000000000001771475730356400177050ustar00rootroot00000000000000// pcm-numa-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-numa.cpp" pcm-202502/src/windows/pcm-pcicfg-win.cpp000066400000000000000000000002031475730356400201660ustar00rootroot00000000000000// pcm-pcicfg-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-pcicfg.cpp" pcm-202502/src/windows/pcm-pcie-win.cpp000066400000000000000000000001771475730356400176650ustar00rootroot00000000000000// pcm-pcie-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-pcie.cpp" pcm-202502/src/windows/pcm-power-win.cpp000066400000000000000000000002011475730356400200650ustar00rootroot00000000000000// pcm-power-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-power.cpp" pcm-202502/src/windows/pcm-raw-win.cpp000066400000000000000000000001751475730356400175340ustar00rootroot00000000000000// pcm-raw-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-raw.cpp" pcm-202502/src/windows/pcm-tsx-win.cpp000066400000000000000000000001751475730356400175610ustar00rootroot00000000000000// pcm-tsx-win.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm-tsx.cpp" pcm-202502/src/windows/pcm.cpp000066400000000000000000000003111475730356400161420ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // pcm.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "../pcm.cpp" pcm-202502/src/windows/pcm_lib_ReadMe.txt000066400000000000000000000023761475730356400202570ustar00rootroot00000000000000======================================================================== DYNAMIC LINK LIBRARY : PMU Library Project Overview ======================================================================== AppWizard has created this PMU Library DLL for you. This file contains a summary of what you will find in each of the files that make up your PMU Library application. PMU Library.vcproj This is the main project file for VC++ projects generated using an Application Wizard. It contains information about the version of Visual C++ that generated the file, and information about the platforms, configurations, and project features selected with the Application Wizard. PMU Library.cpp This is the main DLL source file. ///////////////////////////////////////////////////////////////////////////// Other standard files: StdAfx.h, StdAfx.cpp These files are used to build a precompiled header (PCH) file named PMU Library.pch and a precompiled types file named StdAfx.obj. ///////////////////////////////////////////////////////////////////////////// Other notes: AppWizard uses "TODO:" comments to indicate parts of the source code you should add to or customize. ///////////////////////////////////////////////////////////////////////////// pcm-202502/src/windows/pcm_power_ReadMe.txt000066400000000000000000000023411475730356400206350ustar00rootroot00000000000000======================================================================== CONSOLE APPLICATION : pcm Project Overview ======================================================================== AppWizard has created this pcm application for you. This file contains a summary of what you will find in each of the files that make up your pcm application. pcm.vcproj This is the main project file for VC++ projects generated using an Application Wizard. It contains information about the version of Visual C++ that generated the file, and information about the platforms, configurations, and project features selected with the Application Wizard. pcm.cpp This is the main application source file. ///////////////////////////////////////////////////////////////////////////// Other standard files: StdAfx.h, StdAfx.cpp These files are used to build a precompiled header (PCH) file named pcm.pch and a precompiled types file named StdAfx.obj. ///////////////////////////////////////////////////////////////////////////// Other notes: AppWizard uses "TODO:" comments to indicate parts of the source code you should add to or customize. ///////////////////////////////////////////////////////////////////////////// pcm-202502/src/windows/stdafx.cpp000066400000000000000000000005621475730356400166640ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // stdafx.cpp : source file that includes just the standard includes // pcm.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" // TODO: reference any additional headers you need in STDAFX.H // and not in this file pcm-202502/src/windows/stdafx.h000066400000000000000000000010771475730356400163330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation // stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently // #pragma once #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. #endif #include #include // TODO: reference additional headers your program requires here pcm-202502/src/windows/windriver.h000066400000000000000000000173071475730356400170560ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation #ifndef WINDRIVER_HEADER #define WINDRIVER_HEADER // contact: Roman Dementiev // WARNING: This driver code is only for testing purposes, not for production use // #include #include #include #include "../cpucounters.h" namespace pcm { /*! \file windriver.h \brief Loading and unloading custom Windows MSR (Model Specific Register) Driver */ extern void restrictDriverAccess(LPCTSTR path); /*! \brief Manage custom Windows MSR (Model Specific Register) Driver The driver is required to access hardware Model Specific Registers (MSRs) under Windows. Currently only 64-bit Windows 7 has been tested. */ class Driver { SC_HANDLE hSCManager{}; SC_HANDLE hService{}; SERVICE_STATUS ss{}; public: static tstring msrLocalPath() { tstring driverPath; DWORD driverPathLen = 1; DWORD gcdReturn = 0; do { if (0 != gcdReturn) { driverPathLen = gcdReturn; } driverPath.resize(driverPathLen); gcdReturn = GetCurrentDirectory(driverPathLen, &driverPath[0]); } while (0 != gcdReturn && driverPathLen < gcdReturn); removeNullTerminator(driverPath); return driverPath + TEXT("\\msr.sys"); } Driver() : Driver(TEXT("c:\\windows\\system32\\msr.sys")) { } Driver(const tstring& driverPath) : Driver(driverPath, TEXT("PCM MSR"), TEXT("PCM MSR Driver")) { } Driver(const tstring& driverPath, const tstring& driverName, const tstring& driverDescription) : driverPath_(setConfigValue(TEXT("DriverPath"), driverPath)), driverName_(setConfigValue(TEXT("DriverName"), driverName)), driverDescription_(setConfigValue(TEXT("DriverDescription"), driverDescription)) { } const tstring& driverPath() const { return driverPath_; } /*! \brief Installs and loads the driver Installs the driver if not installed and then loads it. \param driverPath full path to the driver \return true iff driver start up was successful */ bool start() { hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (hSCManager) { hService = CreateService(hSCManager, &driverName_[0], &driverDescription_[0], SERVICE_START | DELETE | SERVICE_STOP, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, &driverPath_[0], NULL, NULL, NULL, NULL, NULL); if (!hService) { hService = OpenService(hSCManager, &driverName_[0], SERVICE_START | DELETE | SERVICE_STOP); } if (hService) { if (0 != StartService(hService, 0, NULL)) { restrictDriverAccess(PCM_MSR_DRV_NAME); return true; } DWORD err = GetLastError(); if (err == ERROR_SERVICE_ALREADY_RUNNING) return true; std::wcerr << "Starting MSR service failed with error " << err << " "; const _com_error comError{ (int)err }; const TCHAR * errorStr = comError.ErrorMessage(); if (errorStr) std::wcerr << errorStr << "\n"; ControlService(hService, SERVICE_CONTROL_STOP, &ss); // DeleteService(hService); CloseServiceHandle(hService); } else { std::wcerr << "Opening service manager failed with error " << GetLastError() << " "; const _com_error comError{ (int)GetLastError() }; const TCHAR * errorStr = comError.ErrorMessage(); if (errorStr) std::wcerr << errorStr << "\n"; } CloseServiceHandle(hSCManager); } else { std::wcerr << "Opening service manager failed with error " << GetLastError() << " "; const _com_error comError{ (int)GetLastError() }; const TCHAR * errorStr = comError.ErrorMessage(); if (errorStr) std::wcerr << errorStr << "\n"; } #ifndef NO_WINRING // In cases where loading the WinRing0 driver is not desirable as a fallback to MSR.sys, add -DNO_WINRING to compile command to remove ability to load driver (will also remove initWinRing0Lib function) std::cerr << "Trying to load winring0.dll/winring0.sys driver...\n"; if(PCM::initWinRing0Lib()) { std::cerr << "Using winring0.dll/winring0.sys driver.\n\n"; return true; } else { std::cerr << "Failed to load winring0.dll/winring0.sys driver.\n\n"; } #endif // NO_WINRING return false; } //! \brief Stop and unload the driver void stop() { hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (hSCManager) { hService = OpenService(hSCManager, &driverName_[0], SERVICE_START | DELETE | SERVICE_STOP); if (hService) { ControlService(hService, SERVICE_CONTROL_STOP, &ss); CloseServiceHandle(hService); } CloseServiceHandle(hSCManager); } else { std::wcerr << "Opening service manager failed with error " << GetLastError() << " "; const _com_error comError{ (int)GetLastError() }; const TCHAR * errorStr = comError.ErrorMessage(); if (errorStr) std::wcerr << errorStr; } } /*! \brief Uninstall the driver Uninstalls the driver. For successeful uninstallation you need to reboot the system after calling this method. */ void uninstall() { hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (hSCManager) { hService = OpenService(hSCManager, &driverName_[0], SERVICE_START | DELETE | SERVICE_STOP); if (hService) { ControlService(hService, SERVICE_CONTROL_STOP, &ss); DeleteService(hService); CloseServiceHandle(hService); } CloseServiceHandle(hSCManager); } else { std::wcerr << "Opening service manager failed with error " << GetLastError() << " "; const _com_error comError{ (int)GetLastError() }; const TCHAR * errorStr = comError.ErrorMessage(); if (errorStr) std::wcerr << errorStr; } } private: static tstring setConfigValue(LPCTSTR key, const tstring& defaultValue) { tstring regRead; DWORD regLen = 1 * sizeof(TCHAR); DWORD regRes = ERROR_FILE_NOT_FOUND; // Safe error to start with in case key doesn't exist HKEY hKey; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\pcm"), NULL, KEY_READ, &hKey)) { do { regRead.resize(regLen / sizeof(TCHAR)); regRes = RegQueryValueEx(hKey, key, NULL, NULL, (LPBYTE)®Read[0], ®Len); } while (ERROR_MORE_DATA == regRes); RegCloseKey(hKey); } removeNullTerminator(regRead); return ERROR_SUCCESS == regRes ? regRead : defaultValue; } static void removeNullTerminator(tstring& s) { if (!s.empty() && s.back() == '\0') { s.pop_back(); } } const tstring driverName_; const tstring driverPath_; const tstring driverDescription_; }; } // namespace pcm #endif pcm-202502/src/winpmem/000077500000000000000000000000001475730356400146465ustar00rootroot00000000000000pcm-202502/src/winpmem/LICENSE.txt000066400000000000000000000261441475730356400165000ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2012 Michael Cohen Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.pcm-202502/src/winpmem/winpmem.cpp000066400000000000000000000116431475730356400170330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2009-2022, Intel Corporation /* Copyright 2012 Michael Cohen Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /******************************************************************** This is a single binary memory imager for Windows. Supported systems: - Windows XPSP2 to Windows 8 inclusive, both 32 bit and 64 bit. *********************************************************************/ #include "winpmem.h" #ifdef PCM_EXPORTS #define PCM_API __declspec(dllexport) #else #define PCM_API #endif namespace pcm { extern PCM_API void restrictDriverAccess(LPCTSTR path); int WinPmem::set_acquisition_mode(__int32 mode) { DWORD size; // Set the acquisition mode. if(!DeviceIoControl(fd_, PMEM_CTRL_IOCTRL, &mode, 4, NULL, 0, &size, NULL)) { LogError(TEXT("Failed to set acquisition mode.\n")); return -1; }; return 1; }; int WinPmem::toggle_write_mode() { DWORD size; // Set the acquisition mode. if (!DeviceIoControl(fd_, PMEM_WRITE_ENABLE, NULL, 0, NULL, 0, &size, NULL)) { LogError(TEXT("INFO: winpmem driver does not support write mode.\n")); return -1; }; return 1; }; WinPmem::WinPmem(): suppress_output(FALSE), fd_(INVALID_HANDLE_VALUE), out_fd_(INVALID_HANDLE_VALUE) { _tcscpy_s(service_name, PMEM_SERVICE_NAME); _tcscpy_s(last_error, TEXT("")); max_physical_memory_ = 0; } WinPmem::~WinPmem() { if (fd_ != INVALID_HANDLE_VALUE) { CloseHandle(fd_); } } void WinPmem::LogError(const TCHAR *message) { _tcsncpy_s(last_error, message, sizeof(last_error)); if (suppress_output) return; _tprintf(TEXT("%s"), message); }; void WinPmem::Log(const TCHAR *message, ...) { if (suppress_output) return; va_list ap; va_start(ap, message); _vtprintf(message, ap); va_end(ap); }; // Roman Dementiev (Intel): added delete_driver option (default is true) int WinPmem::install_driver(bool delete_driver) { SC_HANDLE scm, service; int status = -1; // Try to load the driver from the resource section. if (load_driver_() < 0) goto error; uninstall_driver(); scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (!scm) { LogError(TEXT("Can not open SCM. Are you administrator?")); goto error; } service = CreateService(scm, service_name, service_name, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, driver_filename, NULL, NULL, NULL, NULL, NULL); if (GetLastError() == ERROR_SERVICE_EXISTS) { CloseServiceHandle(service); service = OpenService(scm, service_name, SERVICE_ALL_ACCESS); } if (!service) { CloseServiceHandle(scm); goto error; }; if (!StartService(service, 0, NULL)) { if (GetLastError() != ERROR_SERVICE_ALREADY_RUNNING) { LogError(TEXT("Error: StartService(), Cannot start the driver.\n")); goto service_error; } } Log(TEXT("Loaded Driver %s.\n"), driver_filename); fd_ = CreateFile(TEXT("\\\\.\\") TEXT(PMEM_DEVICE_NAME), // Write is needed for IOCTL. GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(fd_ == INVALID_HANDLE_VALUE) { LogError(TEXT("Can not open raw device.")); status = -1; } else status = 1; service_error: CloseServiceHandle(service); CloseServiceHandle(scm); if(status == 1) restrictDriverAccess(TEXT("\\\\.\\") TEXT(PMEM_DEVICE_NAME)); if(delete_driver) DeleteFile(driver_filename); error: return status; } int WinPmem::uninstall_driver() { SC_HANDLE scm, service; SERVICE_STATUS ServiceStatus; scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (!scm) return 0; service = OpenService(scm, service_name, SERVICE_ALL_ACCESS); if (service) { ControlService(service, SERVICE_CONTROL_STOP, &ServiceStatus); }; DeleteService(service); CloseServiceHandle(service); Log(TEXT("Driver Unloaded.\n")); CloseServiceHandle(scm); return 1; } } // namespace pcm pcm-202502/src/winpmem/winpmem.h000066400000000000000000000051261475730356400164770ustar00rootroot00000000000000#include "windows.h" #include "stdio.h" #include "tchar.h" namespace pcm { // Executable version. static TCHAR version[] = TEXT("1.3. Built ") TEXT(__DATE__); #define PMEM_DEVICE_NAME "pmem" #define PMEM_SERVICE_NAME TEXT("winpmem") #define PAGE_SIZE 0x1000 class WinPmem { WinPmem(const WinPmem&) = delete; WinPmem& operator = (const WinPmem&) = delete; public: WinPmem(); virtual ~WinPmem(); virtual int install_driver(bool delete_driver = true); virtual int uninstall_driver(); virtual int set_acquisition_mode(__int32 mode); virtual int toggle_write_mode(); template void read(__int64 start, T & result) { LARGE_INTEGER large_start; DWORD bytes_read = 0; large_start.QuadPart = start; if (0xFFFFFFFF == SetFilePointer(fd_, (LONG)large_start.LowPart, &large_start.HighPart, FILE_BEGIN)) { LogError(TEXT("Failed to seek in the pmem device.\n")); return; } if (!ReadFile(fd_, &result, (DWORD)sizeof(T), &bytes_read, NULL)) { LogError(TEXT("Failed to read memory.")); } } template void write(__int64 start, T val) { LARGE_INTEGER large_start; DWORD bytes_written = 0; large_start.QuadPart = start; if (0xFFFFFFFF == SetFilePointer(fd_, (LONG)large_start.LowPart, &large_start.HighPart, FILE_BEGIN)) { LogError(TEXT("Failed to seek in the pmem device.\n")); return; } if (!WriteFile(fd_, &val, (DWORD)sizeof(T), &bytes_written, NULL)) { LogError(TEXT("Failed to write memory.")); } } // This is set if output should be suppressed (e.g. if we pipe the // image to the STDOUT). int suppress_output; TCHAR last_error[1024]; protected: virtual int load_driver_() = 0; virtual void LogError(const TCHAR *message); virtual void Log(const TCHAR *message, ...); // The file handle to the pmem device. HANDLE fd_; // The file handle to the image file. HANDLE out_fd_; // 256: The maximum length of Windows service name string. TCHAR service_name[256]; TCHAR driver_filename[MAX_PATH]; // This is the maximum size of memory calculated. __int64 max_physical_memory_; }; // This is the filename of the driver we drop. static TCHAR driver_filename[MAX_PATH]; // ioctl to get memory ranges from our driver. #define PMEM_CTRL_IOCTRL CTL_CODE(0x22, 0x101, 3, 3) #define PMEM_WRITE_ENABLE CTL_CODE(0x22, 0x102, 3, 3) #define PMEM_INFO_IOCTRL CTL_CODE(0x22, 0x103, 3, 3) // Available modes #define PMEM_MODE_IOSPACE 0 #define PMEM_MODE_PHYSICAL 1 } // namespace pcmpcm-202502/src/winring0/000077500000000000000000000000001475730356400147275ustar00rootroot00000000000000pcm-202502/src/winring0/COPYRIGHT.txt000066400000000000000000000023451475730356400170440ustar00rootroot00000000000000Copyright (c) 2007-2009 OpenLibSys.org. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pcm-202502/src/winring0/OlsApi.h000066400000000000000000000461341475730356400162770ustar00rootroot00000000000000//----------------------------------------------------------------------------- // Author : hiyohiyo // Mail : hiyohiyo@crystalmark.info // Web : http://openlibsys.org/ // License : The modified BSD license // // Copyright 2007-2009 OpenLibSys.org. All rights reserved. //----------------------------------------------------------------------------- // for WinRing0 1.3.x #pragma once /****************************************************************************** ** ** DLL Information ** ******************************************************************************/ //----------------------------------------------------------------------------- // GetDllStatus //----------------------------------------------------------------------------- DWORD // DLL Status, defined OLS_DLL_**** WINAPI GetDllStatus(); //----------------------------------------------------------------------------- // GetDllVersion //----------------------------------------------------------------------------- DWORD // DLL Version, defined OLS_VERSION WINAPI GetDllVersion( PBYTE major, // major version PBYTE minor, // minor version PBYTE revision, // revision PBYTE release // release/build ); //----------------------------------------------------------------------------- // GetDriverVersion //----------------------------------------------------------------------------- DWORD // Device Driver Version, defined OLS_DRIVER_VERSION WINAPI GetDriverVersion( PBYTE major, // major version PBYTE minor, // minor version PBYTE revision, // revision PBYTE release // release/build ); //----------------------------------------------------------------------------- // GetDriverType //----------------------------------------------------------------------------- DWORD // Device Driver Type, defined OLS_DRIVER_TYPE_**** WINAPI GetDriverType(); //----------------------------------------------------------------------------- // InitializeOls //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI InitializeOls(); //----------------------------------------------------------------------------- // DeinitializeOls //----------------------------------------------------------------------------- VOID WINAPI DeinitializeOls(); /****************************************************************************** ** ** CPU ** ******************************************************************************/ //----------------------------------------------------------------------------- // IsCpuid //----------------------------------------------------------------------------- BOOL // TRUE: support CPUID instruction, FALSE: not support CPUID instruction WINAPI IsCpuid(); //----------------------------------------------------------------------------- // IsMsr //----------------------------------------------------------------------------- BOOL // TRUE: support MSR(Model-Specific Register), FALSE: not support MSR WINAPI IsMsr(); //----------------------------------------------------------------------------- // IsTsc //----------------------------------------------------------------------------- BOOL // TRUE: support TSC(Time Stamp Counter), FALSE: not support TSC WINAPI IsTsc(); //----------------------------------------------------------------------------- // Rdmsr //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI Rdmsr( DWORD index, // MSR index PDWORD eax, // bit 0-31 PDWORD edx // bit 32-63 ); //----------------------------------------------------------------------------- // RdmsrTx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI RdmsrTx( DWORD index, // MSR index PDWORD eax, // bit 0-31 PDWORD edx, // bit 32-63 DWORD_PTR threadAffinityMask ); //----------------------------------------------------------------------------- // RdmsrPx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI RdmsrPx( DWORD index, // MSR index PDWORD eax, // bit 0-31 PDWORD edx, // bit 32-63 DWORD_PTR processAffinityMask ); //----------------------------------------------------------------------------- // Wrmsr //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI Wrmsr( DWORD index, // MSR index DWORD eax, // bit 0-31 DWORD edx // bit 32-63 ); //----------------------------------------------------------------------------- // WrmsrTx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WrmsrTx( DWORD index, // MSR index DWORD eax, // bit 0-31 DWORD edx, // bit 32-63 DWORD_PTR threadAffinityMask ); //----------------------------------------------------------------------------- // WrmsrPx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WrmsrPx( DWORD index, // MSR index DWORD eax, // bit 0-31 DWORD edx, // bit 32-63 DWORD_PTR processAffinityMask ); //----------------------------------------------------------------------------- // Rdpmc //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI Rdpmc( DWORD index, // PMC index PDWORD eax, // bit 0-31 PDWORD edx // bit 32-63 ); //----------------------------------------------------------------------------- // RdmsrTx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI RdpmcTx( DWORD index, // PMC index PDWORD eax, // bit 0-31 PDWORD edx, // bit 32-63 DWORD_PTR threadAffinityMask ); //----------------------------------------------------------------------------- // RdmsrPx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI RdpmcPx( DWORD index, // PMC index PDWORD eax, // bit 0-31 PDWORD edx, // bit 32-63 DWORD_PTR processAffinityMask ); //----------------------------------------------------------------------------- // Cpuid //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI Cpuid( DWORD index, // CPUID index PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx ); //----------------------------------------------------------------------------- // CpuidTx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI CpuidTx( DWORD index, // CPUID index PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx, DWORD_PTR threadAffinityMask ); //----------------------------------------------------------------------------- // CpuidPx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI CpuidPx( DWORD index, // CPUID index PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx, DWORD_PTR processAffinityMask ); //----------------------------------------------------------------------------- // Rdtsc //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI Rdtsc( PDWORD eax, // bit 0-31 PDWORD edx // bit 32-63 ); //----------------------------------------------------------------------------- // RdmsrTx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI RdtscTx( PDWORD eax, // bit 0-31 PDWORD edx, // bit 32-63 DWORD_PTR threadAffinityMask ); //----------------------------------------------------------------------------- // RdmsrPx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI RdtscPx( PDWORD eax, // bit 0-31 PDWORD edx, // bit 32-63 DWORD_PTR processAffinityMask ); //----------------------------------------------------------------------------- // Hlt //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI Hlt(); //----------------------------------------------------------------------------- // HltTx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI HltTx( DWORD_PTR threadAffinityMask ); //----------------------------------------------------------------------------- // HltPx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI HltPx( DWORD_PTR processAffinityMask ); /****************************************************************************** ** ** I/O ** ******************************************************************************/ //----------------------------------------------------------------------------- // ReadIoPortByte //----------------------------------------------------------------------------- BYTE // Read Value WINAPI ReadIoPortByte( WORD port // I/O port address ); //----------------------------------------------------------------------------- // ReadIoPortWord //----------------------------------------------------------------------------- WORD // Read Value WINAPI ReadIoPortWord( WORD port // I/O port address ); //----------------------------------------------------------------------------- // ReadIoPortDword //----------------------------------------------------------------------------- DWORD // Read Value WINAPI ReadIoPortDword( WORD port // I/O port address ); //----------------------------------------------------------------------------- // ReadIoPortByteEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI ReadIoPortByteEx( WORD port, // I/O port address PBYTE value // Read Value ); //----------------------------------------------------------------------------- // ReadIoPortWordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI ReadIoPortWordEx( WORD port, // I/O port address PWORD value // Read Value ); //----------------------------------------------------------------------------- // ReadIoPortDwordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI ReadIoPortDwordEx( WORD port, // I/O port address PDWORD value // Read Value ); //----------------------------------------------------------------------------- // WriteIoPortByte //----------------------------------------------------------------------------- VOID WINAPI WriteIoPortByte( WORD port, // I/O port address BYTE value // Write Value ); //----------------------------------------------------------------------------- // WriteIoPortDword //----------------------------------------------------------------------------- VOID WINAPI WriteIoPortDword( WORD port, // I/O port address DWORD value // Write Value ); //----------------------------------------------------------------------------- // WriteIoPortWord //----------------------------------------------------------------------------- VOID WINAPI WriteIoPortWord( WORD port, // I/O port address WORD value // Write Value ); //----------------------------------------------------------------------------- // WriteIoPortByteEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WriteIoPortByteEx( WORD port, // I/O port address BYTE value // Write Value ); //----------------------------------------------------------------------------- // WriteIoPortWordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WriteIoPortWordEx( WORD port, // I/O port address WORD value // Write Value ); //----------------------------------------------------------------------------- // WriteIoPortDwordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WriteIoPortDwordEx( WORD port, // I/O port address DWORD value // Write Value ); /****************************************************************************** ** ** PCI ** ******************************************************************************/ // pciAddress // 0- 2: Function Number // 3- 7: Device Number // 8-15: PCI Bus Number // 16-31: Reserved // 0xFFFFFFFF : Error //----------------------------------------------------------------------------- // SetPciMaxBusNo //----------------------------------------------------------------------------- VOID WINAPI SetPciMaxBusIndex( BYTE max // Max PCI Bus to Scan ); //----------------------------------------------------------------------------- // ReadPciConfigByte //----------------------------------------------------------------------------- BYTE // Read Value WINAPI ReadPciConfigByte( DWORD pciAddress, // PCI Device Address BYTE regAddress // Configuration Address 0-255 ); //----------------------------------------------------------------------------- // ReadPciConfigWord //----------------------------------------------------------------------------- WORD // Read Value WINAPI ReadPciConfigWord( DWORD pciAddress, // PCI Device Address BYTE regAddress // Configuration Address 0-255 ); //----------------------------------------------------------------------------- // ReadPciConfigDword //----------------------------------------------------------------------------- DWORD // Read Value WINAPI ReadPciConfigDword( DWORD pciAddress, // PCI Device Address BYTE regAddress // Configuration Address 0-255 ); //----------------------------------------------------------------------------- // ReadPciConfigByteEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI ReadPciConfigByteEx( DWORD pciAddress, // PCI Device Address DWORD regAddress, // Configuration Address 0-whatever PBYTE value // Read Value ); //----------------------------------------------------------------------------- // ReadPciConfigWordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI ReadPciConfigWordEx( DWORD pciAddress, // PCI Device Address DWORD regAddress, // Configuration Address 0-whatever PWORD value // Read Value ); //----------------------------------------------------------------------------- // ReadPciConfigDwordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI ReadPciConfigDwordEx( DWORD pciAddress, // PCI Device Address DWORD regAddress, // Configuration Address 0-whatever PDWORD value // Read Value ); //----------------------------------------------------------------------------- // WritePciConfigByte //----------------------------------------------------------------------------- VOID WINAPI WritePciConfigByte( DWORD pciAddress, // PCI Device Address BYTE regAddress, // Configuration Address 0-255 BYTE value // Write Value ); //----------------------------------------------------------------------------- // WritePciConfigWord //----------------------------------------------------------------------------- VOID WINAPI WritePciConfigWord( DWORD pciAddress, // PCI Device Address BYTE regAddress, // Configuration Address 0-255 WORD value // Write Value ); //----------------------------------------------------------------------------- // WritePciConfigDword //----------------------------------------------------------------------------- VOID WINAPI WritePciConfigDword( DWORD pciAddress, // PCI Device Address BYTE regAddress, // Configuration Address 0-255 DWORD value // Write Value ); //----------------------------------------------------------------------------- // WritePciConfigByteEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WritePciConfigByteEx( DWORD pciAddress, // PCI Device Address DWORD regAddress, // Configuration Address 0-whatever BYTE value // Write Value ); //----------------------------------------------------------------------------- // WritePciConfigWordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WritePciConfigWordEx( DWORD pciAddress, // PCI Device Address DWORD regAddress, // Configuration Address 0-whatever WORD value // Write Value ); //----------------------------------------------------------------------------- // WritePciConfigDwordEx //----------------------------------------------------------------------------- BOOL // TRUE: success, FALSE: failure WINAPI WritePciConfigDwordEx( DWORD pciAddress, // PCI Device Address DWORD regAddress, // Configuration Address 0-whatever DWORD value // Write Value ); //----------------------------------------------------------------------------- // FindPciDeviceById //----------------------------------------------------------------------------- DWORD // pciAddress, 0xFFFFFFFF: failure WINAPI FindPciDeviceById( WORD vendorId, // Vendor ID WORD deviceId, // Device ID BYTE index // Index ); //----------------------------------------------------------------------------- // FindPciDeviceByClass //----------------------------------------------------------------------------- DWORD // pciAddress, 0xFFFFFFFF: failure WINAPI FindPciDeviceByClass( BYTE baseClass, // Base Class BYTE subClass, // Sub Class BYTE programIf, // Program Interface BYTE index // Index ); /****************************************************************************** ** ** Memory (Special API) ** ******************************************************************************/ #ifdef _PHYSICAL_MEMORY_SUPPORT //----------------------------------------------------------------------------- // ReadDmiMemory //----------------------------------------------------------------------------- DWORD // Read size(byte), 0: failure WINAPI ReadDmiMemory( PBYTE buffer, // Buffer DWORD count, // Count DWORD unitSize // Unit Size (BYTE, WORD, DWORD) ); //----------------------------------------------------------------------------- // ReadPhysicalMemory //----------------------------------------------------------------------------- DWORD // Read size(byte), 0: failure WINAPI ReadPhysicalMemory( DWORD_PTR address, // Physical Memory Address PBYTE buffer, // Buffer DWORD count, // Count DWORD unitSize // Unit Size (BYTE, WORD, DWORD) ); //----------------------------------------------------------------------------- // WritePhysicalMemory //----------------------------------------------------------------------------- DWORD // Write size(byte), 0: failure WINAPI WritePhysicalMemory( DWORD_PTR address, // Physical Memory Address PBYTE buffer, // Buffer DWORD count, // Count DWORD unitSize // Unit Size (BYTE, WORD, DWORD) ); #endifpcm-202502/src/winring0/OlsApiInit.h000066400000000000000000000246161475730356400171240ustar00rootroot00000000000000//----------------------------------------------------------------------------- // Author : hiyohiyo // Mail : hiyohiyo@crystalmark.info // Web : http://openlibsys.org/ // License : The modified BSD license // // Copyright 2007-2009 OpenLibSys.org. All rights reserved. //----------------------------------------------------------------------------- // for WinRing0 1.3.x #pragma once #include "OlsDef.h" #include "OlsApiInitDef.h" //----------------------------------------------------------------------------- // // Prototypes // //----------------------------------------------------------------------------- BOOL InitOpenLibSys(HMODULE *hModule); BOOL DeinitOpenLibSys(HMODULE *hModule); //----------------------------------------------------------------------------- // // Functions // //----------------------------------------------------------------------------- // DLL _GetDllStatus GetDllStatus = NULL; _GetDllVersion GetDllVersion = NULL; _GetDriverVersion GetDriverVersion = NULL; _GetDriverType GetDriverType = NULL; _InitializeOls InitializeOls = NULL; _DeinitializeOls DeinitializeOls = NULL; // CPU _IsCpuid IsCpuid = NULL; _IsMsr IsMsr = NULL; _IsTsc IsTsc = NULL; _Hlt Hlt = NULL; _Rdmsr Rdmsr = NULL; _Wrmsr Wrmsr = NULL; _Rdpmc Rdpmc = NULL; _Cpuid Cpuid = NULL; _Rdtsc Rdtsc = NULL; _HltTx HltTx = NULL; _RdmsrTx RdmsrTx = NULL; _WrmsrTx WrmsrTx = NULL; _RdpmcTx RdpmcTx = NULL; _CpuidTx CpuidTx = NULL; _RdtscTx RdtscTx = NULL; _HltPx HltPx = NULL; _RdmsrPx RdmsrPx = NULL; _WrmsrPx WrmsrPx = NULL; _RdpmcPx RdpmcPx = NULL; _CpuidPx CpuidPx = NULL; _RdtscPx RdtscPx = NULL; // I/O _ReadIoPortByte ReadIoPortByte = NULL; _ReadIoPortWord ReadIoPortWord = NULL; _ReadIoPortDword ReadIoPortDword = NULL; _ReadIoPortByteEx ReadIoPortByteEx = NULL; _ReadIoPortWordEx ReadIoPortWordEx = NULL; _ReadIoPortDwordEx ReadIoPortDwordEx = NULL; _WriteIoPortByte WriteIoPortByte = NULL; _WriteIoPortWord WriteIoPortWord = NULL; _WriteIoPortDword WriteIoPortDword = NULL; _WriteIoPortByteEx WriteIoPortByteEx = NULL; _WriteIoPortWordEx WriteIoPortWordEx = NULL; _WriteIoPortDwordEx WriteIoPortDwordEx = NULL; // PCI _SetPciMaxBusIndex SetPciMaxBusIndex = NULL; _ReadPciConfigByte ReadPciConfigByte = NULL; _ReadPciConfigWord ReadPciConfigWord = NULL; _ReadPciConfigDword ReadPciConfigDword = NULL; _ReadPciConfigByteEx ReadPciConfigByteEx = NULL; _ReadPciConfigWordEx ReadPciConfigWordEx = NULL; _ReadPciConfigDwordEx ReadPciConfigDwordEx = NULL; _WritePciConfigByte WritePciConfigByte = NULL; _WritePciConfigWord WritePciConfigWord = NULL; _WritePciConfigDword WritePciConfigDword = NULL; _WritePciConfigByteEx WritePciConfigByteEx = NULL; _WritePciConfigWordEx WritePciConfigWordEx = NULL; _WritePciConfigDwordEx WritePciConfigDwordEx = NULL; _FindPciDeviceById FindPciDeviceById = NULL; _FindPciDeviceByClass FindPciDeviceByClass = NULL; // Memory #ifdef _PHYSICAL_MEMORY_SUPPORT _ReadDmiMemory ReadDmiMemory = NULL; _ReadPhysicalMemory ReadPhysicalMemory = NULL; _WritePhysicalMemory WritePhysicalMemory = NULL; #endif #ifdef _OPEN_LIB_SYS #ifdef _UNICODE #define GetOlsString GetOlsStringW #else #define GetOlsString GetOlsStringA #endif _InstallOpenLibSys InstallOpenLibSys = NULL; _UninstallOpenLibSys UninstallOpenLibSys = NULL; _GetDriverStatus GetDriverStatus = NULL; _GetOlsStringA GetOlsStringA = NULL; _GetOlsStringW GetOlsStringW = NULL; _GetOlsValue GetOlsValue = NULL; _SetOlsValue SetOlsValue = NULL; #endif //----------------------------------------------------------------------------- // // Initialize // //----------------------------------------------------------------------------- BOOL InitOpenLibSys(HMODULE *hModule) { TCHAR dll_path[MAX_PATH]; GetSystemDirectory(dll_path, MAX_PATH - 20); #ifdef _M_X64 _tcscat_s(dll_path, MAX_PATH, TEXT("\\WinRing0x64.dll")); #else _tcscat_s(dll_path, MAX_PATH, TEXT("\\WinRing0.dll")); #endif *hModule = LoadLibrary(dll_path); if(*hModule == NULL) { std::wcerr << "The dll could not be loaded from " << dll_path <<"\n"; return FALSE; } //----------------------------------------------------------------------------- // GetProcAddress //----------------------------------------------------------------------------- // DLL GetDllStatus = (_GetDllStatus) GetProcAddress (*hModule, "GetDllStatus"); GetDllVersion = (_GetDllVersion) GetProcAddress (*hModule, "GetDllVersion"); GetDriverVersion = (_GetDriverVersion) GetProcAddress (*hModule, "GetDriverVersion"); GetDriverType = (_GetDriverType) GetProcAddress (*hModule, "GetDriverType"); InitializeOls = (_InitializeOls) GetProcAddress (*hModule, "InitializeOls"); DeinitializeOls = (_DeinitializeOls) GetProcAddress (*hModule, "DeinitializeOls"); // CPU IsCpuid = (_IsCpuid) GetProcAddress (*hModule, "IsCpuid"); IsMsr = (_IsMsr) GetProcAddress (*hModule, "IsMsr"); IsTsc = (_IsTsc) GetProcAddress (*hModule, "IsTsc"); Hlt = (_Hlt) GetProcAddress (*hModule, "Hlt"); Rdmsr = (_Rdmsr) GetProcAddress (*hModule, "Rdmsr"); Wrmsr = (_Wrmsr) GetProcAddress (*hModule, "Wrmsr"); Rdpmc = (_Rdpmc) GetProcAddress (*hModule, "Rdpmc"); Cpuid = (_Cpuid) GetProcAddress (*hModule, "Cpuid"); Rdtsc = (_Rdtsc) GetProcAddress (*hModule, "Rdtsc"); HltTx = (_HltTx) GetProcAddress (*hModule, "HltTx"); RdmsrTx = (_RdmsrTx) GetProcAddress (*hModule, "RdmsrTx"); WrmsrTx = (_WrmsrTx) GetProcAddress (*hModule, "WrmsrTx"); RdpmcTx = (_RdpmcTx) GetProcAddress (*hModule, "RdpmcTx"); CpuidTx = (_CpuidTx) GetProcAddress (*hModule, "CpuidTx"); RdtscTx = (_RdtscTx) GetProcAddress (*hModule, "RdtscTx"); HltPx = (_HltPx) GetProcAddress (*hModule, "HltPx"); RdmsrPx = (_RdmsrPx) GetProcAddress (*hModule, "RdmsrPx"); WrmsrPx = (_WrmsrPx) GetProcAddress (*hModule, "WrmsrPx"); RdpmcPx = (_RdpmcPx) GetProcAddress (*hModule, "RdpmcPx"); CpuidPx = (_CpuidPx) GetProcAddress (*hModule, "CpuidPx"); RdtscPx = (_RdtscPx) GetProcAddress (*hModule, "RdtscPx"); // I/O ReadIoPortByte = (_ReadIoPortByte) GetProcAddress (*hModule, "ReadIoPortByte"); ReadIoPortWord = (_ReadIoPortWord) GetProcAddress (*hModule, "ReadIoPortWord"); ReadIoPortDword = (_ReadIoPortDword) GetProcAddress (*hModule, "ReadIoPortDword"); ReadIoPortByteEx = (_ReadIoPortByteEx) GetProcAddress (*hModule, "ReadIoPortByteEx"); ReadIoPortWordEx = (_ReadIoPortWordEx) GetProcAddress (*hModule, "ReadIoPortWordEx"); ReadIoPortDwordEx = (_ReadIoPortDwordEx) GetProcAddress (*hModule, "ReadIoPortDwordEx"); WriteIoPortByte = (_WriteIoPortByte) GetProcAddress (*hModule, "WriteIoPortByte"); WriteIoPortWord = (_WriteIoPortWord) GetProcAddress (*hModule, "WriteIoPortWord"); WriteIoPortDword = (_WriteIoPortDword) GetProcAddress (*hModule, "WriteIoPortDword"); WriteIoPortByteEx = (_WriteIoPortByteEx) GetProcAddress (*hModule, "WriteIoPortByteEx"); WriteIoPortWordEx = (_WriteIoPortWordEx) GetProcAddress (*hModule, "WriteIoPortWordEx"); WriteIoPortDwordEx = (_WriteIoPortDwordEx) GetProcAddress (*hModule, "WriteIoPortDwordEx"); // PCI SetPciMaxBusIndex = (_SetPciMaxBusIndex) GetProcAddress (*hModule, "SetPciMaxBusIndex"); ReadPciConfigByte = (_ReadPciConfigByte) GetProcAddress (*hModule, "ReadPciConfigByte"); ReadPciConfigWord = (_ReadPciConfigWord) GetProcAddress (*hModule, "ReadPciConfigWord"); ReadPciConfigDword = (_ReadPciConfigDword) GetProcAddress (*hModule, "ReadPciConfigDword"); ReadPciConfigByteEx = (_ReadPciConfigByteEx) GetProcAddress (*hModule, "ReadPciConfigByteEx"); ReadPciConfigWordEx = (_ReadPciConfigWordEx) GetProcAddress (*hModule, "ReadPciConfigWordEx"); ReadPciConfigDwordEx = (_ReadPciConfigDwordEx) GetProcAddress (*hModule, "ReadPciConfigDwordEx"); WritePciConfigByte = (_WritePciConfigByte) GetProcAddress (*hModule, "WritePciConfigByte"); WritePciConfigWord = (_WritePciConfigWord) GetProcAddress (*hModule, "WritePciConfigWord"); WritePciConfigDword = (_WritePciConfigDword) GetProcAddress (*hModule, "WritePciConfigDword"); WritePciConfigByteEx = (_WritePciConfigByteEx) GetProcAddress (*hModule, "WritePciConfigByteEx"); WritePciConfigWordEx = (_WritePciConfigWordEx) GetProcAddress (*hModule, "WritePciConfigWordEx"); WritePciConfigDwordEx = (_WritePciConfigDwordEx)GetProcAddress (*hModule, "WritePciConfigDwordEx"); FindPciDeviceById = (_FindPciDeviceById) GetProcAddress (*hModule, "FindPciDeviceById"); FindPciDeviceByClass = (_FindPciDeviceByClass) GetProcAddress (*hModule, "FindPciDeviceByClass"); // Memory #ifdef _PHYSICAL_MEMORY_SUPPORT ReadDmiMemory = (_ReadDmiMemory) GetProcAddress (*hModule, "ReadDmiMemory"); ReadPhysicalMemory = (_ReadPhysicalMemory) GetProcAddress (*hModule, "ReadPhysicalMemory"); WritePhysicalMemory = (_WritePhysicalMemory) GetProcAddress (*hModule, "WritePhysicalMemory"); #endif //----------------------------------------------------------------------------- // Check Functions //----------------------------------------------------------------------------- if(!( GetDllStatus && GetDllVersion && GetDriverVersion && GetDriverType && InitializeOls && DeinitializeOls && IsCpuid && IsMsr && IsTsc && Hlt && HltTx && HltPx && Rdmsr && RdmsrTx && RdmsrPx && Wrmsr && WrmsrTx && WrmsrPx && Rdpmc && RdpmcTx && RdpmcPx && Cpuid && CpuidTx && CpuidPx && Rdtsc && RdtscTx && RdtscPx && ReadIoPortByte && ReadIoPortWord && ReadIoPortDword && ReadIoPortByteEx && ReadIoPortWordEx && ReadIoPortDwordEx && WriteIoPortByte && WriteIoPortWord && WriteIoPortDword && WriteIoPortByteEx && WriteIoPortWordEx && WriteIoPortDwordEx && SetPciMaxBusIndex && ReadPciConfigByte && ReadPciConfigWord && ReadPciConfigDword && ReadPciConfigByteEx && ReadPciConfigWordEx && ReadPciConfigDwordEx && WritePciConfigByte && WritePciConfigWord && WritePciConfigDword && WritePciConfigByteEx && WritePciConfigWordEx && WritePciConfigDwordEx && FindPciDeviceById && FindPciDeviceByClass #ifdef _PHYSICAL_MEMORY_SUPPORT && ReadDmiMemory && ReadPhysicalMemory && WritePhysicalMemory #endif )) { return FALSE; } return InitializeOls(); } //----------------------------------------------------------------------------- // // Deinitialize // //----------------------------------------------------------------------------- BOOL DeinitOpenLibSys(HMODULE *hModule) { BOOL result = FALSE; if(*hModule == NULL) { return TRUE; } else { DeinitializeOls(); result = FreeLibrary(*hModule); *hModule = NULL; return result; } } pcm-202502/src/winring0/OlsApiInitDef.h000066400000000000000000000120761475730356400175400ustar00rootroot00000000000000//----------------------------------------------------------------------------- // Author : hiyohiyo // Mail : hiyohiyo@crystalmark.info // Web : http://openlibsys.org/ // License : The modified BSD license // // Copyright 2007-2009 OpenLibSys.org. All rights reserved. //----------------------------------------------------------------------------- // for WinRing0 1.3.x #pragma once //----------------------------------------------------------------------------- // // Type Defines // //----------------------------------------------------------------------------- // DLL typedef DWORD (WINAPI *_GetDllStatus) (); typedef DWORD (WINAPI *_GetDllVersion) (PBYTE major, PBYTE minor, PBYTE revision, PBYTE release); typedef DWORD (WINAPI *_GetDriverVersion) (PBYTE major, PBYTE minor, PBYTE revision, PBYTE release); typedef DWORD (WINAPI *_GetDriverType) (); typedef BOOL (WINAPI *_InitializeOls) (); typedef VOID (WINAPI *_DeinitializeOls) (); // CPU typedef BOOL (WINAPI *_IsCpuid) (); typedef BOOL (WINAPI *_IsMsr) (); typedef BOOL (WINAPI *_IsTsc) (); typedef BOOL (WINAPI *_Hlt) (); typedef DWORD (WINAPI *_Rdmsr) (DWORD index, PDWORD eax, PDWORD edx); typedef DWORD (WINAPI *_Wrmsr) (DWORD index, DWORD eax, DWORD edx); typedef DWORD (WINAPI *_Rdpmc) (DWORD index, PDWORD eax, PDWORD edx); typedef DWORD (WINAPI *_Cpuid) (DWORD index, PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx); typedef DWORD (WINAPI *_Rdtsc) (PDWORD eax, PDWORD edx); typedef BOOL (WINAPI *_HltTx) (DWORD_PTR threadAffinityMask); typedef DWORD (WINAPI *_RdmsrTx) (DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR threadAffinityMask); typedef DWORD (WINAPI *_WrmsrTx) (DWORD index, DWORD eax, DWORD edx, DWORD_PTR threadAffinityMask); typedef DWORD (WINAPI *_RdpmcTx) (DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR threadAffinityMask); typedef DWORD (WINAPI *_CpuidTx) (DWORD index, PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx, DWORD_PTR threadAffinityMask); typedef DWORD (WINAPI *_RdtscTx) (PDWORD eax, PDWORD edx, DWORD_PTR threadAffinityMask); typedef BOOL (WINAPI *_HltPx) (DWORD_PTR processAffinityMask); typedef DWORD (WINAPI *_RdmsrPx) (DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR processAffinityMask); typedef DWORD (WINAPI *_WrmsrPx) (DWORD index, DWORD eax, DWORD edx, DWORD_PTR processAffinityMask); typedef DWORD (WINAPI *_RdpmcPx) (DWORD index, PDWORD eax, PDWORD edx, DWORD_PTR processAffinityMask); typedef DWORD (WINAPI *_CpuidPx) (DWORD index, PDWORD eax, PDWORD ebx, PDWORD ecx, PDWORD edx, DWORD_PTR processAffinityMask); typedef DWORD (WINAPI *_RdtscPx) (PDWORD eax, PDWORD edx, DWORD_PTR processAffinityMask); // I/O typedef BYTE (WINAPI *_ReadIoPortByte) (WORD address); typedef WORD (WINAPI *_ReadIoPortWord) (WORD address); typedef DWORD (WINAPI *_ReadIoPortDword) (WORD address); typedef BOOL (WINAPI *_ReadIoPortByteEx) (WORD address, PBYTE value); typedef BOOL (WINAPI *_ReadIoPortWordEx) (WORD address, PWORD value); typedef BOOL (WINAPI *_ReadIoPortDwordEx) (WORD address, PDWORD value); typedef VOID (WINAPI *_WriteIoPortByte) (WORD address, BYTE value); typedef VOID (WINAPI *_WriteIoPortWord) (WORD address, WORD value); typedef VOID (WINAPI *_WriteIoPortDword) (WORD address, DWORD value); typedef BOOL (WINAPI *_WriteIoPortByteEx) (WORD address, BYTE value); typedef BOOL (WINAPI *_WriteIoPortWordEx) (WORD address, WORD value); typedef BOOL (WINAPI *_WriteIoPortDwordEx) (WORD address, DWORD value); // PCI typedef VOID (WINAPI *_SetPciMaxBusIndex) (BYTE max); typedef BYTE (WINAPI *_ReadPciConfigByte) (DWORD pciAddress, BYTE regAddress); typedef WORD (WINAPI *_ReadPciConfigWord) (DWORD pciAddress, BYTE regAddress); typedef DWORD (WINAPI *_ReadPciConfigDword) (DWORD pciAddress, BYTE regAddress); typedef BOOL (WINAPI *_ReadPciConfigByteEx) (DWORD pciAddress, DWORD regAddress, PBYTE value); typedef BOOL (WINAPI *_ReadPciConfigWordEx) (DWORD pciAddress, DWORD regAddress, PWORD value); typedef BOOL (WINAPI *_ReadPciConfigDwordEx) (DWORD pciAddress, DWORD regAddress, PDWORD value); typedef VOID (WINAPI *_WritePciConfigByte) (DWORD pciAddress, BYTE regAddress, BYTE value); typedef VOID (WINAPI *_WritePciConfigWord) (DWORD pciAddress, BYTE regAddress, WORD value); typedef VOID (WINAPI *_WritePciConfigDword) (DWORD pciAddress, BYTE regAddress, DWORD value); typedef BOOL (WINAPI *_WritePciConfigByteEx) (DWORD pciAddress, DWORD regAddress, BYTE value); typedef BOOL (WINAPI *_WritePciConfigWordEx) (DWORD pciAddress, DWORD regAddress, WORD value); typedef BOOL (WINAPI *_WritePciConfigDwordEx) (DWORD pciAddress, DWORD regAddress, DWORD value); typedef DWORD (WINAPI *_FindPciDeviceById) (WORD vendorId, WORD deviceId, BYTE index); typedef DWORD (WINAPI *_FindPciDeviceByClass) (BYTE baseClass, BYTE subClass, BYTE programIf, BYTE index); // Memory #ifdef _PHYSICAL_MEMORY_SUPPORT typedef DWORD (WINAPI *_ReadDmiMemory) (PBYTE buffer, DWORD count, DWORD unitSize); typedef DWORD (WINAPI *_ReadPhysicalMemory) (DWORD_PTR address, PBYTE buffer, DWORD count, DWORD unitSize); typedef DWORD (WINAPI *_WritePhysicalMemory) (DWORD_PTR address, PBYTE buffer, DWORD count, DWORD unitSize); #endif pcm-202502/src/winring0/OlsApiInitExt.h000066400000000000000000000053671475730356400176070ustar00rootroot00000000000000//----------------------------------------------------------------------------- // Author : hiyohiyo // Mail : hiyohiyo@crystalmark.info // Web : http://openlibsys.org/ // License : The modified BSD license // // Copyright 2007-2009 OpenLibSys.org. All rights reserved. //----------------------------------------------------------------------------- // for WinRing0 1.3.x #pragma once #include "OlsApiInitDef.h" //----------------------------------------------------------------------------- // // Externs // //----------------------------------------------------------------------------- // DLL extern _GetDllStatus GetDllStatus; extern _GetDllVersion GetDllVersion; extern _GetDriverVersion GetDriverVersion; extern _GetDriverType GetDriverType; extern _InitializeOls InitializeOls; extern _DeinitializeOls DeinitializeOls; // CPU extern _IsCpuid IsCpuid; extern _IsMsr IsMsr; extern _IsTsc IsTsc; extern _Hlt Hlt; extern _Rdmsr Rdmsr; extern _Wrmsr Wrmsr; extern _Rdpmc Rdpmc; extern _Cpuid Cpuid; extern _Rdtsc Rdtsc; extern _HltTx HltTx; extern _RdmsrTx RdmsrTx; extern _WrmsrTx WrmsrTx; extern _RdpmcTx RdpmcTx; extern _CpuidTx CpuidTx; extern _RdtscTx RdtscTx; extern _HltPx HltPx; extern _RdmsrPx RdmsrPx; extern _WrmsrPx WrmsrPx; extern _RdpmcPx RdpmcPx; extern _CpuidPx CpuidPx; extern _RdtscPx RdtscPx; // I/O extern _ReadIoPortByte ReadIoPortByte; extern _ReadIoPortWord ReadIoPortWord; extern _ReadIoPortDword ReadIoPortDword; extern _ReadIoPortByteEx ReadIoPortByteEx; extern _ReadIoPortWordEx ReadIoPortWordEx; extern _ReadIoPortDwordEx ReadIoPortDwordEx; extern _WriteIoPortByte WriteIoPortByte; extern _WriteIoPortWord WriteIoPortWord; extern _WriteIoPortDword WriteIoPortDword; extern _WriteIoPortByteEx WriteIoPortByteEx; extern _WriteIoPortWordEx WriteIoPortWordEx; extern _WriteIoPortDwordEx WriteIoPortDwordEx; // PCI extern _SetPciMaxBusIndex SetPciMaxBusIndex; extern _ReadPciConfigByte ReadPciConfigByte; extern _ReadPciConfigWord ReadPciConfigWord; extern _ReadPciConfigDword ReadPciConfigDword; extern _ReadPciConfigByteEx ReadPciConfigByteEx; extern _ReadPciConfigWordEx ReadPciConfigWordEx; extern _ReadPciConfigDwordEx ReadPciConfigDwordEx; extern _WritePciConfigByte WritePciConfigByte; extern _WritePciConfigWord WritePciConfigWord; extern _WritePciConfigDword WritePciConfigDword; extern _WritePciConfigByteEx WritePciConfigByteEx; extern _WritePciConfigWordEx WritePciConfigWordEx; extern _WritePciConfigDwordEx WritePciConfigDwordEx; extern _FindPciDeviceById FindPciDeviceById; extern _FindPciDeviceByClass FindPciDeviceByClass; // Memory #ifdef _PHYSICAL_MEMORY_SUPPORT extern _ReadDmiMemory ReadDmiMemory; extern _ReadPhysicalMemory ReadPhysicalMemory; extern _WritePhysicalMemory WritePhysicalMemory; #endif pcm-202502/src/winring0/OlsDef.h000066400000000000000000000043431475730356400162600ustar00rootroot00000000000000//----------------------------------------------------------------------------- // Author : hiyohiyo // Mail : hiyohiyo@crystalmark.info // Web : http://openlibsys.org/ // License : The modified BSD license // // Copyright 2007 OpenLibSys.org. All rights reserved. //----------------------------------------------------------------------------- #pragma once //----------------------------------------------------------------------------- // // DLL Status Code // //----------------------------------------------------------------------------- #define OLS_DLL_NO_ERROR 0 #define OLS_DLL_UNSUPPORTED_PLATFORM 1 #define OLS_DLL_DRIVER_NOT_LOADED 2 #define OLS_DLL_DRIVER_NOT_FOUND 3 #define OLS_DLL_DRIVER_UNLOADED 4 #define OLS_DLL_DRIVER_NOT_LOADED_ON_NETWORK 5 #define OLS_DLL_UNKNOWN_ERROR 9 //----------------------------------------------------------------------------- // // Driver Type // //----------------------------------------------------------------------------- #define OLS_DRIVER_TYPE_UNKNOWN 0 #define OLS_DRIVER_TYPE_WIN_9X 1 #define OLS_DRIVER_TYPE_WIN_NT 2 #define OLS_DRIVER_TYPE_WIN_NT4 3 // Obsolete #define OLS_DRIVER_TYPE_WIN_NT_X64 4 #define OLS_DRIVER_TYPE_WIN_NT_IA64 5 // Reserved //----------------------------------------------------------------------------- // // PCI Error Code // //----------------------------------------------------------------------------- #define OLS_ERROR_PCI_BUS_NOT_EXIST (0xE0000001L) #define OLS_ERROR_PCI_NO_DEVICE (0xE0000002L) #define OLS_ERROR_PCI_WRITE_CONFIG (0xE0000003L) #define OLS_ERROR_PCI_READ_CONFIG (0xE0000004L) //----------------------------------------------------------------------------- // // Support Macros // //----------------------------------------------------------------------------- // Bus Number, Device Number and Function Number to PCI Device Address #define PciBusDevFunc(Bus, Dev, Func) ((Bus&0xFF)<<8) | ((Dev&0x1F)<<3) | (Func&7) // PCI Device Address to Bus Number #define PciGetBus(address) ((address>>8) & 0xFF) // PCI Device Address to Device Number #define PciGetDev(address) ((address>>3) & 0x1F) // PCI Device Address to Function Number #define PciGetFunc(address) (address&7) pcm-202502/tests/000077500000000000000000000000001475730356400135455ustar00rootroot00000000000000pcm-202502/tests/CMakeLists.txt000066400000000000000000000027671475730356400163210ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2022-2024, Intel Corporation set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/tests) if(UNIX) # daemon_alignment_test on Linux and Unix file(GLOB TEST_FILE daemon_alignment_test.cpp pcm-accel-common.cpp) add_executable(daemon_alignment_test ${TEST_FILE}) target_link_libraries(daemon_alignment_test) # PCM_STATIC + pcm_sensor = urltest if(LINUX) add_executable(urltest urltest.cpp) target_link_libraries(urltest Threads::Threads PCM_STATIC) endif(LINUX) endif(UNIX) if(PCM_FUZZ) find_package(OpenSSL REQUIRED) set(SSL_LIBS OpenSSL::SSL OpenSSL::Crypto) add_executable(urltest-fuzz urltest-fuzz.cpp) add_executable(pcm-sensor-server-fuzz pcm-sensor-server-fuzz.cpp) add_executable(pcm-sensor-server-ssl-fuzz pcm-sensor-server-fuzz.cpp) target_compile_options(pcm-sensor-server-fuzz PRIVATE -DUSE_SSL=1) target_compile_options(pcm-sensor-server-ssl-fuzz PRIVATE -DUSE_SSL=1 -DFUZZ_USE_SSL=1) add_executable(pcm-fuzz pcm-fuzz.cpp) add_executable(pcm-memory-fuzz pcm-memory-fuzz.cpp) target_link_libraries(urltest-fuzz Threads::Threads PCM_STATIC) target_link_libraries(pcm-sensor-server-fuzz Threads::Threads PCM_STATIC ${SSL_LIBS}) target_link_libraries(pcm-sensor-server-ssl-fuzz Threads::Threads PCM_STATIC ${SSL_LIBS}) target_link_libraries(pcm-fuzz Threads::Threads PCM_STATIC) target_link_libraries(pcm-memory-fuzz Threads::Threads PCM_STATIC) endif() pcm-202502/tests/daemon_alignment_test.cpp000066400000000000000000000043741475730356400206210ustar00rootroot00000000000000#include #include #include #include #include "../src/daemon/common.h" #include "../src/utils.h" #define ALIGNMENT 64 void checkAlignment(char const * debugMessage, void* ptr) { printf("Checking: %-20s\t\t", debugMessage); uint64_t currentAlignment = (uint64_t)ptr % ALIGNMENT; if(currentAlignment != 0) { printf("Failed\n"); printf("Current alignment: %llu\n\n", (unsigned long long)currentAlignment); exit(EXIT_FAILURE); } else { printf("Passed\n"); } } int main() { printf("Testing alignment\n\n"); PCMDaemon::SharedPCMState* pcmState = (PCMDaemon::SharedPCMState*)aligned_alloc(ALIGNMENT, sizeof(PCMDaemon::SharedPCMState)); if (pcmState == nullptr) { printf("Memory allocation failed\n\n"); exit(EXIT_FAILURE); } std::fill((char*)pcmState, ((char*)pcmState) + sizeof(PCMDaemon::SharedPCMState), 0); checkAlignment("pcmState", pcmState); checkAlignment("pcm", &pcmState->pcm); checkAlignment("pcm core", &pcmState->pcm.core); checkAlignment("pcm memory", &pcmState->pcm.memory); checkAlignment("pcm qpi", &pcmState->pcm.qpi); for(uint32_t i(0); i < MAX_CPU_CORES; ++i) { checkAlignment("pcm core cores", &pcmState->pcm.core.cores[i]); } checkAlignment("pcm core energyUsed", &pcmState->pcm.core.energyUsedBySockets); for(uint32_t i(0); i < MAX_SOCKETS; ++i) { checkAlignment("pcm memory sockets", &pcmState->pcm.memory.sockets[i]); } for(uint32_t i(0); i < MAX_SOCKETS; ++i) { checkAlignment("pcm qpi incoming", &pcmState->pcm.qpi.incoming[i]); } for(uint32_t i(0); i < MAX_SOCKETS; ++i) { for(uint32_t j(0); j < QPI_MAX_LINKS; ++j) { checkAlignment("pcm qpi incoming links", &pcmState->pcm.qpi.incoming[i].links[j]); } } for(uint32_t i(0); i < MAX_SOCKETS; ++i) { checkAlignment("pcm qpi outgoing", &pcmState->pcm.qpi.outgoing[i]); } for(uint32_t i(0); i < MAX_SOCKETS; ++i) { for(uint32_t j(0); j < QPI_MAX_LINKS; ++j) { checkAlignment("pcm qpi outgoing links", &pcmState->pcm.qpi.outgoing[i].links[j]); } } pcm::freeAndNullify(pcmState); printf("\n------ All passed ------\n\n"); return EXIT_SUCCESS; }pcm-202502/tests/fuzz.sh000066400000000000000000000165201475730356400151030ustar00rootroot00000000000000 export PCM_ENFORCE_MBM="1" factor=1 if [ "$#" -eq 1 ]; then factor=$1 fi # Suppress leaks in libcrypto # Below is caused by openssl leaks # similar to https://github.com/spdk/spdk/issues/2947 echo leak:libcrypto.so >> pcm_asan_suppression_file export LSAN_OPTIONS=suppressions="pcm_asan_suppression_file" echo "Running fuzz tests with running time multiplier $factor" CC=`which clang` CXX=`which clang++` cmake .. -DCMAKE_BUILD_TYPE=Debug -DPCM_FUZZ=ON && mkdir -p corpus && make urltest-fuzz \ pcm-fuzz \ pcm-memory-fuzz \ pcm-sensor-server-fuzz \ pcm-sensor-server-ssl-fuzz \ -j && ldd bin/tests/pcm-fuzz && rm -rf corpus/* && printf '%b' "GET / HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/1 && printf '%b' "GET /metrics HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/2 && printf '%b' "GET /persecond HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/3 && printf '%b' "GET /persecond HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/3.1 && printf '%b' "GET /persecond HTTP/1.1\r\nHost: localhost\r\nAccept: text/plain; version=0.0.4\r\n\r\n" > corpus/3.2 && printf '%b' "GET /persecond/1 HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/4 && printf '%b' "GET /persecond/1 HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/4.1 && printf '%b' "GET /persecond/1 HTTP/1.1\r\nHost: localhost\r\nAccept: text/plain; version=0.0.4\r\n\r\n" > corpus/4.2 && printf '%b' "GET /persecond/10 HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/5 && printf '%b' "GET /persecond/10 HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/5.1 && printf '%b' "GET /persecond/10 HTTP/1.1\r\nHost: localhost\r\nAccept: text/plain; version=0.0.4\r\n\r\n" > corpus/5.2 && printf '%b' "GET /persecond/100 HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/6 && printf '%b' "GET /metrics HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/7 && printf '%b' "GET /dashboard/influxdb HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/8 && printf '%b' "GET /dashboard/prometheus HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/9 && printf '%b' "GET /dashboard/prometheus/default HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/10 && printf '%b' "GET /dashboard HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/11 && printf '%b' "GET /favicon.ico HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/12 && LLVM_PROFILE_FILE="pcm-sensor-server-ssl.profraw" bin/tests/pcm-sensor-server-ssl-fuzz -detect_leaks=0 -max_total_time=$((10 * $factor)) -rss_limit_mb=10000 corpus > /dev/null && rm -rf corpus/* && printf '%b' "GET / HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/1 && printf '%b' "GET /metrics HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/2 && printf '%b' "GET /persecond HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/3 && printf '%b' "GET /persecond HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/3.1 && printf '%b' "GET /persecond HTTP/1.1\r\nHost: localhost\r\nAccept: text/plain; version=0.0.4\r\n\r\n" > corpus/3.2 && printf '%b' "GET /persecond/1 HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/4 && printf '%b' "GET /persecond/1 HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/4.1 && printf '%b' "GET /persecond/1 HTTP/1.1\r\nHost: localhost\r\nAccept: text/plain; version=0.0.4\r\n\r\n" > corpus/4.2 && printf '%b' "GET /persecond/10 HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/5 && printf '%b' "GET /persecond/10 HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/5.1 && printf '%b' "GET /persecond/10 HTTP/1.1\r\nHost: localhost\r\nAccept: text/plain; version=0.0.4\r\n\r\n" > corpus/5.2 && printf '%b' "GET /persecond/100 HTTP/1.1\r\nHost: localhost\r\nAccept: application/json\r\n\r\n" > corpus/6 && printf '%b' "GET /metrics HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/7 && printf '%b' "GET /dashboard/influxdb HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/8 && printf '%b' "GET /dashboard/prometheus HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/9 && printf '%b' "GET /dashboard/prometheus/default HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/10 && printf '%b' "GET /dashboard HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/11 && printf '%b' "GET /favicon.ico HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n\r\n" > corpus/12 && LLVM_PROFILE_FILE="pcm-sensor-server.profraw" bin/tests/pcm-sensor-server-fuzz -detect_leaks=0 -rss_limit_mb=10000 -max_total_time=$((10 * $factor)) corpus > /dev/null && rm -rf corpus/* && printf '%b' "http://otto:test@www.intel.com/~otto/file1.txt" > corpus/1 && printf '%b' "file://localhost/c/mnt/cd/file2.txt" > corpus/2 && printf '%b' "ftp://otto%40yahoo.com:abcd%3B1234@www.intel.com:30/xyz.php?a=1&t=3" > corpus/3 && printf '%b' "gopher://otto@hostname1.intel.com:8080/file3.zyx" > corpus/4 && printf '%b' "www.intel.com" > corpus/5 && printf '%b' "http://www.blah.org/file.html#firstmark" > corpus/6 && printf '%b' "http://www.blah.org/file.html#firstmark%21%23" > corpus/7 && printf '%b' "localhost" > corpus/8 && printf '%b' "https://www.intel.com" > corpus/9 && printf '%b' "://google.com/" > corpus/10 && printf '%b' "https://intc.com/request?" > corpus/11 && printf '%b' "htt:ps//www.intel.com" > corpus/12 && printf '%b' "http://www.intel.com:66666/" > corpus/13 && printf '%b' "http:///" > corpus/14 && printf '%b' "http://[1234::1234::1234/" > corpus/15 && printf '%b' "http://@www.intel.com" > corpus/16 && printf '%b' "http://otto@:www.intel.com" > corpus/17 && printf '%b' "https://:@www.intel.com" > corpus/18 && printf '%b' "https://user:@www.intel.com" > corpus/19 && printf '%b' "http:www.intel.com/" > corpus/20 && printf '%b' "http://ww\x00\x00\x00rstmark\x0a" > corpus/21 && LLVM_PROFILE_FILE="urltest.profraw" bin/tests/urltest-fuzz -max_total_time=$((10 * $factor)) corpus > /dev/null && rm -rf corpus/* && LLVM_PROFILE_FILE="pcm.profraw" bin/tests/pcm-fuzz -max_total_time=$((5 * $factor)) corpus > /dev/null && rm -rf corpus/* && LLVM_PROFILE_FILE="pcm.no_perf.profraw" PCM_NO_PERF=1 bin/tests/pcm-fuzz -max_total_time=$((5 * $factor)) corpus > /dev/null && rm -rf corpus/* && LLVM_PROFILE_FILE="pcm.uncore_perf.profraw" PCM_USE_UNCORE_PERF=1 bin/tests/pcm-fuzz -max_total_time=$((5 * $factor)) corpus > /dev/null && rm -rf corpus/* && LLVM_PROFILE_FILE="pcm.nmi_watchdog.profraw" PCM_KEEP_NMI_WATCHDOG=1 bin/tests/pcm-fuzz -max_total_time=$((1 * $factor)) corpus > /dev/null && rm -rf corpus/* && LLVM_PROFILE_FILE="pcm-memory.profraw" bin/tests/pcm-memory-fuzz -max_total_time=$((10 * $factor)) corpus > /dev/null && llvm-profdata merge -sparse \ urltest.profraw \ pcm.profraw \ pcm.no_perf.profraw \ pcm.uncore_perf.profraw \ pcm.nmi_watchdog.profraw \ pcm-memory.profraw \ pcm-sensor-server.profraw \ pcm-sensor-server-ssl.profraw \ -o all.profdata && llvm-cov report --summary-only \ -object ./bin/tests/pcm-fuzz \ -object ./bin/tests/urltest-fuzz \ -object ./bin/tests/pcm-memory-fuzz \ -object ./bin/tests/pcm-sensor-server-fuzz \ -object ./bin/tests/pcm-sensor-server-ssl-fuzz \ -instr-profile=all.profdata | tee report.txt pcm-202502/tests/pcm-fuzz.cpp000066400000000000000000000056531475730356400160350ustar00rootroot00000000000000#define UNIT_TEST 1 #include "../src/pcm.cpp" #undef UNIT_TEST extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { size_t size_int = size / sizeof(int); const auto ints_used = 7; if (size_int < ints_used) { return 0; } auto m = PCM::getInstance(); const int *data_int = reinterpret_cast(data); int pos = 0; int pid = data_int[pos++]; bool use_pid = data_int[pos++] % 2; if (!use_pid) { pid = -1; } print_help(""); m->resetPMU(); m->disableJKTWorkaround(); const PCM::ErrorCode status = m->program(PCM::DEFAULT_EVENTS, nullptr, false, pid); switch (status) { case PCM::Success: break; case PCM::UnknownError: // expected for invalid pid return 0; case PCM::MSRAccessDenied: cerr << "Access to Intel(r) Performance Counter Monitor has denied (no MSR or PCI CFG space access).\n"; exit(EXIT_FAILURE); case PCM::PMUBusy: cerr << "Access to Intel(r) Performance Counter Monitor has denied (Performance Monitoring Unit is occupied by other application). Try to stop the application that uses PMU.\n"; cerr << "Alternatively you can try running PCM with option -r to reset PMU.\n"; exit(EXIT_FAILURE); default: cerr << "Access to Intel(r) Performance Counter Monitor has denied (Unknown error).\n"; exit(EXIT_FAILURE); } print_cpu_details(); std::vector cstates1, cstates2; std::vector sktstate1, sktstate2; SystemCounterState sstate1, sstate2; bitset ycores; const auto cpu_family_model = m->getCPUFamilyModel(); print_pid_collection_message(pid); bool show_partial_core_output = false; // TODO: add support for partial core output bool csv_output = data_int[pos++] % 2; int metricVersion = data_int[pos++]; bool show_socket_output = data_int[pos++] % 2; bool show_system_output = data_int[pos++] % 2; bool show_core_output = data_int[pos++] % 2; assert(pos == ints_used); m->getAllCounterStates(sstate1, sktstate1, cstates1); m->getAllCounterStates(sstate2, sktstate2, cstates2); if (csv_output) print_csv(m, cstates1, cstates2, sktstate1, sktstate2, ycores, sstate1, sstate2, show_core_output, show_partial_core_output, show_socket_output, show_system_output); else print_output(m, cstates1, cstates2, sktstate1, sktstate2, ycores, sstate1, sstate2, cpu_family_model, show_core_output, show_partial_core_output, show_socket_output, show_system_output, metricVersion); return 0; } pcm-202502/tests/pcm-memory-fuzz.cpp000066400000000000000000000076611475730356400173440ustar00rootroot00000000000000#define UNIT_TEST 1 #include "../src/pcm-memory.cpp" #undef UNIT_TEST extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { size_t size_int = size / sizeof(int); const auto ints_used = 9; if (size_int < ints_used) { return 0; } print_help(""); auto m = PCM::getInstance(); const int *data_int = reinterpret_cast(data); int pos = 0; bool csv = data_int[pos++] % 2; bool csvheader = data_int[pos++] % 2; bool show_channel_output = data_int[pos++] % 2; bool print_update = data_int[pos++] % 2; uint32 no_columns = DEFAULT_DISPLAY_COLUMNS; // Default number of columns is 2 int delay = data_int[pos++] % 4; int rankA = data_int[pos++] % 11; int rankB = data_int[pos++] % 11; bool use_rank = data_int[pos++] % 2; if (!use_rank) { rankA = -1; rankB = -1; } ServerUncoreMemoryMetrics metrics; switch (data_int[pos++] % 4) { case 0: metrics = PartialWrites; break; case 1: metrics = Pmem; break; case 2: metrics = PmemMemoryMode; break; case 3: metrics = PmemMixedMode; break; } assert(pos == ints_used); m->resetPMU(); m->disableJKTWorkaround(); const auto cpu_family_model = m->getCPUFamilyModel(); if (!m->hasPCICFGUncore()) { cerr << "Unsupported processor model (0x" << std::hex << cpu_family_model << std::dec << ").\n"; if (m->memoryTrafficMetricsAvailable()) cerr << "For processor-level memory bandwidth statistics please use 'pcm' utility\n"; return 0; } if (anyPmem(metrics) && (m->PMMTrafficMetricsAvailable() == false)) { cerr << "PMM/Pmem traffic metrics are not available on your processor.\n"; return 0; } if (metrics == PmemMemoryMode && m->PMMMemoryModeMetricsAvailable() == false) { cerr << "PMM Memory Mode metrics are not available on your processor.\n"; return 0; } if (metrics == PmemMixedMode && m->PMMMixedModeMetricsAvailable() == false) { cerr << "PMM Mixed Mode metrics are not available on your processor.\n"; return 0; } if((rankA >= 0 || rankB >= 0) && anyPmem(metrics)) { cerr << "PMM/Pmem traffic metrics are not available on rank level\n"; return 0; } if((rankA >= 0 || rankB >= 0) && !show_channel_output) { cerr << "Rank level output requires channel output\n"; return 0; } std::cerr << "programServerUncoreMemoryMetrics parameters:" << metrics << ";" << rankA << ";" << rankB << "\n"; PCM::ErrorCode status = m->programServerUncoreMemoryMetrics(metrics, rankA, rankB); m->checkError(status); max_imc_channels = (pcm::uint32)m->getMCChannelsPerSocket(); std::vector BeforeState(m->getNumSockets()); std::vector AfterState(m->getNumSockets()); uint64 BeforeTime = 0, AfterTime = 0; readState(BeforeState); BeforeTime = m->getTickCount(); MySleepMs(delay); AfterTime = m->getTickCount(); readState(AfterState); if(rankA >= 0 || rankB >= 0) calculate_bandwidth_rank(m,BeforeState, AfterState, AfterTime - BeforeTime, csv, csvheader, no_columns, rankA, rankB); else calculate_bandwidth(m,BeforeState,AfterState,AfterTime-BeforeTime,csv,csvheader, no_columns, metrics, show_channel_output, print_update, 0); return 0; } pcm-202502/tests/pcm-sensor-server-fuzz.cpp000066400000000000000000000175471475730356400206550ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #define UNIT_TEST 1 #include "../src/pcm-sensor-server.cpp" #undef UNIT_TEST int port = 0; bool waitForPort(int port, int timeoutSeconds) { int sockfd; struct sockaddr_in address; bool isBound = false; time_t startTime = time(nullptr); // Create a socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { DBG( 0, "Client: Error creating socket" ); return false; } // Set up the address structure memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr("127.0.0.1"); address.sin_port = htons(port); // Loop until the port is bound or the timeout is reached while (!isBound && (time(nullptr) - startTime) < timeoutSeconds) { // Attempt to connect to the port if (connect(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) { // Connection failed, wait a bit before retrying sleep(1); } else { // Connection succeeded, the port is bound isBound = true; } } // Clean up the socket close(sockfd); return isBound; } HTTPServer * httpServer; std::thread * serverThread; void cleanup() { DBG( 0, "Client: Stopping HTTPServer" ); httpServer->stop(); DBG( 0, "Client: Cleaning up PMU:" ); PCM::getInstance()->cleanup(); } bool init() { port = (rand() % 100) + 10000; // to be able to restart the fuzzer quickly without waiting for the port to be released serverThread = new std::thread([]() { PCM::ErrorCode status; PCM * pcmInstance = PCM::getInstance(); assert(pcmInstance); pcmInstance->resetPMU(); status = pcmInstance->program(); if (status != PCM::Success) { DBG( 0, "Client: Error in program() function" ); exit(1); } debug::dyn_debug_level(1); #ifdef FUZZ_USE_SSL DBG( 0, "Client: Starting SSL enabled server on https://localhost:", port ); auto httpsServer = new HTTPSServer( "", port ); httpsServer->setPrivateKeyFile ( "/private.key" ); httpsServer->setCertificateFile( "/certificate.crt" ); httpsServer->initialiseSSL(); httpServer = httpsServer; #else DBG( 0, "Client: Starting plain HTTP server on http://localhost:", port ); httpServer = new HTTPServer( "", port ); #endif // HEAD is GET without body, we will remove the body in execute() httpServer->registerCallback( HTTPRequestMethod::GET, my_get_callback ); httpServer->registerCallback( HTTPRequestMethod::HEAD, my_get_callback ); httpServer->run(); }); int timeout = 60; // Timeout in seconds DBG( 0, "Client: Waiting for port ", port, " to be bound with timeout of ", timeout, " seconds..." ); if (waitForPort(port, timeout)) { DBG( 0, "Client: Port ", port, " is now bound." ); } else { DBG( 0, "Client: Port ", port, " is not bound after ", timeout, " seconds." ); exit(1); } atexit(cleanup); return true; } std::string make_request(const std::string& request) { #ifdef FUZZ_USE_SSL const SSL_METHOD* method = TLS_client_method(); SSL_CTX* ctx = SSL_CTX_new(method); if (!ctx) { throw std::runtime_error("Unable to create SSL context"); } #endif std::string server = "127.0.0.1"; // Resolve the host struct hostent* host = gethostbyname(server.c_str()); if (!host) { #ifdef FUZZ_USE_SSL SSL_CTX_free(ctx); #endif DBG( 0, "Client: Failed to resolve host. Error: ", strerror(errno) ); throw std::runtime_error("Failed to resolve host: " + server); } // Create socket int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { #ifdef FUZZ_USE_SSL SSL_CTX_free(ctx); #endif DBG( 0, "Client: Failed to create socket. Error: ", strerror(errno) ); throw std::runtime_error("Failed to create socket"); } // Create server address structure struct sockaddr_in server_addr; std::memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); std::memcpy(&server_addr.sin_addr, host->h_addr, host->h_length); // Connect to server if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { DBG( 0, "Failed to connect to server. Error: ", strerror(errno) ); close(sock); #ifdef FUZZ_USE_SSL SSL_CTX_free(ctx); #endif throw std::runtime_error("Failed to connect to server"); } #ifdef FUZZ_USE_SSL // Create SSL structure SSL* ssl = SSL_new(ctx); SSL_set_fd(ssl, sock); int con_ret = SSL_connect(ssl); DBG( 1, "Client: SSL_connect returned ", con_ret ); if ( con_ret <= 0) { SSL_free(ssl); close(sock); SSL_CTX_free(ctx); throw std::runtime_error("Failed to establish SSL connection"); } #endif #ifdef FUZZ_USE_SSL // "Client:" is used as a hint to indicate whether client or server wrote the debug messages inside socketstream and socketbuf // When using this socketstream, it takes ownership of the socket and ssl connection and is responsible for properly closing // connections and freeing the allocated structures, this is why all of the frees and closes are commented out below DBG( 0, "Client: Opening an SSL socket stream" ); socketstream mystream( sock, ssl, "Client: " ); #else DBG( 0, "Client: Opening a normal socket stream" ); socketstream mystream( sock ); #endif DBG( 0, "Sending request: \n", request, "\n=====" ); std::string response; int bytes_received = -1; // Send the request try { mystream << request.c_str(); mystream.sync(); } catch ( const std::exception& e ) { DBG( 0, "Writing caused an exception: ", e.what() ); mystream.close(); #ifdef FUZZ_USE_SSL SSL_CTX_free(ctx); #endif throw std::runtime_error(std::string("Client Failed to write the request: ") + e.what()); } // Receive the response HTTPResponse resp; DBG( 0, "Client: Waiting for response:" ); try { mystream >> resp; } catch ( const std::exception& e ) { mystream.close(); #ifdef FUZZ_USE_SSL SSL_CTX_free(ctx); #endif DBG( 0, "Reading from the socket failed, reason: ", e.what() ); return std::string("Not necessarily fatal: Client: Exception caught while reading a response from the server: ") + e.what(); } // We've got a valid HTTPResponse otherwise we'd have caught an exception above HTTPHeader const h = resp.getHeader( "Content-Length" ); size_t contentLength = h.headerValueAsNumber(); // contentLength must be positive now otherwise the bad Content-Length should have thrown an exception bytes_received = contentLength; DBG( 0, "Client: received ", bytes_received, " bytes, copying them into response." ); // Reducing verbosity, only print the first 1024 characters of the response response.append( resp.body() ); if ( response.size() > 1024 ) response.erase(1024, std::string::npos ); // clean up mystream.close(); #ifdef FUZZ_USE_SSL SSL_CTX_free(ctx); #endif return response; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { static bool initialized = false; if (!initialized) { initialized = init(); } try { std::string request = std::string((const char*)data, size); std::string response = make_request(request); DBG( 0, "Response:\n", response, "\n====" ); } catch (const std::exception& e) { DBG( 0, "Client: LLVMFuzzerTestOneInput Exception: \"", e.what(), "\"" ); exit(1); } return 0; } pcm-202502/tests/test.sh000077500000000000000000000231131475730356400150630ustar00rootroot00000000000000modprobe msr export PCM_ENFORCE_MBM="1" export BIN_DIR="build/bin" pushd $BIN_DIR echo Enable NMI watchdog echo 1 > /proc/sys/kernel/nmi_watchdog echo Testing pcm with PCM_NO_PERF=1 PCM_NO_PERF=1 ./pcm -r -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm with PCM_USE_UNCORE_PERF=1 PCM_USE_UNCORE_PERF=1 ./pcm -r -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm with PCM_DEBUG_LEVEL=100 PCM_DEBUG_LEVEL=100 ./pcm -r -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm w/o env vars ./pcm -r -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm w/o env vars + color ./pcm -r --color -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm with -pid perl -e ' do {} until (0)' & test_pid="$!" ./pcm -pid $test_pid -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" kill $perl_pid exit 1 fi kill $test_pid echo Testing pcm with PCM_KEEP_NMI_WATCHDOG=1 PCM_KEEP_NMI_WATCHDOG=1 ./pcm -r -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm with -csv ./pcm -r 0.1 -csv=pcm.csv -- sleep 5 if [ "$?" -ne "0" ]; then echo "Error in pcm" exit 1 fi echo Testing pcm-memory ./pcm-memory -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-memory" exit 1 fi echo Testing pcm-memory with csv output ./pcm-memory -csv=pcm-memory.csv -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-memory" exit 1 fi echo Testing pcm-memory with -rank ./pcm-memory -rank=1 -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-memory" exit 1 fi echo Testing pcm-memory with -rank and -csv ./pcm-memory -rank=1 -csv -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-memory" exit 1 fi echo Testing pcm-iio --list ./pcm-iio --list if [ "$?" -ne "0" ]; then echo "Error in pcm-iio" exit 1 fi echo Testing pcm-iio ./pcm-iio -i=1 if [ "$?" -ne "0" ]; then echo "Error in pcm-iio" exit 1 fi echo Testing pcm-raw ./pcm-raw -e core/config=0x30203,name=LD_BLOCKS.STORE_FORWARD/ -e cha/config=0,name=UNC_CHA_CLOCKTICKS/ -e imc/fixed,name=DRAM_CLOCKS -e thread_msr/config=0x10,config1=1 -e thread_msr/config=0x19c,config1=0 -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Testing pcm-mmio ./pcm-mmio 0x0 if [ "$?" -ne "0" ]; then echo "Error in pcm-mmio" exit 1 fi echo Testing pcm-pcicfg ./pcm-pcicfg 0 0 0 0 0 if [ "$?" -ne "0" ]; then echo "Error in pcm-pcicfg" exit 1 fi echo Testing pcm-tpmi ./pcm-tpmi 2 0x10 -d -b 26:26 if [ "$?" -ne "0" ]; then echo "Error in pcm-tpmi" exit 1 fi echo Testing pcm-numa ./pcm-numa -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-numa" exit 1 fi echo Testing pcm-core ./pcm-core -e cpu/umask=0x01,event=0x0e,name=UOPS_ISSUED.STALL_CYCLES/ -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-core" exit 1 fi echo Testing c_example # see https://github.com/google/sanitizers/issues/934 LD_PRELOAD="$(realpath "$(gcc -print-file-name=libasan.so)") $(realpath "$(gcc -print-file-name=libstdc++.so)")" LD_LIBRARY_PATH=../lib/ ./examples/c_example if [ "$?" -ne "0" ]; then echo "Error in c_example" exit 1 fi echo Testing c_example_shlib ./examples/c_example_shlib if [ "$?" -ne "0" ]; then echo "Error in c_example_shlib" exit 1 fi echo Testing pcm-msr \(read only\) ./pcm-msr -a 0x30A if [ "$?" -ne "0" ]; then echo "Error in pcm-msr" exit 1 fi echo Testing pcm-power ./pcm-power -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-power" exit 1 fi echo "/sys/fs/cgroup/cpuset/cpuset.cpus:" cat /sys/fs/cgroup/cpuset/cpuset.cpus echo Testing pcm-pcie ./pcm-pcie -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-pcie" exit 1 fi echo Testing pcm-latency ./pcm-latency -i=1 if [ "$?" -ne "0" ]; then echo "Error in pcm-latency" exit 1 fi echo Testing pcm-tsx ./pcm-tsx -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-tsx" exit 1 fi # TODO add more tests # e.g for ./pcm-sensor-server, ./pcm-sensor, ... echo Testing urltest ./tests/urltest # We have 12 expected errors, anything else is a bug if [ "$?" != 12 ]; then echo "Error in urltest, 12 expected errors but found $?!" exit 1 fi echo Testing pcm-raw with event files echo Download necessary files if [ ! -f "mapfile.csv" ]; then echo "Downloading https://raw.githubusercontent.com/intel/perfmon/main/mapfile.csv" wget -q --timeout=10 https://raw.githubusercontent.com/intel/perfmon/main/mapfile.csv if [ "$?" -ne "0" ]; then echo "Could not download mapfile.csv" exit 1 fi fi VENDOR=$(lscpu | grep "Vendor ID:" | awk '{print $3}') FAMILY=$(lscpu | grep "CPU family:" | awk '{print $3}') MODEL=$(lscpu | grep "Model:" | awk '{printf("%x", $2)}') STRING="${VENDOR}-${FAMILY}-${MODEL}-" FILES=$(grep $STRING "mapfile.csv" | awk -F "\"*,\"*" '{print $3}') DIRS= for FILE in $FILES do DIR="$(dirname $FILE)" DIR="${DIR#?}" if [[ ! " ${DIRS[*]} " =~ " ${DIR} " ]]; then DIRS+="${DIR} " fi done for DIR in $DIRS do if [ ! -d $DIR ]; then mkdir -p $DIR cd $DIR DIRPATH="https://github.com/intel/perfmon.git" echo "Downloading all files from ${DIRPATH} using git" git clone $DIRPATH if [ "$?" -ne "0" ]; then cd .. echo "Could not download ${DIRPATH}" exit 1 fi mv perfmon/${DIR}/* . rm -rf perfmon cd ../.. fi done echo Now check pcm-raw with JSON files from mapFile.csv ./pcm-raw -r -e LD_BLOCKS.STORE_FORWARD -e CPU_CLK_UNHALTED.THREAD_ANY -e INST_RETIRED.ANY -e UNC_CHA_CLOCKTICKS -- sleep 1 if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Now get corresponding TSV files and replace JSON files in mapFile.csv with them cp "mapfile.csv" "mapfile.csv_orig" for FILE in $FILES do DIR="$(dirname $FILE)" DIR="${DIR#?}" cd $DIR BASE="$(basename $FILE)" TYPE="$(echo $BASE | sed 's/_v[0-9].*json//g')" # TYPE can be for example: skylakex_core or skylakex_uncore. CMD="find . -type f -regex '\.\/${TYPE}_v[0-9]*\.[0-9]*.tsv'" TSVFILE=$(eval $CMD) TSVFILE="${TSVFILE:2}" cd ../.. CMD="sed -i 's/${BASE}/${TSVFILE}/g' mapfile.csv" eval $CMD done # echo Test pcm-raw with TSV files #./pcm-raw -r -e LD_BLOCKS.STORE_FORWARD -e CPU_CLK_UNHALTED.THREAD_ANY -e INST_RETIRED.ANY -e UNC_CHA_CLOCKTICKS -- sleep 1 #if [ "$?" -ne "0" ]; then # echo "Error in pcm-raw" # rm -rf mapfile.csv # cp "mapfile.csv_orig" "mapfile.csv" # exit 1 #fi rm -rf mapfile.csv cp "mapfile.csv_orig" "mapfile.csv" if [ ! -f "event_file_test.txt" ]; then cat < event_file_test.txt # group 1 INST_RETIRED.ANY CPU_CLK_UNHALTED.REF_TSC MEM_TRANS_RETIRED.LOAD_LATENCY_GT_4 UNC_CHA_DIR_LOOKUP.SNP UNC_CHA_DIR_LOOKUP.NO_SNP UNC_M_CAS_COUNT.RD UNC_M_CAS_COUNT.WR UNC_UPI_CLOCKTICKS UNC_UPI_TxL_FLITS.ALL_DATA UNC_UPI_TxL_FLITS.NON_DATA UNC_UPI_L1_POWER_CYCLES UNC_CHA_TOR_INSERTS.IA_MISS MSR_EVENT:msr=0x19C:type=STATIC:scope=THREAD MSR_EVENT:msr=0x1A2:type=STATIC:scope=THREAD MSR_EVENT:msr=0x34:type=FREERUN:scope=PACKAGE MSR_EVENT:msr=0x34:type=static:scope=PACKAGE package_msr/config=0x34,config1=0 thread_msr/config=0x10,config1=1,name=TSC_DELTA thread_msr/config=0x10,config1=0,name=TSC pcicfg/config=0x208d,config1=0,config2=0,width=64,name=first_8_bytes_of_208d_device pcicfg/config=0x2021,config1=0,config2=0,width=32 pcicfg/config=0x2021,config1=0,config2=0,width=64 pcicfg/config=0x2058,config1=0x318,config2=1,width=64,name=UPI_reg ; # group 2 OFFCORE_REQUESTS_BUFFER.SQ_FULL UNC_CHA_DIR_UPDATE.HA UNC_CHA_DIR_UPDATE.TOR UNC_M2M_DIRECTORY_UPDATE.ANY UNC_M_CAS_COUNT.RD UNC_M_CAS_COUNT.WR imc/fixed,name=DRAM_CLOCKS UNC_CHA_TOR_INSERTS.IA_MISS:tid=0x20 UNC_M_PRE_COUNT.PAGE_MISS UNC_UPI_TxL0P_POWER_CYCLES UNC_UPI_RxL0P_POWER_CYCLES UNC_UPI_RxL_FLITS.ALL_DATA UNC_UPI_RxL_FLITS.NON_DATA UNC_P_FREQ_MAX_LIMIT_THERMAL_CYCLES MSR_EVENT:msr=0x10:type=FREERUN:scope=thread MSR_EVENT:msr=0x10:type=static:scope=thread pcicfg/config=0x2021,config1=4,config2=0,width=32 pcicfg/config=0x208d,config1=0,config2=1,width=64,name=first_8_bytes_of_208d_device_diff ; EOF fi echo Testing pcm-raw with -el event_file_test.txt -tr -csv ./pcm-raw -el event_file_test.txt -tr -csv=raw_tr_wo_ext.csv -i=4 0.25 if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Testing pcm-raw with -el event_file_test.txt -tr -ext -csv ./pcm-raw -el event_file_test.txt -tr -ext -csv=raw_tr_wi_ext.csv -i=4 0.25 if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Testing pcm-raw with -el event_file_test.txt -tr -ext -single-header -csv ./pcm-raw -el event_file_test.txt -tr -ext -single-header -csv=raw_tr_wi_ext_single_header.csv -i=4 0.25 if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Testing pcm-raw with -json ./pcm-raw -el event_file_test.txt -json=raw_json.json -i=4 0.25 if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Testing pcm-raw with -edp ./pcm-raw -edp -out raw_edp.txt 0.25 -tr -i=4 -el event_file_test.txt if [ "$?" -ne "0" ]; then echo "Error in pcm-raw" exit 1 fi echo Testing pcm-raw with -edp and offlined cores online_offline_cores() { for i in {5..10}; do echo $1 > /sys/devices/system/cpu/cpu$i/online done } online_offline_cores 0 ./pcm-raw -edp -out raw_edp_offlined_cores.txt 0.25 -tr -i=4 -el event_file_test.txt if [ "$?" -ne "0" ]; then online_offline_cores 1 echo "Error in pcm-raw with offlined cores" exit 1 fi online_offline_cores 1 popd pcm-202502/tests/urltest-fuzz.cpp000066400000000000000000000011671475730356400167540ustar00rootroot00000000000000#define UNIT_TEST 1 #include "../src/pcm-sensor-server.cpp" #undef UNIT_TEST extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { try { std::string buf(reinterpret_cast(data), size); buf.push_back('\0'); URL x = URL::parse(buf.c_str()); } catch (std::runtime_error & ) { // catch recognized malformed input (thrown as runtime_error in the URL::parse) // do not catch any other errors or exceptions to let them be reported // by libFuzzer } return 0; } pcm-202502/tests/urltest.cpp000066400000000000000000000023201475730356400157500ustar00rootroot00000000000000#define UNIT_TEST 1 #include "../src/pcm-sensor-server.cpp" #undef UNIT_TEST std::vector urls{ "http://otto:test@www.intel.com/~otto/file1.txt", "file://localhost/c/mnt/cd/file2.txt", "ftp://otto%40yahoo.com:abcd%3B1234@www.intel.com:30/xyz.php?a=1&t=3", "gopher://otto@hostname1.intel.com:8080/file3.zyx", "www.intel.com", "http://www.blah.org/file.html#firstmark", "http://www.blah.org/file.html#firstmark%21%23", "localhost", "https://www.intel.com", "://google.com/", "https://intc.com/request?", "htt:ps//www.intel.com", "http://www.intel.com:66666/", "http:///", "http://[1234::1234::1234/", "http://@www.intel.com", "http://otto@:www.intel.com", "https://:@www.intel.com", "https://user:@www.intel.com", "http:www.intel.com/", "http://ww\x00\x00\x00rstmark\x0a" }; int main( int, char** ) { int errors = 0; for ( auto & s : urls ) { try { std::cout << s << "\n"; URL x = URL::parse( s ); x.printURL(std::cout); } catch (const std::runtime_error & x ) { std::cout << "\"" << s << "\": " << x.what() << "\n"; ++errors; } } return errors; }