Initial commit

[ROCm/rocprofiler-compute commit: 62d130b458]
This commit is contained in:
colramos-amd
2022-11-04 14:49:36 -05:00
commit f0cf74353e
9127 changed files with 371405 additions and 0 deletions
@@ -0,0 +1,65 @@
parse:
additional_commands: {}
override_spec: {}
vartags: []
proptags: []
format:
disable: false
line_width: 90
tab_size: 4
use_tabchars: false
fractional_tab_policy: use-space
max_subgroups_hwrap: 2
max_pargs_hwrap: 6
max_rows_cmdline: 2
separate_ctrl_name_with_space: false
separate_fn_name_with_space: false
dangle_parens: false
dangle_align: child
min_prefix_chars: 4
max_prefix_chars: 10
max_lines_hwrap: 2
line_ending: unix
command_case: lower
keyword_case: upper
always_wrap: []
enable_sort: true
autosort: false
require_valid_layout: false
layout_passes: {}
markup:
bullet_char: '-'
enum_char: '*'
first_comment_is_literal: true
literal_comment_pattern: ^#
fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$
ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$
explicit_trailing_pattern: '#<'
hashruler_min_length: 10
canonicalize_hashrulers: true
enable_markup: true
lint:
disabled_codes: []
function_pattern: '[0-9a-z_]+'
macro_pattern: '[0-9A-Z_]+'
global_var_pattern: '[A-Z][0-9A-Z_]+'
internal_var_pattern: _[A-Z][0-9A-Z_]+
local_var_pattern: '[a-z][a-z0-9_]+'
private_var_pattern: _[0-9a-z_]+
public_var_pattern: '[A-Z][0-9A-Z_]+'
argument_var_pattern: '[a-z][a-z0-9_]+'
keyword_pattern: '[A-Z][0-9A-Z_]+'
max_conditionals_custom_parser: 2
min_statement_spacing: 1
max_statement_spacing: 2
max_returns: 6
max_branches: 12
max_arguments: 5
max_localvars: 15
max_statements: 50
encode:
emit_byteorder_mark: false
input_encoding: utf-8
output_encoding: utf-8
misc:
per_command: {}
@@ -0,0 +1,8 @@
/dashboards
/mongodb_connector
/perfmon
/sample
/soc_params
/utils
/wave_parser
/TestArea
@@ -0,0 +1,60 @@
# This is a basic workflow to help you get started with Actions
name: analyze commands
# Controls when the workflow will run
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
# 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
jobs:
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# 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
- uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.GH_PAT }}
# Cancel any previous runs
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Install Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install Rocm
run: |
sudo ./cmake/rocm_install.sh
sudo apt-get update
sudo apt-get dist-upgrade -y
sudo apt-get autoclean
- name: Install python prereqs
run: |
python3 -m pip install -r requirements.txt
python3 -m pip install pyinstaller pytest pytest-cov mock
- name: build and install
run: |
ls
ls src
ls src/mibench
ls src/mibench/roofline
cmake -B build-omniperf -DCMAKE_INSTALL_PREFIX=/opt/omniperf -DROCM_VERSIONS="4.3.1" "4.5.2" "5.0.2" "5.1.3" "5.2.3"
cmake --build build-omniperf --target all
cmake --build build-omniperf --target install
- name: run ctest
run: |
cd build-omniperf
ctest --verbose -R test_analyze_commands
@@ -0,0 +1,61 @@
# This is a basic workflow to help you get started with Actions
name: analyze workloads
# Controls when the workflow will run
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
# 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
jobs:
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# 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
- uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.GH_PAT }}
# Cancel any previous runs
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Install Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install Rocm
run: |
sudo ./cmake/rocm_install.sh
sudo apt-get update
sudo apt-get dist-upgrade -y
sudo apt-get autoclean
- name: Install python prereqs
run: |
python3 -m pip install -r requirements.txt
python3 -m pip install pyinstaller pytest pytest-cov
- name: build and install
run: |
ls
ls src
ls src/mibench
ls src/mibench/roofline
cmake -B build-omniperf -DCMAKE_INSTALL_PREFIX=/opt/omniperf -DROCM_VERSIONS="4.3.1" "4.5.2" "5.0.2" "5.1.3" "5.2.3"
cmake --build build-omniperf --target all
cmake --build build-omniperf --target install
- name: run ctest
run: |
cd build-omniperf
ctest --verbose -R test_analyze_workloads test_saved_analysis
@@ -0,0 +1,78 @@
name: formatting
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
python:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.8, 3.9]
steps:
- uses: actions/checkout@v3
# Cancel any previous runs
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install black
if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; fi
- name: black format
run: |
black --diff --check .
cmake:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3-pip
python3 -m pip install cmake-format
- name: cmake-format
run: |
set +e
cmake-format -i $(find . -type f | egrep 'CMakeLists.txt|\.cmake$')
if [ $(git diff | wc -l) -gt 0 ]; then
echo -e "\nError! CMake code not formatted. Run cmake-format...\n"
echo -e "\nFiles:\n"
git diff --name-only
echo -e "\nFull diff:\n"
git diff
exit 1
fi
python-bytecode:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: find-bytecode
run: |
set +e
FILES=$(find . -type f | egrep '__pycache__|\.pyc$')
if [ -n "${FILES}" ]; then
echo -e "\nError! Python bytecode included in commit\n"
echo -e "### FILES: ###"
echo -e "${FILES}"
echo -e "##############"
exit 1
fi
@@ -0,0 +1,42 @@
name: Packaging
on:
push:
tags:
- "v[1-9].[0-9]+.[0-9]+*"
jobs:
distbuild:
runs-on: ubuntu-latest
name: Create release distribution
env:
INSTALL_DIR: /tmp
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Verify VERSION file consistent with tag
run: utils/ver_check.py --tag ${{github.ref_name}}
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Python dependency installs
run: python3 -m pip install -t${INSTALL_DIR}/python-libs -r requirements.txt
- name: Configure
run: |
mkdir build
cd build
cmake -DPYTHON_DEPS=${INSTALL_DIR}/python-libs ..
- name: Release tarball
run: |
cd build
make package_source
- name: Rename tarball
run: mv build/omniperf-*.tar.gz build/omniperf-${{github.ref_name}}.tar.gz
- name: Archive tarball
uses: actions/upload-artifact@v3
with:
name: omniperf-${{github.ref_name}}.tar.gz
path: build/omniperf-${{github.ref_name}}.tar.gz
@@ -0,0 +1,64 @@
# This is a basic workflow to help you get started with Actions
name: Packaging
# Controls when the workflow will run
on:
push:
tags:
- "v*.*.*"
# 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
jobs:
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# 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
- uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.GH_PAT }}
# Cancel any previous runs
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Install Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install Rocm
run: |
sudo ./cmake/rocm_install.sh
sudo apt-get update
sudo apt-get dist-upgrade -y
sudo apt-get autoclean
- name: Install python prereqs
run: |
python3 -m pip install -r requirements.txt
python3 -m pip install pyinstaller
- name: build and install
run: |
cmake -B build-omniperf -DCMAKE_INSTALL_PREFIX=/opt/omniperf -DROCM_VERSIONS="4.3.1" "4.5.2" "5.0.2" "5.1.3" "5.2.3"
cmake --build build-omniperf --target all
cmake --build build-omniperf --target install
- name: Read VERSION
id: VERSION
uses: juliangruber/read-file-action@v1
with:
path: ./VERSION
- name: generate packaging
run: |
cd build-omniperf
cpack -G STGZ
cpack -G DEB -D CPACK_PACKAGING_INSTALL_PREFIX=/opt/omniperf
cpack -G RPM -D CPACK_PACKAGING_INSTALL_PREFIX=/opt/omniperf
ls
@@ -0,0 +1,92 @@
name: Tarball
on:
push:
branches:
- main
pull_request:
jobs:
distbuild:
runs-on: ubuntu-latest
name: Create distribution tarball
env:
INSTALL_DIR: /tmp/foo1
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Python dependency installs
run: python3 -m pip install -t${INSTALL_DIR}/python-libs -r requirements.txt
- name: Configure
run: |
mkdir build
cd build
cmake -DPYTHON_DEPS=${INSTALL_DIR}/python-libs ..
- name: Release tarball
run: |
cd build
make package_source
- name: Archive tarball
uses: actions/upload-artifact@v3
with:
name: tarball-testing
path: build/omniperf-*.tar.gz
retention-days: 3
disttest:
runs-on: ubuntu-latest
needs: [distbuild]
name: Tarball tests
env:
INSTALL_DIR: /tmp/foo2
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.11.0
- name: Access tarball
uses: actions/download-artifact@v3
with:
name: tarball-testing
- name: Expand
run: tar xfz omniperf-*.tar.gz; rm omniperf-*.tar.gz
- name: Python dependency installs
run: |
cd omniperf-*
python3 -m pip install -t${INSTALL_DIR}/python-libs -r requirements.txt
- name: Configure
run: |
cd omniperf-*
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}/omniperf \
-DPYTHON_DEPS=${INSTALL_DIR}/python-libs \
-DMOD_INSTALL_PATH=${INSTALL_DIR}/modulefiles ..
- name: Install
run: |
cd omniperf-*
cd build
make install
- name: Verify expected paths
run: |
test -d $INSTALL_DIR/omniperf
test -d $INSTALL_DIR/omniperf/bin
test -x $INSTALL_DIR/omniperf/bin/omniperf
test -d $INSTALL_DIR/modulefiles/omniperf
- name: Query version (setting PYTHONPATH by hand)
run: |
export PYTHONPATH=${INSTALL_DIR}/python-libs:$PYTHONPATH
$INSTALL_DIR/omniperf/bin/omniperf --version
- name: Install Lmod
run: sudo apt-get install -y lmod
- name: Access omniperf using modulefile
run: |
. /etc/profile.d/lmod.sh
module use $INSTALL_DIR/modulefiles
module load omniperf
module list
omniperf --version
+19
View File
@@ -0,0 +1,19 @@
# mongodb_connector files
__pycache__
# edit files
*~
# generated files/folders
/dist
/omniperf.spec
/build*
/.vscode
/.cache
/workloads
.coverage
saved_analysis
pmc_kernel_top.csv
# temp files
/tests/Testing
+10
View File
@@ -0,0 +1,10 @@
[submodule "src/mibench"]
path = src/mibench
url = git@github.com:AARInternal/mibench.git
branch = main
[submodule "src/waveparser"]
path = src/waveparser
url = git@github.com:AARInternal/waveparser.git
[submodule "src/multevent"]
path = src/multevent
url = git@github.com:AARInternal/multevent.git
+10
View File
@@ -0,0 +1,10 @@
# This is the list of Omniperf's significant contributors.
#
# This does not necessarily list everyone who has contributed code,
# especially since many employees of one corporation may be contributing.
# To see the full list of contributors, see the revision history in
# source control.
Xiaomin Lu
Cole Ramos
Fei Zheng
Jose Santos
+277
View File
@@ -0,0 +1,277 @@
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND CMAKE_CURRENT_SOURCE_DIR STREQUAL
CMAKE_SOURCE_DIR)
set(MSG "")
message(STATUS "Warning! Building from the source directory is not recommended")
message(STATUS "If unintended, please remove 'CMakeCache.txt' and 'CMakeFiles'")
message(STATUS "and build from a separate directory")
message(FATAL_ERROR "In-source build")
endif()
# System info
cmake_host_system_information(RESULT LOCALHOST QUERY FQDN)
message(STATUS "Hostname: ${LOCALHOST}")
# Versioning info derived from file
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" FULL_VERSION_STRING LIMIT_COUNT 1)
string(REGEX REPLACE "(\n|\r)" "" FULL_VERSION_STRING "${FULL_VERSION_STRING}")
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\1.\\2.\\3" OMNIPERF_VERSION
"${FULL_VERSION_STRING}")
string(REGEX REPLACE "(${OMNIPERF_VERSION})(.*)" "\\2" OMNIPERF_VERSION_TWEAK
"${FULL_VERSION_STRING}")
string(REGEX REPLACE "^\\." "" OMNIPERF_VERSION_TWEAK "${OMNIPERF_VERSION_TWEAK}")
project(
omniperf
VERSION ${OMNIPERF_VERSION}
LANGUAGES C
DESCRIPTION "OmniPerf"
HOMEPAGE_URL "https://github.com/AMDResearch/omniperf")
include(ExternalProject)
include(GNUInstallDirs)
set(CMAKE_BUILD_TYPE "Release")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX
"/opt/apps/omniperf"
CACHE PATH "default install path" FORCE)
endif()
message(STATUS "Installation path: ${CMAKE_INSTALL_PREFIX}")
# Python 3 is required
message(STATUS "Detecting Python interpreter...")
find_package(
Python3 3.7
COMPONENTS Interpreter
REQUIRED)
# Allow user-provided python search path
if(DEFINED PYTHON_DEPS)
set(ENV{PYTHONPATH} "${PYTHON_DEPS}")
message(STATUS "Optional PYTHON_DEPS provided:")
list(APPEND CMAKE_MESSAGE_INDENT " ")
message(STATUS "including ${PYTHON_DEPS} in search path")
list(POP_BACK CMAKE_MESSAGE_INDENT)
endif()
# Check required Python packages
set(pythonDeps
"astunparse"
"colorlover"
"dash"
"matplotlib"
"numpy"
"pandas"
"pymongo"
"yaml"
"tabulate"
"tqdm"
"dash_svg"
"dash_bootstrap_components")
message(STATUS "Checking for required Python package dependencies...")
set_property(GLOBAL PROPERTY pythonDepsFlag "groovy")
function(checkPythonPackage [package])
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import ${ARGV0}"
OUTPUT_QUIET ERROR_QUIET
RESULT_VARIABLE EXIT_CODE)
if(${EXIT_CODE} EQUAL 0)
message(STATUS "${ARGV0} = yes")
else()
message(STATUS "${ARGV0} = missing")
set_property(GLOBAL PROPERTY pythonDepsFlag "missing")
endif()
endfunction()
list(APPEND CMAKE_MESSAGE_INDENT " ")
foreach(package IN LISTS pythonDeps)
checkpythonpackage(${package})
endforeach()
list(POP_BACK CMAKE_MESSAGE_INDENT)
get_property(pythonDepsInstalled GLOBAL PROPERTY pythonDepsFlag)
if(${pythonDepsInstalled} STREQUAL "groovy")
message(STATUS "OK: Python dependencies available in current environment.")
else()
message(
FATAL_ERROR
"\nNecessary Python package dependencies not found. Please install required dependencies "
"above using your favorite package manager. If using pip, consider running:\n"
"python3 -m pip install -r requirements.txt\n"
"at the top-level of this repository. If preparing a shared installation for "
"multiple users, consider adding the -t <target-dir> option to install necessary dependencies "
"into a shared directory, e.g.\n"
"python3 -m pip install -t <shared-install-path> -r requirements.txt\n"
"Note that the -DPYTHON_DEPS=<shared-install-path> can be used to provide an "
"additional search path to cmake for python packages.")
endif()
# ----------------------
# modulefile creation
# ----------------------
set(MOD_INSTALL_PATH
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles"
CACHE STRING "Install path for modulefile")
message(STATUS "Modulefile install path: ${MOD_INSTALL_PATH}")
set(moduleFileTemplate "omniperf.lua.in")
configure_file(
${PROJECT_SOURCE_DIR}/cmake/${moduleFileTemplate}
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles/${PROJECT_NAME}/${PROJECT_VERSION}.lua
@ONLY)
# Crusher mods
if(LOCALHOST MATCHES ".*\.crusher\.olcf\.ornl\.gov")
list(APPEND CMAKE_MESSAGE_INDENT " ")
message(STATUS "Using crusher-specific modulefile modification")
file(READ ${PROJECT_SOURCE_DIR}/cmake/modfile.crusher.mod mod_additions)
file(
APPEND
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles/${PROJECT_NAME}/${PROJECT_VERSION}.lua
${mod_additions})
list(POP_BACK CMAKE_MESSAGE_INDENT)
endif()
# Thera mods
if(LOCALHOST MATCHES ".*\.thera\.amd\.com")
list(APPEND CMAKE_MESSAGE_INDENT " ")
message(STATUS "Using thera-specific modulefile modification")
file(READ ${PROJECT_SOURCE_DIR}/cmake/modfile.thera.mod mod_additions)
file(
APPEND
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles/${PROJECT_NAME}/${PROJECT_VERSION}.lua
${mod_additions})
list(POP_BACK CMAKE_MESSAGE_INDENT)
endif()
enable_testing()
add_test(
NAME test_analyze_commands
COMMAND ${Python3_EXECUTABLE} -m pytest
${PROJECT_SOURCE_DIR}/tests/test_analyze_commands.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_test(
NAME test_analyze_workloads
COMMAND ${Python3_EXECUTABLE} -m pytest
${PROJECT_SOURCE_DIR}/tests/test_analyze_workloads.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_test(
NAME test_saved_analysis
COMMAND ${Python3_EXECUTABLE} -m pytest
${PROJECT_SOURCE_DIR}/tests/test_saved_analysis.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
# if(EXISTS ${PROJECT_SOURCE_DIR}/src/mibench/roofline/roofline.cpp) message(STATUS
# "Enabling optional roofline binaries..") foreach(ROCM_VERSION ${ROCM_VERSIONS})
# externalproject_add( roofline-rocm-${ROCM_VERSION} PREFIX
# ${PROJECT_BINARY_DIR}/rocm-${ROCM_VERSION}/roofline SOURCE_DIR
# ${PROJECT_SOURCE_DIR}/src/mibench/roofline BINARY_DIR
# ${PROJECT_BINARY_DIR}/rocm-${ROCM_VERSION}/roofline/build BUILD_BYPRODUCTS
# <BINARY_DIR>/roofline CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env
# HIP_PATH=/opt/rocm-${ROCM_VERSION} ${CMAKE_COMMAND}
# -DCMAKE_CXX_COMPILER=/opt/rocm-${ROCM_VERSION}/hip/bin/hipcc
# -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B <BINARY_DIR> <SOURCE_DIR> BUILD_COMMAND
# ${CMAKE_COMMAND} --build <BINARY_DIR> --target all INSTALL_COMMAND "") #
# add_dependencies(roofline-rocm-${ROCM_VERSION} run-pyinstaller) string(REGEX REPLACE
# "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\1.\\2" ROCM_BASIC_VERSION "${ROCM_VERSION}")
# install( PROGRAMS ${PROJECT_BINARY_DIR}/rocm-${ROCM_VERSION}/roofline/build/roofline
# DESTINATION ${CMAKE_INSTALL_BINDIR}/utils RENAME roofline-${ROCM_BASIC_VERSION})
# endforeach() else() message(STATUS "Optional roofline package not found") endif()
# ---------
# Install
# ---------
# top-level driver and associated .py files
install(PROGRAMS src/omniperf TYPE BIN)
install(FILES src/parser.py TYPE BIN)
install(FILES src/common.py TYPE BIN)
install(FILES VERSION DESTINATION ${CMAKE_INSTALL_PREFIX})
# src/omniperf_cli
install(
DIRECTORY src/omniperf_cli
TYPE BIN
PATTERN src/omniperf_cli/tests EXCLUDE
PATTERN "__pycache__" EXCLUDE)
# src/utils
install(
DIRECTORY src/utils
TYPE BIN
PATTERN "rooflines*" EXCLUDE)
# src/utils/rooflines
install(PROGRAMS src/utils/rooflines/roofline-rhel8-mi200-rocm5
src/utils/rooflines/roofline-sle15sp3-mi200-rocm5
DESTINATION ${CMAKE_INSTALL_BINDIR}/utils/rooflines)
# src/perfmon_pub
install(
DIRECTORY src/perfmon_pub
TYPE BIN
PATTERN "__pycache__" EXCLUDE)
# src/soc_params
install(
DIRECTORY src/soc_params
TYPE BIN
PATTERN "__pycache__" EXCLUDE)
# samples
install(DIRECTORY sample TYPE DATA)
# modulefile
install(
FILES
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_DATAROOTDIR}/modulefiles/${PROJECT_NAME}/${PROJECT_VERSION}.lua
DESTINATION ${MOD_INSTALL_PATH}/${PROJECT_NAME})
# install(PROGRAMS src/omniperf.py DESTINATION ${OMNIPERF_PYINSTALLER_PATH})
# set(OMNIPERF_BUILD_ARGS "-y --workpath=${PROJECT_BINARY_DIR}/pyinstaller
# --distpath=${PROJECT_BINARY_DIR}/dist" CACHE STRING "Arguments for build.sh" FORCE)
#
# string(REPLACE " " ";" _OMNIPERF_BUILD_ARGS "${OMNIPERF_BUILD_ARGS}")
#
# add_custom_target( run-pyinstaller COMMAND ${PROJECT_SOURCE_DIR}/build.sh
# ${_OMNIPERF_BUILD_ARGS} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} BYPRODUCTS
# ${PROJECT_BINARY_DIR}/dist COMMENT "Running pyinstaller")
#
# set(OMNIPERF_PYINSTALLER_PATH
# "${CMAKE_INSTALL_LIBDIR}/python${PYTHON_VERSION}/site-packages/omniperf" CACHE STRING
# "Relative installation directory for omniperf")
#
# install( DIRECTORY ${PROJECT_BINARY_DIR}/dist/omniperf/ DESTINATION
# ${OMNIPERF_PYINSTALLER_PATH} USE_SOURCE_PERMISSIONS COMPONENT pyinstaller PATTERN ".a"
# EXCLUDE PATTERN ".git" EXCLUDE PATTERN "utils/roofline" EXCLUDE)
#
# install(FILES ${PROJECT_SOURCE_DIR}/VERSION DESTINATION
# ${OMNIPERF_PYINSTALLER_PATH}/../)
#
# file(RELATIVE_PATH OMNIPERF_RELATIVE_PATH
# "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}"
# "${CMAKE_INSTALL_PREFIX}/${OMNIPERF_PYINSTALLER_PATH}")
#
# configure_file(${PROJECT_SOURCE_DIR}/cmake/omniperf.sh.in
# ${PROJECT_BINARY_DIR}/omniperf-sh @ONLY) install( PROGRAMS
# ${PROJECT_BINARY_DIR}/omniperf-sh DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME omniperf)
# Packaging directives set(CPACK_GENERATOR "DEB" "RPM" "TGZ") set(CPACK_PACKAGE_NAME
# "omniperf") set(CPACK_PACKAGE_VENDOR "AMD") set(CPACK_PACKAGE_VERSION_MAJOR
# ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
# set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}${OMNIPERF_VERSION_TWEAK})
# set(CPACK_PACKAGE_CONTACT "Advanced Micro Devices Inc.")
# set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "omniperf") set(CPACK_RESOURCE_FILE_LICENSE
# "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
# Source tarball
set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_SOURCE_PACKAGE_FILE_NAME ${CMAKE_PROJECT_NAME}-${PROJECT_VERSION})
set(CPACK_SOURCE_IGNORE_FILES ".*~$" \.git/ \.github /tests /build)
include(CPack)
@@ -0,0 +1,59 @@
## How to fork from us
To keep our development fast and conflict free, we recommend you to [fork](https://github.com/AMDResearch/omniperf/fork) our repository and start your work from our `develop` branch in your private repository.
Afterwards, git clone your repository to your local machine. But that is not it! To keep track of the original develop repository, add it as another remote.
```
git remote add mainline https://github.com/AMDResearch/omniperf.git
git checkout develop
```
As always in git, start a new branch with
```
git checkout -b topic-<yourFeatureName>
```
and apply your changes there.
## How to contribute to Omniperf
### Did you find a bug?
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/AMDResearch/omniperf/issues).
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/AMDResearch/omniperf/issues/new).
### Did you write a patch that fixes a bug?
- Open a new GitHub [pull request](https://github.com/AMDResearch/omniperf/compare) with the patch.
- Ensure the PR description clearly describes the problem and solution. If there is an existing GitHub issue open describing this bug, please include it in the description so we can close it.
- Ensure the PR is based on the `develop` branch of the Omniperf GitHub repository.
- Omniperf requires new commits to include a "Signed-off-by" token in the commit message (typically enabled via the `git commit -s` option), indicating your agreement to the projects's [Developer's Certificate of Origin](https://developercertificate.org/) and compatability with the project [LICENSE](https://github.com/AMDResearch/omniperf/blob/main/LICENSE):
> (a) The contribution was created in whole or in part by me and I
> have the right to submit it under the open source license
> indicated in the file; or
>
> (b) The contribution is based upon previous work that, to the best
> of my knowledge, is covered under an appropriate open source
> license and I have the right under that license to submit that
> work with modifications, whether created in whole or in part
> by me, under the same open source license (unless I am
> permitted to submit under a different license), as indicated
> in the file; or
>
> (c) The contribution was provided directly to me by some other
> person who certified (a), (b) or (c) and I have not modified
> it.
>
> (d) I understand and agree that this project and the contribution
> are public and that a record of the contribution (including all
> personal information I submit with it, including my sign-off) is
> maintained indefinitely and may be redistributed consistent with
> this project or the open source license(s) involved.
+67
View File
@@ -0,0 +1,67 @@
# -----------------------------------------------------------------------
# NOTE:
# Dependencies are not included as part of Omniperf.
# It's the user's responsibility to accept any licensing implications
# before building the project
# -----------------------------------------------------------------------
FROM ubuntu:20.04
WORKDIR /app
USER root
ENV DEBIAN_FRONTEND noninteractive
ENV TZ "US/Chicago"
ADD grafana_plugins/svg_plugin /var/lib/grafana/plugins/custom-svg
ADD grafana_plugins/omniperfData_plugin /var/lib/grafana/plugins/omniperfData_plugin
RUN apt-get update && \
apt-get install -y apt-transport-https software-properties-common adduser libfontconfig1 wget curl && \
wget https://dl.grafana.com/enterprise/release/grafana-enterprise_8.3.4_amd64.deb &&\
dpkg -i grafana-enterprise_8.3.4_amd64.deb &&\
echo "deb https://packages.grafana.com/enterprise/deb stable main" | tee -a /etc/apt/sources.list.d/grafana.list && \
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get install gnupg && \
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc -O server-5.0.asc &&\
apt-key add server-5.0.asc && \
echo "deb [trusted=yes arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org.list && \
wget -q -O - https://packages.grafana.com/gpg.key | apt-key add - && \
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg > /dev/null && \
apt-get update && \
apt-get install -y mongodb-org && \
apt-get install -y tzdata systemd apt-utils npm vim net-tools && \
mkdir -p /nonexistent && \
/usr/sbin/grafana-cli plugins install michaeldmoore-multistat-panel && \
/usr/sbin/grafana-cli plugins install ae3e-plotly-panel && \
/usr/sbin/grafana-cli plugins install natel-plotly-panel && \
/usr/sbin/grafana-cli plugins install grafana-image-renderer && \
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt-get install -y yarn nodejs && \
chown root:grafana /etc/grafana && \
cd /var/lib/grafana/plugins/omniperfData_plugin && \
npm install && \
npm run build && \
apt-get autoremove -y && \
apt-get autoclean -y && \
cd /var/lib/grafana/plugins/custom-svg && \
yarn install && \
yarn build && \
yarn autoclean && \
sed -i "s/ bindIp.*/ bindIp: 0.0.0.0/" /etc/mongod.conf && \
mkdir -p /var/lib/grafana && \
touch /var/lib/grafana/grafana.lib && \
chown grafana:grafana /var/lib/grafana/grafana.lib && \
rm /app/grafana-enterprise_8.3.4_amd64.deb /app/server-5.0.asc
# Overwrite grafana ini file
COPY docker/grafana.ini /etc/grafana
# switch Grafana port to 4000
RUN sed -i "s/^;http_port = 3000/http_port = 4000/" /etc/grafana/grafana.ini && \
sed -i "s/^http_port = 3000/http_port = 4000/" /usr/share/grafana/conf/defaults.ini
# starts mongo and grafana-server at startup
COPY docker/docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT [ "/docker-entrypoint.sh" ]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Advanced Micro Devices, Inc. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+36
View File
@@ -0,0 +1,36 @@
<!-- [![Docs](https://github.com/AMDResearch/omniperf/actions/workflows/pages/pages-build-deployment/badge.svg?branch=gh-pages)](https://amdresearch.github.io/omniperf/) -->
<!-- [![GitHub commits since last release](https://img.shields.io/github/commits-since/AMDResearch/omniperf/latest/dev.svg)](https://github.com/AMDResearch/omniperf/compare/main...dev) -->
# Omniperf
## General
Omniperf is a system performance profiling tool for machine
learning/HPC workloads running on AMD MI GPUs. The tool presently
targets usage on MI100 and MI200 accelerators.
* For more information on available features, installation steps, and
workload profiling and analysis, please refer to the online
[documentation](https://amdresearch.github.io/omniperf).
* Omniperf is an AMD open source research project and is not supported
as part of the ROCm software stack. We welcome contributions and
feedback from the community. Please see the
[CONTRIBUTING.md](CONTRIBUTING.md) file for additional details on our
contribution process.
* Licensing information can be found in the [LICENSE](LICENSE) file.
## Development
Omniperf follows a
[main-dev](https://nvie.com/posts/a-successful-git-branching-model/)
branching model. As a result, our latest stable release is shipped
from the `main` branch, while new features are developed in our
`dev` branch.
Before publishing a new release, we'll open a new `release-*` branch
from `dev` with `*` being the version number of the upcoming
release. This branch will only receive bug fixes and users may
checkout to preview upcoming features.
+1
View File
@@ -0,0 +1 @@
1.0.3
@@ -0,0 +1,66 @@
################################################################################
# Copyright (c) 2021 - 2022 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
################################################################################
FROM ubuntu:20.04
USER root
COPY rocm_install.sh /omniperf/rocm_install.sh
ENV PATH="/omniperf:${PATH}"
ENV TZ="US/Chicago"
ENV DEBIAN_FRONTEND noninteractive
#pyenv dependencies
RUN apt update && \
apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl
ENV HOME="/omniperf"
WORKDIR $HOME
ENV PYENV_ROOT="$HOME/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PATH"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
apt update && \
apt-get install -y cmake wget git python3-dev rpm python3-venv software-properties-common &&\
add-apt-repository ppa:deadsnakes/ppa -y &&\
apt install python3.7 -y libpython3.7-dev python3.7-venv libnuma-dev &&\
curl https://pyenv.run | bash
RUN echo "export PATH=$HOME/.pyenv/bin:$PATH" >> ~/.bashrc &&\
echo eval "$(pyenv init -)" >> ~/.bashrc &&\
echo eval "$(pyenv virtualenv-init -)" >> ~/.bashrc &&\
CPPFLAGS=-I/usr/bin/openssl \
LDFLAGS=-L/usr/lib64 \
CONFIGURE_OPTS=--enable-shared pyenv install -v 3.8.12 &&\
pyenv global 3.8.12 &&\
apt-get install -y python3-pip
#clang?
RUN python3 -m pip install astunparse==1.6.2 colorlover dash matplotlib numpy pandas pymongo pyyaml tabulate tqdm dash-svg pyinstaller dash-bootstrap-components &&\
python3 -m pip install 'cmake==3.21.4' && \
./rocm_install.sh &&\
#wget -q -O - https://repo.radeon.com/rocm/rocm.gpg.key | apt-key add - && \
#echo "deb [arch=amd64] https://repo.radeon.com/rocm/apt/${ROCM_REPO_VERSION}/ ${ROCM_REPO_DIST} main" | tee /etc/apt/sources.list.d/rocm.list && \
apt-get update && \
apt-get dist-upgrade -y && \
#apt-get install -y rocm-dev rocm-utils rocm-smi-lib roctracer-dev rocprofiler-dev rccl-dev hip-base hsa-amd-aqlprofile hsa-rocr-dev hsakmt-roct-dev ${EXTRA_PACKAGES} && \
apt-get autoclean
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
################################################################################
# Copyright (c) 2021 - 2022 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
################################################################################
set -e
SCRIPT_DIR=$(realpath $(dirname ${BASH_SOURCE[0]}))
OMNIPERF_DIR=$(realpath ${SCRIPT_DIR}/@OMNIPERF_RELATIVE_PATH@)
if [ ! -f ${OMNIPERF_DIR}/omniperf ]; then
echo -e "Error! Expected omniperf installation in ${OMNIPERF_DIR}"
exit 1
fi
eval ${OMNIPERF_DIR}/omniperf "${@}"
@@ -0,0 +1,4 @@
-- Crusher-specific additions
depends_on "cray-python"
depends_on "rocm"
prereq(atleast("rocm","5.1.0"))
@@ -0,0 +1,6 @@
-- Thera-specific additions
depends_on "python"
depends_on "rocm"
prereq(atleast("rocm","5.1.0"))
local home = os.getenv("HOME")
setenv("MPLCONFIGDIR",pathJoin(home,".matplotlib"))
@@ -0,0 +1,32 @@
local help_message = [[
Omniperf is an open-source performance analysis tool for profiling
machine learning/HPC workloads running on AMD MI GPUs.
Version @PROJECT_VERSION@
]]
help(help_message,"\n")
whatis("Name: omniperf")
whatis("Version: @PROJECT_VERSION@")
whatis("Keywords: Profiling, Performance, GPU")
whatis("Description: tool for GPU performance profiling")
whatis("URL: https://github.com/AMDResearch/omniperf")
-- Export environmental variables
local topDir="@CMAKE_INSTALL_PREFIX@"
local binDir="@CMAKE_INSTALL_FULL_BINDIR@"
local shareDir="@CMAKE_INSTALL_FULL_DATADIR@"
local pythonDeps="@PYTHON_DEPS@"
setenv("OMNIPERF_DIR",topDir)
setenv("OMNIPERF_BIN",binDir)
setenv("OMNIPERF_SHARE",shareDir)
-- Update relevant PATH variables
prepend_path("PATH",binDir)
if ( pythonDeps ~= "" ) then
prepend_path("PYTHONPATH",pythonDeps)
end
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
################################################################################
# Copyright (c) 2021 - 2022 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
################################################################################
declare -a rocm_versions=("4.3.1" "4.5.2" "5.0.2" "5.1.3" "5.2.3")
wget https://repo.radeon.com/amdgpu-install/22.10/ubuntu/focal/amdgpu-install_22.10.50100-1_all.deb
apt-get install -y ./amdgpu-install_22.10.50100-1_all.deb
for rocm_version in ${rocm_versions[@]}; do
echo "deb [arch=amd64] https://repo.radeon.com/rocm/apt/$rocm_version ubuntu main" | tee /etc/apt/sources.list.d/rocm.list
apt update
amdgpu-install -y --usecase=rocm --rocmrelease=$rocm_version --no-dkms
done
File diff suppressed because one or more lines are too long
@@ -0,0 +1,39 @@
# -----------------------------------------------------------------------
# NOTE:
# Dependencies are not included as part of Omniperf.
# It's the user's responsibility to accept any licensing implications
# before building the project
# -----------------------------------------------------------------------
version: "3.3"
services:
web:
image: omniperf-grafana-v1.0
container_name: omniperf-grafana-v1.0
restart: always
build: .
ports:
- "14000:4000"
volumes:
- grafana-storage:/var/lib/grafana
stdin_open: true
tty: true
db_mongo:
container_name: mongo
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: amd
MONGO_INITDB_ROOT_PASSWORD: amd123
volumes:
- grafana-mongo-db:/data/db
ports:
- "27018:27017"
command: mongod --bind_ip 0.0.0.0
volumes:
grafana-mongo-db:
external: true
grafana-storage:
external: true
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
################################################################################
# Copyright (c) 2021 - 2022 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
################################################################################
pushd /var/lib/grafana/plugins/omniperfData_plugin
npm run server &
popd
service grafana-server start
if [ -z "$1" ]; then
exec bash
else
eval $@
fi
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
# Dependency directories
node_modules/
jspm_packages/
# Nuxt.js build / generate output
.nuxt
dist
@@ -0,0 +1,14 @@
{
"esnext": true,
"disallowImplicitTypeConversion": ["string"],
"disallowKeywords": ["with"],
"disallowMultipleLineBreaks": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"validateIndentation": 2
}
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "es5",
"useTabs": true
}
@@ -0,0 +1,5 @@
# Changelog
## 1.0.0 (Unreleased)
Initial release.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 JamesOsgood
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,19 @@
# Omniperf Data Source Plugin
This plugin allows users of Omniperf to connect their MongoDB database to for visualization in Grafana
## Info
This backend exposes the endpoints Grafana requires to create a custom data source.
An express server, located in the `server` folder exposes these endpoints and makes the proxy to the MongoDB.
## Dependencies
- Node.js
`sudo apt-get install -y npm`
## Useage
<img src="src/img/sample.PNG" alt="Sample Data Source" style="width: 500px;"/>
@@ -0,0 +1,22 @@
const { src, dest, series } = require('gulp');
const babel = require('gulp-babel');
function script() {
return src('src/**/*.js')
.pipe(
babel({
presets: ['es2015'],
plugins: [
'transform-es2015-modules-systemjs',
'transform-es2015-for-of',
],
})
)
.pipe(dest('dist'));
}
function srcToDist() {
return src(['src/**/*', '!src/**/*.js']).pipe(dest('dist'));
}
exports.default = series(script, srcToDist);
@@ -0,0 +1,37 @@
{
"name": "amd-omniperf-data-plugin",
"version": "1.0.0",
"description": "",
"main": "server/mongo-proxy.js",
"scripts": {
"build": "gulp",
"test": "echo 'No test found'",
"server": "cd server && node mongo-proxy.js"
},
"author": "Audacious Software Group",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-plugin-transform-es2015-for-of": "^6.23.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"chai": "~4.2.0",
"gulp": "^4.0.2",
"gulp-babel": "^7.0.1",
"jsdom": "~16.2.2",
"prunk": "^1.3.1",
"q": "^1.5.1"
},
"dependencies": {
"body-parser": "^1.19.0",
"config": "^3.3.1",
"express": "^4.17.1",
"fs": "0.0.1-security",
"lodash": "^4.17.15",
"mocha": "^7.1.2",
"moment": "^2.24.0",
"mongodb": "^3.5.7",
"statman-stopwatch": "^2.11.1"
},
"_comments": "Dependencies are not included as part of Omniperf. It's the user's responsibility to accept any licensing implications before building the project."
}
@@ -0,0 +1,10 @@
{
"server":
{
"port": 3333,
"logRequests": false,
"logQueries": false,
"logTimings": false
}
}
@@ -0,0 +1,647 @@
// ################################################################################
// # Copyright (c) 2018 JamesOsgood
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// #
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
var express = require('express');
var bodyParser = require('body-parser');
var _ = require('lodash');
var app = express();
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
var config = require('config');
var Stopwatch = require("statman-stopwatch");
var moment = require('moment')
const serverConfig = require('./config/default.json').server;
app.use(bodyParser.json({ limit: '5mb' }));
// Called via required 'testDataSource'
app.all('/', function(req, res, next)
{
console.log("Entered /")
logRequest(req.body, "/")
setCORSHeaders(res);
MongoClient.connect(req.body.db.url, function(err, client)
{
if ( err != null )
{
res.send({ status : "error",
display_status : "Error",
message : 'MongoDB Connection Error: ' + err.message });
}
else
{
res.send( { status : "success",
display_status : "Success",
message : 'MongoDB Connection test OK' });
}
next()
})
});
// Called by template functions and to look up variables
app.all('/search', function(req, res, next)
{
console.log("Entered /search")
logRequest(req.body, "/search")
setCORSHeaders(res);
// Generate an id to track requests
const requestId = ++requestIdCounter
// Add state for the queries in this request
var queryStates = []
requestsPending[requestId] = queryStates
// Parse query string in target
queryArgs = parseQuery(req.body.target, {})
if (queryArgs.err != null)
{
queryError(requestId, queryArgs.err, next)
}
else
{
doTemplateQuery(requestId, queryArgs, req.body.db, res, next);
}
});
// State for queries in flight. As results come it, acts as a semaphore and sends the results back
var requestIdCounter = 0
// Map of request id -> array of results. Results is
// { query, err, output }
var requestsPending = {}
// Called when a query finishes with an error
function queryError(requestId, err, next)
{
// We only 1 return error per query so it may have been removed from the list
if ( requestId in requestsPending )
{
// Remove request
delete requestsPending[requestId]
// Send back error
next(err)
}
}
// Called when query finished
function queryFinished(requestId, queryId, results, res, next)
{
// We only 1 return error per query so it may have been removed from the list
if ( requestId in requestsPending )
{
var queryStatus = requestsPending[requestId]
// Mark this as finished
queryStatus[queryId].pending = false
queryStatus[queryId].results = results
// See if we're all done
var done = true
for ( var i = 0; i < queryStatus.length; i++)
{
if (queryStatus[i].pending == true )
{
done = false
break
}
}
// If query done, send back results
if (done)
{
// Concatenate results
output = []
for ( var i = 0; i < queryStatus.length; i++)
{
var queryResults = queryStatus[i].results
var keys = Object.keys(queryResults)
for (var k = 0; k < keys.length; k++)
{
var tg = keys[k]
output.push(queryResults[tg])
}
}
res.json(output);
next()
// Remove request
delete requestsPending[requestId]
}
}
}
// Called to get graph points
app.all('/query', function(req, res, next)
{
console.log("Entered /query")
logRequest(req.body, "/query")
setCORSHeaders(res);
// Parse query string in target
substitutions = { "$from" : new Date(req.body.range.from),
"$to" : new Date(req.body.range.to),
"$dateBucketCount" : getBucketCount(req.body.range.from, req.body.range.to, req.body.intervalMs)
}
// Generate an id to track requests
const requestId = ++requestIdCounter
// Add state for the queries in this request
var queryStates = []
requestsPending[requestId] = queryStates
var error = false
for ( var queryId = 0; queryId < req.body.targets.length && !error; queryId++)
{
tg = req.body.targets[queryId]
queryArgs = parseQuery(tg.target, substitutions)
queryArgs.type = tg.type
// If we picked up any errors while parsing query
if (queryArgs.err != null)
{
queryError(requestId, queryArgs.err, next)
error = true
}
else
{
// Add to the state
queryStates.push( { pending : true } )
// Run the query
runAggregateQuery( requestId, queryId, req.body, queryArgs, res, next)
}
}
}
);
app.use(function(error, req, res, next)
{
// Any request to this server will get here, and will send an HTTP
// response with the error message
res.status(500).json({ message: error.message });
});
app.listen(serverConfig.port);
console.log("Server is listening on port " + serverConfig.port);
function setCORSHeaders(res)
{
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST");
res.setHeader("Access-Control-Allow-Headers", "accept, content-type");
}
function forIn(obj, processFunc)
{
var key;
for (key in obj)
{
var value = obj[key]
processFunc(obj, key, value)
if ( value != null && typeof(value) == "object")
{
forIn(value, processFunc)
}
}
}
function parseQuery(query, substitutions)
{
doc = {}
docs = []
queryErrors = []
chosenDB = null
query = query.trim() // Gets rid of any leading or trailing space
// TODO: Add a case to catch custom db name
// if (query.substring(0,3) != "db.")
// {
// queryErrors.push("Query must start with db.")
// return null
// }
var firstDotIndex = query.indexOf('.')
if (query.substring(0,3) != "db.")
{
if(firstDotIndex == -1){
queryErrors.push("Invalid database name format")
return null
}
chosenDB = query.substring(0, firstDotIndex)
}
doc.db = chosenDB
// Query is of the form db.<collection>.aggregate or db.<collection>.find
// Split on the first ( after db.
var openBracketIndex = query.indexOf('(', firstDotIndex)
if (openBracketIndex == -1)
{
queryErrors.push("Can't find opening bracket")
}
else
{
// Split the first bit - it's the collection name and operation ( must be aggregate )
var parts = query.substring(firstDotIndex+1, openBracketIndex).split('.')
// Collection names can have .s so last part is operation, rest is the collection name
if (parts.length >= 2)
{
doc.operation = parts.pop().trim()
doc.collection = parts.join('.')
}
else
{
queryErrors.push("Invalid collection and operation syntax")
}
// Args is the rest up to the last bracket
const potentialPipelineEnding = ['])', '],'];
const pipelineEnd = potentialPipelineEnding.find(end => query.indexOf(end, openBracketIndex) !== -1);
const pipelineEndIndex = query.indexOf(pipelineEnd, openBracketIndex)
if (!pipelineEnd)
{
queryErrors.push("Can't find last bracket")
}
else
{
var args = query.substring(openBracketIndex + 1, pipelineEndIndex)
if ( doc.operation == 'aggregate')
{
// Wrap args in array syntax so we can check for optional options arg
args = '[' + args + ']]'
docs = ((raw) => {
try {
const data = JSON.parse(raw);
return data;
}
catch (error) {
return null;
}
})(args);
if(!docs) {
queryErrors.push("Invalid query syntax");
}
// First Arg is pipeline
doc.pipeline = docs[0]
// If we have 2 top level args, second is agg options
if ( docs.length == 2 )
{
doc.agg_options = docs[1]
}
// Replace with substitutions
for ( var i = 0; i < doc.pipeline.length; i++)
{
var stage = doc.pipeline[i]
forIn(stage, function (obj, key, value)
{
if ( typeof(value) == "string" )
{
if ( value.startsWith("&") )
{
obj[key] = "$"+value.substring(1) // ie "$from" --> new Date(req.body.range.from)
}
}
})
}
}
else
{
queryErrors.push("Unknown operation " + doc.operation + ", only aggregate supported")
}
}
}
if (queryErrors.length > 0 )
{
doc.err = new Error('Failed to parse query - ' + queryErrors.join(':'))
}
// Checker for debugging
if (chosenDB == null){
console.log("Chosen DB is DEFAULT");
}
else{
console.log("chosenDB is " + doc.db);
}
console.log("Collection is "+ doc.collection);
return doc
}
// Run an aggregate query. Must return documents of the form
// { value : 0.34334, ts : <epoch time in seconds> }
function runAggregateQuery( requestId, queryId, body, queryArgs, res, next )
{
MongoClient.connect(body.db.url, function(err, client)
{
if ( err != null )
{
queryError(requestId, err, next)
}
else
{
// TODO: Use the other db name if it was provided
// var assignedName = null
// if(queryArgs.db == null){
// assignedName = body.db.db
// }
// else{
// assignedName = queryArgs.db
// }
// console.log("Assigned DB name is " + assignedName)
// const db = client.db(assignedName);
var secondDb = client.db(queryArgs.db)
// Get the documents collection
const collection = secondDb.collection(queryArgs.collection);
logQuery(queryArgs.pipeline, queryArgs.agg_options)
// Track how long it takes to complete query
var stopwatch = new Stopwatch(true)
collection.aggregate(queryArgs.pipeline, {allowDiskUse:true}).toArray(function(err, docs)
{
if ( err != null )
{
client.close();
queryError(requestId, err, next)
}
else
{
try
{
var results = {}
if ( queryArgs.type == 'timeserie' )
{
results = getTimeseriesResults(docs)
}
// This is where omniperf will go for most results
else
{
results = getTableResults(docs)
}
client.close();
var elapsedTimeMs = stopwatch.stop()
logTiming(body, elapsedTimeMs)
// Mark query as finished - will send back results when all queries finished
queryFinished(requestId, queryId, results, res, next)
}
catch(err)
{
queryError(requestId, err, next)
}
}
})
}
})
}
function getTableResults(docs)
{
var columns = {}
// Build superset of columns
for ( var i = 0; i < docs.length; i++)
{
var doc = docs[i]
// Go through all properties
for (var propName in doc )
{
// See if we need to add a new column
if ( !(propName in columns) )
{
columns[propName] =
{
text : propName,
type : "text"
}
}
}
}
// Build return rows
rows = []
for ( var i = 0; i < docs.length; i++)
{
var doc = docs[i]
row = []
// All cols
for ( var colName in columns )
{
var col = columns[colName]
if ( col.text in doc )
{
row.push(doc[col.text])
}
else
{
row.push(null)
}
}
rows.push(row)
}
var results = {}
results["table"] = {
columns : Object.values(columns),
rows : rows,
type : "table"
}
return results
}
function getTimeseriesResults(docs)
{
var results = {}
for ( var i = 0; i < docs.length; i++)
{
var doc = docs[i]
var tg = doc.name
var dp = null
if (tg in results)
{
dp = results[tg]
}
else
{
dp = { 'target' : tg, 'datapoints' : [] }
results[tg] = dp
}
results[tg].datapoints.push([doc['value'], doc['ts'].getTime()])
}
return results
}
// Runs a query to support templates. Must return documents of the form
// { _id : <id> }
function doTemplateQuery(requestId, queryArgs, db, res, next)
{
if ( queryArgs.err == null)
{
if(queryArgs.db != null){ //We're looking at a custom dbName.aggregate
// Use connect method to connect to the server
MongoClient.connect(db.url, function(err, client)
{
if ( err != null )
{
queryError(requestId, err, next )
}
else
{
// Remove request from list
if ( requestId in requestsPending )
{
delete requestsPending[requestId]
}
var secondDb = client.db(queryArgs.db);
// Get the documents collection
const collection = secondDb.collection(queryArgs.collection);
collection.aggregate(queryArgs.pipeline).toArray(function(err, result)
{
assert.equal(err, null)
output = []
for ( var i = 0; i < result.length; i++)
{
var doc = result[i]
output.push(doc["_id"])
}
res.json(output);
client.close()
next()
})
}
})
}
else{ //Then we're just looking at the default db.aggregate
// Database Name
const dbName = db.db
// Use connect method to connect to the server
MongoClient.connect(db.url, function(err, client)
{
if ( err != null )
{
queryError(requestId, err, next )
}
else
{
// Remove request from list
if ( requestId in requestsPending )
{
delete requestsPending[requestId]
}
const db = client.db(dbName);
// Get the documents collection
const collection = db.collection(queryArgs.collection);
collection.aggregate(queryArgs.pipeline).toArray(function(err, result)
{
assert.equal(err, null)
output = []
for ( var i = 0; i < result.length; i++)
{
var doc = result[i]
output.push(doc["_id"])
}
res.json(output);
client.close()
next()
})
}
})
}
}
else
{
next(queryArgs.err)
}
}
function logRequest(body, type)
{
if (serverConfig.logRequests)
{
console.log("REQUEST: " + type + ":\n" + JSON.stringify(body,null,2))
}
}
function logQuery(query, options)
{
if (serverConfig.logQueries)
{
console.log("Query:")
console.log(JSON.stringify(query,null,2))
if ( options != null )
{
console.log("Query Options:")
console.log(JSON.stringify(options,null,2))
}
}
}
function logTiming(body, elapsedTimeMs)
{
if (serverConfig.logTimings)
{
var range = new Date(body.range.to) - new Date(body.range.from)
var diff = moment.duration(range)
console.log("Request: " + intervalCount(diff, body.interval, body.intervalMs) + " - Returned in " + elapsedTimeMs.toFixed(2) + "ms")
}
}
// Take a range as a moment.duration and a grafana interval like 30s, 1m etc
// And return the number of intervals that represents
function intervalCount(range, intervalString, intervalMs)
{
// Convert everything to seconds
var rangeSeconds = range.asSeconds()
var intervalsInRange = rangeSeconds / (intervalMs / 1000)
var output = intervalsInRange.toFixed(0) + ' ' + intervalString + ' intervals'
return output
}
function getBucketCount(from, to, intervalMs)
{
var boundaries = []
var current = new Date(from).getTime()
var toMs = new Date(to).getTime()
var count = 0
while ( current < toMs )
{
current += intervalMs
count++
}
return count
}
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>mongodb-grafana-proxy</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin/:$PATH</string>
</dict>
<key>WorkingDirectory</key>
<string>/usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/lib/npm-packages/bin/forever</string>
<string>-l</string>
<string>/usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server/mongodb-proxy-forever.log</string>
<string>-o</string>
<string>/usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server/mongodb-proxy.log</string>
<string>-e</string>
<string>/usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server/mongodb-proxy-err.log</string>
<string>--workingDir</string>
<string>/usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server</string>
<string>/usr/local/var/lib/grafana/plugins/mongodb-grafana/dist/server/mongodb-proxy.js</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,3 @@
.generic-datasource-query-row .query-keyword {
width: 75px;
}
@@ -0,0 +1,147 @@
// ################################################################################
// # Copyright (c) 2018 JamesOsgood
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// #
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import _ from "lodash";
export class GenericDatasource {
constructor(instanceSettings, $q, backendSrv, templateSrv) {
this.type = instanceSettings.type;
this.url = instanceSettings.url;
this.name = instanceSettings.name;
this.db = { 'url' : instanceSettings.jsonData.mongodb_url, 'db' : instanceSettings.jsonData.mongodb_db }
this.q = $q;
this.backendSrv = backendSrv;
this.templateSrv = templateSrv;
this.withCredentials = instanceSettings.withCredentials;
this.headers = {'Content-Type': 'application/json'};
// Assert valid sign in parameter
if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) {
this.headers['Authorization'] = instanceSettings.basicAuth;
}
}
query(options) {
var query = this.buildQueryParameters(options);
query.targets = query.targets.filter(t => !t.hide);
query.db = this.db
if (query.targets.length <= 0) {
return this.q.when({data: []});
}
return this.doRequest({
url: this.url + '/query',
data: query,
method: 'POST'
});
}
// Testing whether connection is valid
testDatasource() {
return this.doRequest({
url: this.url + '/',
data : { db : this.db },
method: 'POST',
}).then(response => {
if (response.status === 200) {
return { status: response.data.status, message: response.data.message, title: response.data.display_status };
}
});
}
annotationQuery(options) {
var query = this.templateSrv.replace(options.annotation.query, {}, 'glob');
var annotationQuery = {
range: options.range,
annotation: {
name: options.annotation.name,
datasource: options.annotation.datasource,
enable: options.annotation.enable,
iconColor: options.annotation.iconColor,
query: query
},
rangeRaw: options.rangeRaw
};
return this.doRequest({
url: this.url + '/annotations',
method: 'POST',
data: annotationQuery
}).then(result => {
response.data.$$status = result.status;
response.data.$$config = result.config;
return result.data;
});
}
metricFindQuery(query) {
var interpolated = {
target: this.templateSrv.replace(query, null, '')
};
interpolated.db = this.db
return this.doRequest({
url: this.url + '/search',
data: interpolated,
method: 'POST',
}).then(this.mapToTextValue);
}
mapToTextValue(result) {
return _.map(result.data, (d, i) => {
if (d && d.text && d.value) {
return { text: d.text, value: d.value };
} else if (_.isObject(d)) {
return { text: d, value: i};
}
return { text: d, value: d };
});
}
doRequest(options) {
options.withCredentials = this.withCredentials;
options.headers = this.headers;
return this.backendSrv.datasourceRequest(options);
}
buildQueryParameters(options) {
//remove place holder targets
options.targets = _.filter(options.targets, target => {
return target.target !== 'select metric';
});
var targets = _.map(options.targets, target => {
return {
target: this.templateSrv.replace(target.target, options.scopedVars, ''),
refId: target.refId,
hide: target.hide,
type: target.type || 'timeserie'
};
});
options.targets = targets;
return options;
}
}
@@ -0,0 +1,28 @@
<!-- ################################################################################
# Copyright (c) 2018 JamesOsgood
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################ -->
<h5 class="section-heading">Query</h5>
<div class="gf-form-group">
<div class="gf-form">
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder=""></input>
</div>
</div>
@@ -0,0 +1,48 @@
<!-- ################################################################################
# Copyright (c) 2018 JamesOsgood
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################ -->
<datasource-http-settings current="ctrl.current">
</datasource-http-settings>
<h3 class="page-heading">MongoDB details</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">MongoDB URL</span>
<input class="gf-form-input width-30" type="text"
ng-model='ctrl.current.jsonData.mongodb_url'
placeholder="mongodb://localhost:27017" required>
</input>
</div>
<div class="gf-form">
<!--TODO: Make this compatible with $db Grafana variable-->
<span class="gf-form-label width-10">Database Name</span>
<input class="gf-form-input width-30" type="text"
ng-model='ctrl.current.jsonData.mongodb_db'
placeholder="myDatabase" required>
</input>
</div>
</div>
</div>
@@ -0,0 +1,49 @@
<!-- ################################################################################
# Copyright (c) 2018 JamesOsgood
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################ -->
<query-editor-row query-ctrl="ctrl" class="generic-datasource-query-row" has-text-edit-mode="true">
<div class="gf-form-inline">
<div class="gf-form max-width-8">
<!--Drop down menu for selecting query type-->
<select class="gf-form-input" ng-model="ctrl.target.type" ng-options="f as f for f in ['table', 'timeseries']"></select>
</div>
<div class="gf-form" ng-if="ctrl.target.rawQuery">
<textarea class="gf-form-input" rows="5" cols="150" ng-model="ctrl.target.target" spellcheck="false" ng-blur="ctrl.onChangeInternal()" />
</div>
<div ng-if="!ctrl.target.rawQuery">
<div class="gf-form">
<gf-form-dropdown model="ctrl.target.target"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getOptions($query)"
on-change="ctrl.onChangeInternal()">
</gf-form-dropdown>
</div>
</div>
<div class="gf-form gf-form--auto">
<div class="gf-form-label gf-form-label--auto"></div>
</div>
</div>
</query-editor-row>
@@ -0,0 +1,27 @@
<!-- ################################################################################
# Copyright (c) 2018 JamesOsgood
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################ -->
<section class="grafana-metric-options" >
<div class="gf-form">
</div>
</section>
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@@ -0,0 +1,41 @@
// ################################################################################
// # Copyright (c) 2018 JamesOsgood
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// #
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import {GenericDatasource} from './datasource';
import {GenericDatasourceQueryCtrl} from './query_ctrl';
class GenericConfigCtrl {}
GenericConfigCtrl.templateUrl = 'html/config.html';
class GenericQueryOptionsCtrl {}
GenericQueryOptionsCtrl.templateUrl = 'html/query.options.html';
class GenericAnnotationsQueryCtrl {}
GenericAnnotationsQueryCtrl.templateUrl = 'html/annotations.editor.html'
export {
GenericDatasource as Datasource,
GenericDatasourceQueryCtrl as QueryCtrl,
GenericConfigCtrl as ConfigCtrl,
GenericQueryOptionsCtrl as QueryOptionsCtrl,
GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl
};
@@ -0,0 +1,28 @@
{
"name": "Omniperf Data",
"id": "amd-omniperf-data-plugin",
"type": "datasource",
"backend": true,
"partials": {
"config": "public/app/plugins/datasource/simplejson/partials/config.html"
},
"metrics": true,
"annotations": false,
"info": {
"description": "An Omniperf datasource build for MongoDB",
"author": {
"name": "Audacious Software Group",
"url": ""
},
"logos": {
"small": "img/omniperf_circle.png",
"large": "img/omniperf_circle.png"
},
"version": "%VERSION%",
"updated": "%TODAY%"
},
"dependencies": {
"grafanaVersion": ">=7.0.0",
"plugins": []
}
}
@@ -0,0 +1,51 @@
// ################################################################################
// # Copyright (c) 2018 JamesOsgood
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// #
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import {QueryCtrl} from 'app/plugins/sdk';
import './css/query-editor.css!'
export class GenericDatasourceQueryCtrl extends QueryCtrl {
constructor($scope, $injector) {
super($scope, $injector);
this.scope = $scope;
this.target.target = this.target.target || 'select metric';
this.target.type = this.target.type || 'timeserie';
this.target.rawQuery = true;
}
getOptions(query) {
return this.datasource.metricFindQuery(query || '');
}
toggleEditorMode() {
this.target.rawQuery = !this.target.rawQuery;
}
onChangeInternal() {
this.panelCtrl.refresh(); // Asks the panel to refresh data.
}
}
GenericDatasourceQueryCtrl.templateUrl = 'html/query.editor.html';
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
node_modules/
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Compiled binary addons (https://nodejs.org/api/addons.html)
dist/
artifacts/
work/
ci/
e2e-results/
# Editors
.idea
@@ -0,0 +1,3 @@
module.exports = {
...require("./node_modules/@grafana/toolkit/src/config/prettier.plugin.config.json"),
};
@@ -0,0 +1,5 @@
# Changelog
## 1.0.0 (Unreleased)
Initial release.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 ACE IoT Solutions LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,44 @@
# Grafana Panel Plugin Template
[![Build](https://github.com/grafana/grafana-starter-panel/workflows/CI/badge.svg)](https://github.com/grafana/grafana-starter-panel/actions?query=workflow%3A%22CI%22)
This template is a starting point for building Grafana Panel Plugins in Grafana 7.0+
## What is Grafana Panel Plugin?
Panels are the building blocks of Grafana. They allow you to visualize data in different ways. While Grafana has several types of panels already built-in, you can also build your own panel, to add support for other visualizations.
For more information about panels, refer to the documentation on [Panels](https://grafana.com/docs/grafana/latest/features/panels/panels/)
## Getting started
1. Install dependencies
```bash
yarn install
```
2. Build plugin in development mode or run in watch mode
```bash
yarn dev
```
or
```bash
yarn watch
```
3. Build plugin in production mode
```bash
yarn build
```
## Learn more
- [Build a panel plugin tutorial](https://grafana.com/tutorials/build-a-panel-plugin)
- [Grafana documentation](https://grafana.com/docs/)
- [Grafana Tutorials](https://grafana.com/tutorials/) - Grafana Tutorials are step-by-step guides that help you make the most of Grafana
- [Grafana UI Library](https://developers.grafana.com/ui) - UI components to help you build interfaces using Grafana Design System
@@ -0,0 +1,31 @@
{
"name": "amd-custom-svg",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "grafana-toolkit plugin:build",
"test": "grafana-toolkit plugin:test",
"dev": "grafana-toolkit plugin:dev",
"watch": "grafana-toolkit plugin:dev --watch",
"sign": "grafana-toolkit plugin:sign",
"start": "yarn watch"
},
"author": "Audacious Software Group",
"license": "MIT",
"devDependencies": {
"@grafana/data": "latest",
"@grafana/toolkit": "latest",
"@grafana/ui": "latest",
"emotion": "10.0.27"
},
"engines": {
"node": ">=14"
},
"dependencies": {
"@grafana/runtime": "^8.1.1",
"@svgdotjs/svg.js": "^3.1.1",
"react-monaco-editor": "^0.44.0",
"tslib": "^2.3.1"
},
"_comments": "Dependencies are not included as part of Omniperf. It's the user's responsibility to accept any licensing implications before building the project."
}
@@ -0,0 +1,309 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React, { PureComponent } from 'react';
import { PanelProps } from '@grafana/data';
import { stylesFactory } from '@grafana/ui';
import { SVG, Element as SVGElement, Dom as SVGDom, extend as SVGExtend, Runner as SVGRunner } from '@svgdotjs/svg.js';
import { SVGOptions, SVGIDMapping } from 'types';
import { css } from 'emotion';
interface MappedElements {
[key: string]: SVGElement | SVGDom;
}
interface Props extends PanelProps<SVGOptions> {}
interface PanelState {
addAllIDs: boolean;
svgNode: SVGElement | SVGDom | null;
svgSource: string | null;
mappedElements: MappedElements | null;
svgMappings: SVGIDMapping[];
initFunctionSource: string;
initFunction: Function | null;
eventFunctionSource: string;
eventFunction: Function | null;
initialized: boolean;
}
interface TextMappedElement extends SVGElement {
textElement: Element;
}
SVGExtend(SVGElement, {
openOnClick: function (this: SVGElement, url: string) {
return window.open(url);
},
animateContRotate: function (this: SVGElement, speed: number) {
return (
this.animate(speed)
//@ts-ignore
.ease('-')
//@ts-ignore
.rotate(360)
.loop()
);
},
showOn: function (this: SVGElement, on: boolean) {
if (on) {
this.show();
} else {
this.hide();
}
},
animateOn: function (this: SVGElement, speed: number, on: boolean, animation: Function) {
if (on) {
//@ts-ignore
if (this.timeline()._runners.length === 0) {
animation(this.animate(speed));
} else {
this.timeline().play();
}
} else {
this.timeline().stop();
}
},
stopAnimation: function (this: SVGRunner) {
this.timeline().stop();
},
getParentNode: function (this: SVGElement) {
return this.node.parentNode;
},
getTopNode: function (this: SVGElement) {
let currentNode: Element = this.node as Element;
while (true) {
if (currentNode.parentNode && !currentNode.className.includes('svg-object')) {
currentNode = currentNode.parentNode as Element;
} else {
return currentNode;
}
}
},
});
SVGExtend(SVGDom, {
updateXHTMLFontText: function (this: SVGDom, newText: string) {
let currentElement: Element | TextMappedElement = this.node;
let i = 0;
while (currentElement.localName !== 'xhtml:font') {
if (currentElement.firstElementChild && i < 10) {
currentElement = currentElement.firstElementChild;
i++;
} else {
return;
}
}
currentElement.innerHTML = newText;
},
});
export class SVGPanel extends PureComponent<Props, PanelState> {
constructor(props: any) {
super(props);
this.state = {
addAllIDs: false,
svgNode: null,
svgSource: null,
svgMappings: [],
mappedElements: null,
initFunctionSource: '',
initFunction: null,
eventFunctionSource: '',
eventFunction: null,
initialized: false,
};
}
initializeMappings(svgNode: SVGElement | SVGDom) {
const svgMappings = this.props.options.svgMappings;
let currentElements: MappedElements = { ...this.state.mappedElements };
currentElements = {};
console.log('Current length is ', svgMappings.length);
for (let i = 0; i < svgMappings.length; i++) {
if (svgMappings[i].mappedName !== '') {
currentElements[this.props.options.svgMappings[i].mappedName] = svgNode.findOne(
`#${this.props.options.svgMappings[i].svgId}`
)!;
}
}
this.setState({ mappedElements: currentElements });
}
mapAllIDs(svgNode: SVGDom) {
let svgMappings: SVGIDMapping[] = [...this.props.options.svgMappings];
let nodeFilterID: NodeFilter = {
acceptNode: (node: Element) => {
if (node.id) {
if (node.id !== '') {
return NodeFilter.FILTER_ACCEPT;
}
}
return NodeFilter.FILTER_REJECT;
},
};
let svgWalker = document.createTreeWalker(svgNode.node, NodeFilter.SHOW_ALL, nodeFilterID);
let currentNode: Element | null = svgWalker.currentNode as Element;
while (currentNode) {
if (currentNode && currentNode.id) {
if (svgMappings.filter((mapping) => (currentNode ? mapping.svgId === currentNode.id : false)).length === 0) {
svgMappings.push({ svgId: currentNode.id, mappedName: '' });
}
}
currentNode = svgWalker.nextNode() as Element;
}
this.setState({ svgMappings: [...svgMappings], initialized: false });
this.props.options.svgMappings = [...svgMappings];
this.props.onOptionsChange(this.props.options);
this.forceUpdate();
}
mappingClickHandler(event: React.MouseEvent<HTMLElement, MouseEvent>) {
if (event.target) {
let clicked = event.target as Element;
let loopCount = 0;
let svgMappings: SVGIDMapping[] = [...this.props.options.svgMappings];
if (clicked.id) {
while (clicked.id === '') {
loopCount++;
if (loopCount > 20) {
return;
}
clicked = clicked.parentNode as Element;
}
for (let i = 0; i < svgMappings.length; i++) {
if (svgMappings[i].svgId === clicked.id) {
return;
}
}
svgMappings.push({ svgId: clicked.id, mappedName: '' });
this.setState({ svgMappings: [...svgMappings], initialized: false });
this.props.options.svgMappings = [...svgMappings];
this.props.onOptionsChange(this.props.options);
this.forceUpdate();
}
}
}
renderSVG(element: SVGSVGElement | SVGDom | null) {
if (element) {
if (
this.props.options.initSource !== this.state.initFunctionSource ||
this.state.addAllIDs !== this.props.options.addAllIDs
) {
this.setState({
initFunctionSource: this.props.options.initSource,
addAllIDs: this.props.options.addAllIDs,
initialized: false,
});
}
if (!this.state.initialized) {
console.log('initializing');
let svgNode = SVG(element);
svgNode.clear();
svgNode.svg(this.props.options.svgSource);
svgNode.size(this.props.width, this.props.height);
if (this.props.options.addAllIDs) {
this.mapAllIDs(svgNode);
}
this.initializeMappings(svgNode);
this.setState({ svgNode: svgNode });
try {
this.setState({
initFunction: Function(
'data',
'options',
'svgnode',
'svgmap',
this.props.replaceVariables(this.props.options.initSource)
),
});
if (this.state.mappedElements && this.state.initFunction) {
this.state.initFunction(this.props.data, this.props.options, this.state.svgNode, this.state.mappedElements);
this.setState({ initialized: true });
}
} catch (e) {
this.setState({ initialized: true });
console.log(`User init code failed: ${e}`);
}
}
try {
let eventFunction = this.state.eventFunction;
if (this.props.options.eventSource !== this.state.eventFunctionSource) {
let eventFunctionSource = this.props.options.eventSource;
eventFunction = Function(
'data',
'options',
'svgnode',
'svgmap',
this.props.replaceVariables(eventFunctionSource)
);
this.setState({ eventFunctionSource: eventFunctionSource, eventFunction: eventFunction, initialized: false });
}
if (this.state.mappedElements && eventFunction) {
eventFunction(this.props.data, this.props.options, this.state.svgNode, this.state.mappedElements);
}
} catch (e) {
console.log(`User event code failed: ${e}`);
}
return this.state.svgNode ? this.state.svgNode.svg() : null;
} else {
return null;
}
}
render() {
const styles = this.getStyles();
return (
<div
className={styles.wrapper}
onClick={this.props.options.captureMappings ? this.mappingClickHandler.bind(this) : undefined}
>
<svg
style={{
width: `${this.props.width}px`,
height: `${this.props.height}px`,
}}
className={'svg-object'}
ref={(ref) => this.renderSVG(ref)}
></svg>
</div>
);
}
getStyles = stylesFactory(() => {
return {
wrapper: css`
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
};
});
}
@@ -0,0 +1,90 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React from 'react';
import { PanelProps } from '@grafana/data';
import { SimpleOptions } from 'types';
import { css, cx } from 'emotion';
import { stylesFactory, useTheme } from '@grafana/ui';
interface Props extends PanelProps<SimpleOptions> {}
export const SimplePanel: React.FC<Props> = ({ options, data, width, height }) => {
const theme = useTheme();
const styles = getStyles();
return (
<div
className={cx(
styles.wrapper,
css`
width: ${width}px;
height: ${height}px;
`
)}
>
<svg
className={styles.svg}
width={width}
height={height}
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox={`-${width / 2} -${height / 2} ${width} ${height}`}
>
<g>
<circle style={{ fill: `${theme.isLight ? theme.palette.greenBase : theme.palette.blue95}` }} r={100} />
</g>
</svg>
<div className={styles.textBox}>
{options.showSeriesCount && (
<div
className={css`
font-size: ${theme.typography.size[options.seriesCountSize]};
`}
>
Number of series: {data.series.length}
</div>
)}
<div>Text option value: {options.text}</div>
</div>
</div>
);
};
const getStyles = stylesFactory(() => {
return {
wrapper: css`
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
};
});
@@ -0,0 +1,61 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import { SVGDefaults } from './types';
export const props_defaults: SVGDefaults = {
svgNode: `<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="377px"
viewBox="-0.5 -0.5 377 377"
style="max-width:100%;max-height:377px;"
>
<defs />
<g>
<ellipse
cx="188"
cy="188"
rx="185"
ry="185"
fill="#6666ff"
stroke="#ff6666"
stroke-width="7"
pointer-events="all"
/>
</g>
</svg>;
`,
initSource: '',
eventSource: '',
svgMappings: [
{
mappedName: 'barTwo',
svgId: 'rect4526',
},
{
mappedName: 'barThree',
svgId: 'rect4528',
},
],
};
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 81.9 71.52"><defs><style>.cls-1{fill:#84aff1;}.cls-2{fill:#3865ab;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="42.95" y1="16.88" x2="81.9" y2="16.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M55.46,62.43A2,2,0,0,1,54.07,59l4.72-4.54a2,2,0,0,1,2.2-.39l3.65,1.63,3.68-3.64a2,2,0,1,1,2.81,2.84l-4.64,4.6a2,2,0,0,1-2.22.41L60.6,58.26l-3.76,3.61A2,2,0,0,1,55.46,62.43Z"/><path class="cls-2" d="M37,0H2A2,2,0,0,0,0,2V31.76a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V2A2,2,0,0,0,37,0ZM4,29.76V8.84H35V29.76Z"/><path class="cls-3" d="M79.9,0H45a2,2,0,0,0-2,2V31.76a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V2A2,2,0,0,0,79.9,0ZM47,29.76V8.84h31V29.76Z"/><path class="cls-2" d="M37,37.76H2a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V39.76A2,2,0,0,0,37,37.76ZM4,67.52V46.6H35V67.52Z"/><path class="cls-2" d="M79.9,37.76H45a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V39.76A2,2,0,0,0,79.9,37.76ZM47,67.52V46.6h31V67.52Z"/><rect class="cls-1" x="10.48" y="56.95" width="4" height="5.79"/><rect class="cls-1" x="17.43" y="53.95" width="4" height="8.79"/><rect class="cls-1" x="24.47" y="50.95" width="4" height="11.79"/><path class="cls-1" d="M19.47,25.8a6.93,6.93,0,1,1,6.93-6.92A6.93,6.93,0,0,1,19.47,25.8Zm0-9.85a2.93,2.93,0,1,0,2.93,2.93A2.93,2.93,0,0,0,19.47,16Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,28 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import { PanelPlugin } from '@grafana/data';
import { SVGOptions } from './types';
import { SVGPanel } from './SVGjsPanel';
import { optionsBuilder } from 'options';
export const plugin = new PanelPlugin<SVGOptions>(SVGPanel).useFieldConfig().setPanelOptions(optionsBuilder);
@@ -0,0 +1,349 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React from 'react';
import Editor from '@monaco-editor/react';
import { SVGIDMapping, SVGOptions } from './types';
import { props_defaults } from 'examples';
import { HorizontalGroup, Label, Tooltip, Button, Input, VerticalGroup, stylesFactory } from '@grafana/ui';
import { PanelOptionsEditorProps, GrafanaTheme, PanelOptionsEditorBuilder } from '@grafana/data';
import { config } from '@grafana/runtime';
import { css } from 'emotion';
interface MonacoEditorProps {
value: string;
theme: string;
language: string;
onChange: (value?: string | undefined) => void;
}
class MonacoEditor extends React.PureComponent<MonacoEditorProps> {
getEditorValue: any | undefined;
editorInstance: any | undefined;
// onSourceChange = () => {
// console.log(this.getEditorValue);
// this.props.onChange(this.getEditorValue());
// };
handleEditorChange = (getEditorValue: any, editorInstance: any) => {
this.props.onChange(getEditorValue);
console.log('here is the current model value:', getEditorValue);
};
onEditorDidMount = (editorInstance: any, getEditorValue: any) => {
this.editorInstance = editorInstance;
this.getEditorValue = getEditorValue;
};
updateDimensions() {
this.editorInstance.layout();
}
render() {
const source = this.props.value;
if (this.editorInstance) {
this.editorInstance.layout();
}
return (
//<div onBlur={this.onSourceChange}>
<Editor
height={'33vh'}
language={this.props.language}
theme={this.props.theme}
value={source}
onMount={this.onEditorDidMount}
onChange={this.handleEditorChange}
/>
//</div>
);
}
}
interface SVGIDMappingProps {
value: SVGIDMapping;
index?: number;
styles?: any;
onChangeItem?: (a: SVGIDMapping, b: number) => void | undefined;
onAdd?: (a: SVGIDMapping) => void;
onDelete?: (a: number) => void;
}
class SvgMapping extends React.PureComponent<SVGIDMappingProps> {
constructor(props: SVGIDMappingProps) {
super(props);
this.state = { ...props.value };
}
render() {
const { value, index, onChangeItem, onAdd, onDelete } = this.props;
return (
<HorizontalGroup>
<Label>SVG ID</Label>
<Input
type="text"
name="svgId"
defaultValue={value.svgId}
onBlur={(e) => {
const svgId = e.currentTarget.value;
this.setState({ svgId: svgId });
onChangeItem && index && onChangeItem({ ...value, svgId: svgId }, index);
}}
/>
<Label>Mapped Name</Label>
<Input
type="text"
name="mappedName"
defaultValue={value.mappedName}
onBlur={(e) => {
const mappedName = e.currentTarget.value;
this.setState({ mappedName: mappedName });
onChangeItem && index && onChangeItem({ ...value, mappedName: mappedName }, index);
}}
/>
{value.svgId && onDelete && index !== undefined && (
<Tooltip content="Delete this mapping" theme={'info'}>
<Button
variant="destructive"
icon="trash-alt"
size="sm"
onClick={() => {
onDelete(index);
}}
>
Remove
</Button>
</Tooltip>
)}
{!value.svgId && onAdd && (
<Tooltip content="Add a new SVG Element ID to svgmap property mapping manually" theme={'info'}>
<Button
variant="secondary"
size="sm"
icon="plus-circle"
onClick={() => {
onAdd(this.state as SVGIDMapping);
}}
>
Add
</Button>
</Tooltip>
)}
</HorizontalGroup>
);
}
}
class SvgMappings extends React.PureComponent<PanelOptionsEditorProps<SVGIDMapping[]>> {
onChangeItem = (updatedMapping: SVGIDMapping, index: number) => {
let newMappings = [...this.props.value];
newMappings[index] = updatedMapping;
this.props.onChange(newMappings);
};
onAdd = (newMapping: SVGIDMapping) => {
if (newMapping.svgId !== '') {
let newMappings = [...this.props.value, newMapping];
this.props.onChange(newMappings);
}
};
onDelete = (index: number) => {
let newMappings = [...this.props.value];
newMappings.splice(index, 1);
this.props.onChange(newMappings);
};
render() {
const styles = getStyles(config.theme);
const svgMappings = this.props.value;
return (
<VerticalGroup>
<HorizontalGroup>
<Tooltip content="Clear all SVG Element ID to svgmap property mappings" theme="info">
<Button
variant="destructive"
icon="trash-alt"
size="sm"
onClick={() => {
this.props.onChange([]);
}}
>
Clear All
</Button>
</Tooltip>
<SvgMapping value={{ svgId: '', mappedName: '' }} styles={styles} onAdd={this.onAdd} />
</HorizontalGroup>
{svgMappings.map((currentMapping: SVGIDMapping, index: number) => {
return (
<SvgMapping
key={currentMapping.svgId}
value={currentMapping}
index={index}
onChangeItem={this.onChangeItem}
onDelete={this.onDelete}
styles={styles}
/>
);
})}
</VerticalGroup>
);
}
}
// ---------------------------------------
export const optionsBuilder = (builder: PanelOptionsEditorBuilder<SVGOptions>) => {
return builder
.addBooleanSwitch({
category: ['SVG Document'],
path: 'svgAutoComplete',
name: 'Enable SVG AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
})
.addCustomEditor({
category: ['SVG Document'],
path: 'svgSource',
name: 'SVG Document',
description: `Editor for SVG Document, while small tweaks can be made here, we recommend using a dedicated
Graphical SVG Editor and simply pasting the resulting XML here`,
id: 'svgSource',
defaultValue: props_defaults.svgNode,
//editor: (props) => {
editor: function myNamedFuntion(props) {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="xml"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
.addBooleanSwitch({
category: ['User JS Render'],
path: 'eventAutoComplete',
name: 'Enable Render JS AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
defaultValue: true,
})
.addCustomEditor({
category: ['User JS Render'],
path: 'eventSource',
name: 'User JS Render Code',
description: `The User JS Render code is executed whenever new data is available, the root svg document is available as 'svgnode',
and elements you've mapped using the SVG Mapping tools below are available as properties on the 'svgmap' object.
The Grafana DataFrame is provided as 'data' and the 'options' object can be used to pass values and references between
the Render context and the Init context`,
id: 'eventSource',
defaultValue: props_defaults.eventSource,
//editor: (props) => {
editor: function myNamedFunc(props) {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="javascript"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
.addBooleanSwitch({
category: ['User JS Init'],
path: 'initAutoComplete',
name: 'Enable Init JS AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
defaultValue: true,
})
.addCustomEditor({
category: ['User JS Init'],
path: 'initSource',
name: 'User JS Init Code',
description: `The User JS Init code is executed once when the panel loads, you can use this to define helper functions that
you later reference in the User JS Render code section. The sections have identical execution contexts, and any
JS objects you want to reference between them will need to be attached to the options object as properties`,
id: 'initSource',
defaultValue: props_defaults.initSource,
//editor: (props) => {
editor: function myNamedFunc(props) {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="javascript"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
.addBooleanSwitch({
category: ['SVG Mapping'],
path: 'addAllIDs',
name: 'Add all SVG Element IDs',
description:
'Parse the SVG Document for Elements with IDs assigned and automatically add them to the mapping list',
defaultValue: false,
})
.addBooleanSwitch({
category: ['SVG Mapping'],
path: 'captureMappings',
name: 'Enable SVG Mapping on Click',
description:
'When activated, clicking an element in the panel will attempt to map the clicked element or its nearest parent element with an ID assigned',
defaultValue: false,
})
.addCustomEditor({
category: ['SVG Mapping'],
id: 'svgMappings',
path: 'svgMappings',
name: 'SVG Mappings',
description:
'The SVG ID should match an element in the SVG document with an existing ID tag, the element will be attached to the "svgmap" object in the user code execution contexts as a property using the Mapped Name provided below',
defaultValue: props_defaults.svgMappings,
editor: SvgMappings,
});
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
colorPicker: css`
padding: 0 ${theme.spacing.sm};
`,
inputPrefix: css`
display: flex;
align-items: center;
`,
trashIcon: css`
color: ${theme.colors.textWeak};
cursor: pointer;
//
&:hover {
color: ${theme.colors.text};
}
`,
addIcon: css`
color: ${theme.colors.textWeak};
cursor: pointer;
//
&:hover {
color: ${theme.colors.text};
}
`,
};
});
@@ -0,0 +1,25 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/master/docs/sources/developers/plugins/plugin.schema.json",
"type": "panel",
"name": "custom-svg",
"id": "amd-custom-svg",
"info": {
"description": "",
"author": {
"name": "Cole Ramos",
"url": ""
},
"keywords": [],
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"screenshots": [],
"version": "%VERSION%",
"updated": "%TODAY%"
},
"dependencies": {
"grafanaDependency": ">=7.0.0",
"plugins": []
}
}
@@ -0,0 +1,50 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
type SeriesSize = 'sm' | 'md' | 'lg';
export interface SimpleOptions {
text: string;
showSeriesCount: boolean;
seriesCountSize: SeriesSize;
}
export interface SVGIDMapping {
svgId: string;
mappedName: string;
}
export interface SVGOptions {
captureMappings: boolean;
addAllIDs: boolean;
svgSource: string;
svgAutocomplete: boolean;
eventSource: string;
eventAutocomplete: boolean;
initSource: string;
initAutocomplete: boolean;
svgMappings: SVGIDMapping[];
}
export interface SVGDefaults {
svgNode: string; //svg default text
initSource: string; //init default text
eventSource: string; //render default text
svgMappings: SVGIDMapping[]; //default mappings
}
@@ -0,0 +1,11 @@
{
"extends": "./node_modules/@grafana/toolkit/src/config/tsconfig.plugin.json",
"include": ["src", "types"],
"compilerOptions": {
"types": ["@emotion/core"],
"rootDir": "./src",
"baseUrl": "./src",
"typeRoots": ["./node_modules/@types"],
"jsx": "react"
}
}
@@ -0,0 +1,35 @@
[tool.black]
line-length = 90
target-version = ['py38', 'py39']
include = '\.py$'
exclude = '''
(
/(
\.eggs
| \.git
| \.github
| \.tox
| \.venv
| \.misc
| \.vscode
| \.pyc
| dist
| external
| .pytest_cache
| build
| build-omniperf
)/
)
'''
[tool.pytest.ini_options]
addopts = [
"--import-mode=importlib",
"--cov=src",
]
pythonpath = [
".",
"src",
"src/utils",
"src/omniperf_cli/utils"
]
@@ -0,0 +1,12 @@
astunparse==1.6.2
colorlover
dash
matplotlib
numpy
pandas
pymongo
pyyaml
tabulate
tqdm
dash-svg
dash-bootstrap-components
@@ -0,0 +1,148 @@
#include "hip/hip_runtime.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define HIP_ASSERT(x) (assert((x)==hipSuccess))
// HIP kernel. Each thread takes care of one element of c
__global__ void vecCopy(double *a, double *b, double *c, int n,int stride)
{
// Get our global thread ID
int id = blockIdx.x*blockDim.x+threadIdx.x;
if (id < n) {
c[id] = a[id];
}
}
void usage()
{
printf("\nUsage: vcopy [n] [blocksize]\n\n");
exit(1);
return;
}
int main( int argc, char* argv[] )
{
// Size of vectors
int n; //64 MB
int blockSize, gridSize;
// Host input vectors
double *h_a;
double *h_b;
//Host output vector
double *h_c;
//Host output vector for verification
double *h_verify_c;
// Device input vectors
double *d_a;
double *d_b;
//Device output vector
double *d_c;
int stride = 1;
if(argc < 3)
usage();
n = atoi(argv[1]);
blockSize = atoi(argv[2]);
assert(n > 0);
assert(blockSize > 0);
// Size, in bytes, of each vector
size_t bytes = n*sizeof(double)*stride;
// Allocate memory for each vector on host
h_a = (double*)malloc(bytes);
h_b = (double*)malloc(bytes);
h_c = (double*)malloc(bytes);
h_verify_c = (double*)malloc(bytes);
printf("Finished allocating vectors on the CPU\n");
// Allocate memory for each vector on GPU
HIP_ASSERT(hipMalloc(&d_a, bytes));
HIP_ASSERT(hipMalloc(&d_b, bytes));
HIP_ASSERT(hipMalloc(&d_c, bytes));
printf("Finished allocating vectors on the GPU\n");
int i;
// Initialize vectors on host
for( i = 0; i < n; i++ ) {
h_a[i] = i;
h_b[i] = i;
}
// Copy host vectors to device
HIP_ASSERT(hipMemcpy( d_a, h_a, bytes, hipMemcpyHostToDevice));
HIP_ASSERT(hipMemcpy(d_b, h_b, bytes, hipMemcpyHostToDevice));
printf("Finished copying vectors to the GPU\n");
// Number of thread blocks in grid
gridSize = (int)ceil((float)n/blockSize);
//gridSize = 1;
int tot_waves = (blockSize*gridSize)/64;
float num_bytes_kb = ((sizeof(double))*n)/(1024);
float num_bytes_wave = (1.0*num_bytes_kb)/(1.0*tot_waves);
printf("sw thinks it moved %f KB per wave \n", (2.0*num_bytes_wave));
printf("Total threads: %d, Grid Size: %d block Size:%d, Wavefronts:%d:\n", n, gridSize, blockSize, tot_waves);
printf("Launching the kernel on the GPU\n");
// Execute the kernel
hipLaunchKernelGGL(vecCopy, dim3(gridSize), dim3(blockSize), 0, 0, d_a, d_b, d_c, n,stride);
hipDeviceSynchronize( );
printf("Finished executing kernel\n");
// Copy array back to host
HIP_ASSERT(hipMemcpy( h_c, d_c, bytes, hipMemcpyDeviceToHost));
printf("Finished copying the output vector from the GPU to the CPU\n");
//Compute for CPU
for(i=0; i <n; i++)
{
// h_verify_c[i*stride] = h_a[i*stride] + h_b[i*stride];
h_verify_c[i*stride] = h_a[i*stride] ;
}
//Verfiy results
for(i=0; i <n; i++)
{
if (abs(h_verify_c[i*stride] - h_c[i*stride]) > 1e-5)
{
printf("Error at position i %d, Expected: %f, Found: %f \n", i, h_c[i], d_c[i]);
}
}
// printf("Printing few elements from the output vector\n");
for(i=0; i < 20; i++)
{
// printf("Output[%d]:%f\n",i, h_c[i]);
}
printf("Releasing GPU memory\n");
// Release device memory
HIP_ASSERT(hipFree(d_a));
HIP_ASSERT(hipFree(d_b));
HIP_ASSERT(hipFree(d_c));
// Release host memory
printf("Releasing CPU memory\n");
free(h_a);
free(h_b);
free(h_c);
return 0;
}
@@ -0,0 +1,43 @@
################################################################################
# Copyright (c) 2021 - 2022 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
################################################################################
import os
import sys
from pathlib import Path
import subprocess
OMNIPERF_HOME = Path(__file__).resolve().parent
# OMNIPERF INFO
PROG = "omniperf"
SOC_LIST = ["mi50", "mi100", "mi200"]
DISTRO_MAP = {
"platform:el8": "rhel8",
"15.3": "sle15sp3",
}
version = os.path.join(OMNIPERF_HOME.parent, "VERSION")
try:
with open(version, "r") as file:
VER = file.read().replace("\n", "")
except EnvironmentError:
print("ERROR: Cannot find VERSION file at {}".format(version))
sys.exit(1)
@@ -0,0 +1,5 @@
/build*
/_build
/_doxygen
/.gitinfo
/omniperf.dox
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
import subprocess as sp
sys.path.insert(0, os.path.abspath(".."))
repo_version = "unknown"
# Determine short version by file in repo
if os.path.isfile("../../VERSION"):
with open("../../VERSION") as f:
repo_version = f.readline().strip()
def install(package):
sp.call([sys.executable, "-m", "pip", "install", package])
# -- Project information -----------------------------------------------------
project = "Omniperf"
copyright = "2022, Audacious Software Group"
author = "Audacious Software Group"
# The short X.Y version
version = repo_version
# The full version, including alpha/beta/rc tags
release = ""
# -- General configuration ---------------------------------------------------
install("sphinx_rtd_theme")
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.githubpages",
"myst_parser",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
source_suffix = {
".rst": "restructuredtext",
".txt": "markdown",
".md": "markdown",
}
from recommonmark.parser import CommonMarkParser
source_parsers = {".md": CommonMarkParser}
# The master toctree document.
master_doc = "index"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "Omniperfdoc"
html_theme_options = {
# "analytics_id": "G-1HLBBRSTT9", # Provided by Google in your dashboard
# "analytics_anonymize_ip": False,
"logo_only": False,
"display_version": True,
"prev_next_buttons_location": "bottom",
"style_external_links": False,
"vcs_pageview_mode": "",
# 'style_nav_header_background': 'white',
# Toc options
"collapse_navigation": True,
"sticky_navigation": True,
"navigation_depth": 4,
"includehidden": True,
"titles_only": False,
}
from pygments.styles import get_all_styles
# The name of the Pygments (syntax highlighting) style to use.
styles = list(get_all_styles())
preferences = ("emacs", "pastie", "colorful")
for pref in preferences:
if pref in styles:
pygments_style = pref
break
from recommonmark.transform import AutoStructify
# app setup hook
def setup(app):
app.add_config_value(
"recommonmark_config",
{
"auto_toc_tree_section": "Contents",
"enable_eval_rst": True,
"enable_auto_doc_ref": False,
},
True,
)
app.add_transform(AutoStructify)
app.add_config_value("docstring_replacements", {}, True)
app.connect("source-read", replaceString)
# function to replace version string througout documentation
def replaceString(app, docname, source):
result = source[0]
for key in app.config.docstring_replacements:
result = result.replace(key, app.config.docstring_replacements[key])
source[0] = result
docstring_replacements = {"{__VERSION__}": version}
@@ -0,0 +1,32 @@
# FAQ
```eval_rst
.. toctree::
:glob:
:maxdepth: 4
```
**1. How do I export profiling data I've already generated using Omniperf?**
In order to interact with the Grafana GUI you must sync data with the MongoDB backend. This interaction is done through ***database*** mode.
Simply pass the directory of your desired workload like so,
```shell
$ omniperf database --import -w <path-to-results> -H <hostname> -u <username> -t <team-name>
```
**2. python ast error: 'Constant' object has no attribute 'kind'**
This comes from a bug in the default astunparse 1.6.3 with python 3.8. Seems good with python 3.7 and 3.9.
Workaround:
```shell
$ pip3 uninstall astunparse
$ pip3 astunparse
```
**3. tabulate doesn't print properly**
Workaround:
```shell
$ export LC_ALL=C.UTF-8
$ export LANG=C.UTF-8
```
@@ -0,0 +1,86 @@
# Getting Started
```eval_rst
.. toctree::
:glob:
:maxdepth: 4
```
## Quickstart
1. **Launch & Profile the target application with the command line profiler**
The command line profiler launches the target application, calls the rocProfiler API, and collects profile results for the specified kernels, dispatches, and/or ipblocks.
To collect the default set of data for all kernels in the target application, launch:
```shell
$ omniperf profile -n vcopy -- ./vcopy 1048576 256
```
The app runs, each kernel is launched, and profiling results are generated. By default, results are written to ./workloads/\<name>. To collect all requested profile information, it may be required to replay kernels multiple times.
2. **Customize data collection**
Options are available to specify for which kernels data should be collected.
`-k`/`--kernel` enables filtering kernels by name. `-d`/`--dispatch` enables filtering based on dispatch ID. `-b`/`--ipblocks` enables profiling on one or more IP Block(s).
To view available metrics by IP Block you can always use `--list-metrics` to view a list of all available metrics organized by IP Block.
```shell
$ omniperf analyze --list-metrics <sys_arch>
```
Note that filtering can also be applied after the fact, at the analysis stage, however filtering at the profiling level will often speed up your overall profiling run time.
3. **Analyze at the command line**
After generating a local output folder (./workloads/\<name>), the command line tool can also be used to quickly interface with profiling results. View different metrics derived from your profiled results and get immediate access all metrics organized by IP block.
If no kernel, dispatch, or ipblock filters are applied at this stage, analysis will be reflective of the entirety of the profiling data.
To interact with profiling results from a different session, users just provide the workload path. `-p`/`--path` enables users to analyze existing profiling data in the Omniperf CLI.
4. **Analyze in the Grafana GUI**
To conduct a more in-depth analysis of profiling results we recommend users utilize the Omniperf Grafana GUI. To interact with profiling results, users must import their data to the MongoDB instance included in the Omniperf dockerfile.
To interact with Grafana GUI data, stored in the Omniperf DB, users can enter ***database*** mode. For example:
```shell
$ omniperf database --import [CONNECTION OPTIONS]
```
## Usage
### Modes
Modes change the fundamental behavior of the Omniperf command line tool. Depending on which mode is chosen, different command line options become available.
- **Profile**: Target application is launched on the local system utilizing AMDs [ROC Profiler](https://github.com/ROCm-Developer-Tools/rocprofiler). Depending on the profiling options chosen, selected kernels, dispatches, and/or IP Blocks in the application are profiled and results are stored locally in an output folder (./workloads/\<name>).
```shell
$ omniperf profile --help
```
- **Analyze**: Profiling data from `-p`/`--path` directory is loaded into the Omniperf CLI analyzer where users have immediate access to profiling results and generated metrics. Metrics are quickly generated from the entirety of your profiled application or a subset youve identified through the Omniperf CLI analysis filters.
To gererate a lightweight GUI interface users can add the `--gui` flag to their analysis command.
This mode is designed to be a middle ground to the highly detailed Omniperf Grafana GUI and is great for users who want immediate access to an IP Block theyre already familiar with.
```shell
$ omniperf analyze --help
```
- **Database**: Our detailed Grafana GUI is built on a MongoDB database. `--import` profiling results to the DB to interact with the workload in Grafana or `--remove` the workload from the DB.
Connection options will need to be specified. See the [*Omniperf Performance Analysis*](performance_analysis.md#omniperf-grafana-gui-import) section for more details on this.
```shell
$ omniperf database --help
```
## Basic Operations
Operation | Mode | Required Arguments
:--|:--|:--
Profile a workload | profile | `--name`, `-- <profile_cmd>`
Standalone roofline analysis | profile | `--name`, `--only-roof`, `-- <profile_cmd>`
Import a workload to database | database | `--import`, `--host`, `--username`, `--workload`, `--team`
Remove a workload from database | database | `--remove`, `--host`, `--username`, `--workload`, `--team`
Interact with profiling results from CLI | analyze | `--path`, `--gui`
@@ -0,0 +1,260 @@
# Omniperf Grafana GUI Analyzer
```eval_rst
.. toctree::
:glob:
:maxdepth: 4
```
## Features
The Omniperf Grafana GUI Analyzer supports the following features to facilitate MI GPU performance profiling and analysis:
- System and IP-Block Speed-of-Light (SOL)
- Multiple normalization options, including per-cycle, per-wave, per-kernel and per-second.
- Baseline comparisons
- Regex based Dispatch ID filtering
- Roofline Analysis
- Detailed per IP Block performance counters and metrics
- CPC/CPF
- SPI
- SQ
- SQC
- TA/TD
- TCP
- TCC (both aggregated and per-channel perf info)
### Speed-of-Light
Speed-of-light panels are provided at both the system and per IP block level to help diagnosis performance bottlenecks. The performance numbers of the workload under testing are compared to the theoretical maximum, (e.g. floating point operations, bandwidth, cache hit rate, etc.), to indicate the available room to further utilize the hardware capability.
### Multi Normalization
Multiple performance number normalizations are provided to allow performance inspection within both HW and SW context. The following normalizations are permitted:
- per cycle
- per wave
- per kernel
- per second
### Baseline Comparison
Omniperf enables baseline comparison to allow checking A/B effect. The current release limits the baseline comparison to the same SoC. Cross comparison between SoCs is in development.
For both the Current Workload and the Baseline Workload, one can independently setup the following filters to allow fine grained comparions:
- Workload Name
- GPU ID filtering (multi selection)
- Kernel Name filtering (multi selection)
- Dispatch ID filtering (Regex filtering)
- Omniperf Panels (multi selection)
### Regex based Dispatch ID filtering
This release enables regex based dispatch ID filtering to flexibly choose the kernel invocations. One may refer to [Regex Numeric Range Generator](https://3widgets.com/), to generate typical number ranges.
For example, if one wants to inspect Dispatch Range from 17 to 48, inclusive, the corresponding regex is : **(1[7-9]|[23]\d|4[0-8])**. The generated express can be copied over for filtering.
### Incremental Profiling
Omniperf supports incremental profiling to significantly speed up performance analysis.
> Refer to [*IP Block profiling*](performance_analysis.md#ip-block-profiling) section for this command.
By default, the entire application is profiled to collect perfmon counter for all IP blocks, giving a system level view of where the workload stands in terms of performance optimization opportunities and bottlenecks.
After that one may focus on only a few IP blocks, (e.g., L1 Cache or LDS) to closely check the effect of software optimizations, without performing application replay for all other IP Blocks. This saves lots of compute time. In addition, the prior profiling results for other IP blocks are not overwritten. Instead, they can be merged during the import to piece together the system view.
### Color Coding
The uniform color coding is applied to most visualizations (bars, table, diagrams etc). Typically, Yellow color means over 50%, while Red color mean over 90% percent, for easy inspection.
### Global Variables and Configurations
![Grafana GUI Global Variables](images/global_variables.png)
## Omniperf Panels
### Overview
There are currently 18 main panel categories available for analyzing the compute workload performance. Each category contains several panels for close inspection of the system performance.
- Kernel Statistics
- Kernel time histogram
- Top Ten bottleneck kernels
- System Speed-of-Light
- Speed-of-Light
- System Info table
- Memory Chart Analysis
- Roofline Analysis
- FP32/FP64
- FP16/INT8
- Command Processor
- Command Processor - Fetch (CPF)
- Command Processor - Controller (CPC)
- Shader Processing Input (SPI)
- SPI Stats
- SPI Resource Allocations
- Wavefront Launch
- Wavefront Launch Stats
- Wavefront runtime stats
- per-SE Wavefront Scheduling performance
- Wavefront Lifetime
- Wavefront lifetime breakdown
- per-SE wavefront life (average)
- per-SE wavefront life (histogram)
- Wavefront Occupancy
- per-SE wavefront occupancy
- per-CU wavefront occupancy
- Compute Unit - Instruction Mix
- per-wave Instruction mix
- per-wave VALU Arithmetic instruction mix
- per-wave MFMA Arithmetic instruction mix
- Compute Unit - Compute Pipeline
- Speed-of-Light: Compute Pipeline
- Arithmetic OPs count
- Compute pipeline stats
- Memory latencies
- Local Data Share (LDS)
- Speed-of-Light: LDS
- LDS stats
- Instruction Cache
- Speed-of-Light: Instruction Cache
- Instruction Cache Accesses
- Constant Cache
- Speed-of-Light: Constant Cache
- Constant Cache Accesses
- Constant Cache - L2 Interface stats
- Texture Address and Texture Data
- Texture Address (TA)
- Texture Data (TD)
- L1 Cache
- Speed-of-Light: L1 Cache
- L1 Cache Accesses
- L1 Cache Stalls
- L1 - L2 Transactions
- L1 - UTCL1 Interface stats
- L2 Cache
- Speed-of-Light: L2 Cache
- L2 Cache Accesses
- L2 - EA Transactions
- L2 - EA Stalls
- L2 Cache Per Channel Performance
- Per-channel L2 Hit rate
- Per-channel L1-L2 Read requests
- Per-channel L1-L2 Write Requests
- Per-channel L1-L2 Atomic Requests
- Per-channel L2-EA Read requests
- Per-channel L2-EA Write requests
- Per-channel L2-EA Atomic requests
- Per-channel L2-EA Read latency
- Per-channel L2-EA Write latency
- Per-channel L2-EA Atomic latency
- Per-channel L2-EA Read stall (I/O, GMI, HBM)
- Per-channel L2-EA Write stall (I/O, GMI, HBM, Starve)
Most panels are designed around a specific IP block to thoroughly understand its behavior. Additional panels, including custom panels, could also be added to aid the performance analysis.
### System Info Panel
![System Info Panel](images/System_info_panel.png)
### Kernel Statistics
#### Kernel Time Histogram
![Kernel Time Histogram](images/Kernel_time_histogram.png)
#### Top Bottleneck Kernels
![Top Bottleneck Kernels](images/Top_bottleneck_kernels.png)
#### Top Bottleneck Dispatches
![Top Bottleneck Dispatches](images/Top_bottleneck_dispatches.png)
#### Current and Baseline Dispatch IDs (Filtered)
![Current and Baseline Dispatch IDs](images/Current_and_baseline_dispatch_ids.png)
### System Speed-of-Light
![System Speed-of-Light](images/System_speed_of_light.png)
### Memory Chart Analysis
> Note: The Memory Chart Analysis support multiple normalizations. Due to the space limit, all transactions, when normalized to per-sec, default to unit of Billion transactions per second.
![Memory Chart Analysis](images/Memory_chart_analysis.png)
### Roofline Analysis
![Roofline Analysis](images/Roofline_analysis.png)
### Command Processor
![Command Processor](images/Command_processor.png)
### Shader Processing Input (SPI)
![Shader Processing Input](images/Shader_processing_input.png)
### Wavefront Launch
![Wavefront Launch](images/Wavefront_launch.png)
### Compute Unit - Instruction Mix
#### Instruction Mix
![Instruction Mix](images/Instruction_mix.png)
#### VALU Arithmetic Instruction Mix
![VALU Arithmetic Instruction Mix](images/VALU_arithmetic_instruction_mix.png)
#### MFMA Arithmetic Instruction Mix
![MFMA Arithmetic Instruction Mix](images/MFMA_arithmetic_instruction_mix.png)
#### VMEM Arithmetic Instruction Mix
![VMEM Arithmetic Instruction Mix](images/VMEM_arithmetic_intensity_mix.png)
### Compute Unit - Compute Pipeline
#### Speed-of-Light
![Speed-of-Light](images/Comp_pipe_sol.png)
#### Compute Pipeline Stats
![Compute Pipeline Stats](images/Compute_pipeline_stats.png)
#### Arithmetic Operations
![Arithmetic Operations](images/Arithmetic_operations.png)
#### Memory Latencies
![Memory Latencies](images/Memory_latencies.png)
### Local Data Share (LDS)
#### Speed-of-Light
![Speed-of-Light](images/LDS_sol.png)
#### LDS Stats
![LDS Stats](images/LDS_stats.png)
### Instruction Cache
#### Speed-of-Light
![Speed-of-Light](images/Instruc_cache_sol.png)
#### Instruction Cache Stats
![Instruction Cache Stats](images/Instruction_cache_stats.png)
### Scalar L1D Cache
#### Speed-of-Light
![](images/L1D_sol.png)
#### Constant Cache Stats
![Constant Cache Stats](images/Vec_L1D_cache_accesses.png)
#### Constant Cache - L2 Interface
![Constant Cache - L2 Interface](images/Constant_cache_l2_interface.png)
### Texture Address and Texture Data
#### Texture Address (TA)
![Texture Address](images/Texture_address.png)
#### Texture Data (TD)
![Texture Data](images/Texture_data.png)
### Vector L1D Cache
#### Speed-of-Light
![Speed-of-Light](images/Vec_L1D_cache_sol.png)
#### Vector L1D Cache Accesses
![Vector L1D Cache Accesses](images/Vec_L1D_cache_accesses.png)
#### L1 Cache Stalls
![L1 Cache Stalls](images/L1_cache_stalls.png)
#### L1 - L2 Transactions
![L1 - L2 Transactions](images/L1_l2_transactions.png)
#### L1 - UTCL1 Interface Stats
![L1 - UTCL1 Interface Stats](images/L1_utcl1_transactions.png)
### L2 Cache
#### Speed-of-Light
![Speed-of-Light](images/L2_cache_sol.png)
#### L2 Cache Accesses
![L2 Cache Accesses](images/L2_cache_accesses.png)
#### L2 - EA Transactions
![L2 - EA Transactions](images/L2_ea_transactions.png)
#### L2 - EA Stalls
![L2 - EA Stalls](images/L2_ea_stalls.png)
### L2 Cache Per Channel Performance
#### L1-L2 Transactions
![L1-L2 Transactions](images/L1_l2_transactions_per_channel.png)
#### L2-EA Transactions
![L2-EA Transactions](images/L2_ea_transactions_per_channel.png)
#### L2-EA Latencies
![L2-EA Latencies](images/L2_ea_latencies_per_channel.png)
#### L2-EA Stalls
![L2-EA Stalls](images/L2_ea_stalls_per_channel.png)
#### L2-EA Write Stalls
![L2-EA Write Stalls](images/L2_ea_write_stalls_per_channel.png)
#### L2-EA Write Starvation
![L2-EA Write Starvation](images/L2_ea_write_starvation_per_channel.png)
@@ -0,0 +1,19 @@
# Omniperf High Level Design
```eval_rst
.. toctree::
:glob:
:maxdepth: 4
```
The [Omniperf](https://github.com/AMDResearch/omniperf) Tool is architecturally composed of three major components, as shown in the following figure.
- **Omniperf Profiling**: Acquire raw performance counters via application replay based on the [ROC Profiler](https://github.com/ROCm-Developer-Tools/rocprofiler). A set of MI200 specific micro benchmarks are also run to acquire the hierarchical roofline data.
- **Omniperf Grafana Analyzer**:
- *Grafana database import*: All raw performance counters are imported into the backend MongoDB database for Grafana GUI analysis and visualization.
- *Grafana GUI Analyzer*: A Grafana dashboard is designed to retrieve the raw counters info from the backend database. It also creates the relevant performance metrics and visualization.
- **Omniperf Standalone GUI Analyzer**: A standalone GUI is provided to enable performance analysis without importing data into the backend database.
![Omniperf Architectual Diagram](images/omniperf_architecture.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Some files were not shown because too many files have changed in this diff Show More