未验证 提交 7f375258 编写于 作者: Y Yan Chunwei 提交者: GitHub

Merge pull request #1 from PaddlePaddle/feature/refactor_im_singleton

#!/bin/bash
set -e
readonly VERSION="3.8"
version=$(clang-format -version)
if ! [[ $version == *"$VERSION"* ]]; then
echo "clang-format version check failed."
echo "a version contains '$VERSION' is needed, but get '$version'"
echo "you can install the right version, and make an soft-link to '\$PATH' env"
exit -1
fi
clang-format $@
...@@ -99,3 +99,5 @@ ENV/ ...@@ -99,3 +99,5 @@ ENV/
# mypy # mypy
.mypy_cache/ .mypy_cache/
.DS_Store
- repo: https://github.com/pre-commit/mirrors-yapf.git
sha: v0.16.0
hooks:
- id: yapf
files: \.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
sha: a11d9314b22d8f8c7556443875b731ef05965464
hooks:
- id: check-merge-conflict
- id: check-symlinks
- id: end-of-file-fixer
- id: trailing-whitespace
- id: detect-private-key
- id: check-symlinks
- id: check-added-large-files
- repo: local
hooks:
- id: clang-format-with-version-check
name: clang-format
description: Format files with ClangFormat.
entry: bash ./.clang_format.hook -i
language: system
files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|proto)$
language: cpp
cache:
directories:
- $HOME/.ccache
- $HOME/.cache/pip
sudo: required
dist: trusty
os:
- linux
addons:
apt:
packages:
- gcc-4.8
- g++-4.8
- git
- python
- python-pip
- python2.7-dev
- python-wheel
- clang-format-3.8
- ccache
script:
/bin/bash ./tests.sh
notifications:
email:
on_success: change
on_failure: always
cmake_minimum_required(VERSION 3.2)
project(VisualDL)
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "-fPIC")
set(THIRD_PARTY_PATH "${CMAKE_BINARY_DIR}/third_party" CACHE STRING
"A path setting third party libraries download & build directories.")
################################ Configurations #######################################
option(WITH_TESTING "Compile VisualDL with unit testing" ON)
include(external/zlib) # download, build, install zlib
include(external/gflags) # download, build, install gflags
include(external/glog) # download, build, install glog
include(external/gtest) # download, build, install gtest
include(external/pybind11) # download pybind11
include(external/protobuf) # download, build, install protobuf
include(external/python) # find python and set path
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/local/include)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/visualdl/storage)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/visualdl/logic)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/visualdl/python)
add_executable(vl_test
${PROJECT_SOURCE_DIR}/visualdl/test.cc
${PROJECT_SOURCE_DIR}/visualdl/storage/storage_test.cc
${PROJECT_SOURCE_DIR}/visualdl/utils/test_concurrency.cc
${PROJECT_SOURCE_DIR}/visualdl/logic/im_test.cc
${PROJECT_SOURCE_DIR}/visualdl/logic/sdk_test.cc
${PROJECT_SOURCE_DIR}/visualdl/utils/concurrency.h
${PROJECT_SOURCE_DIR}/visualdl/utils/filesystem.h
)
target_link_libraries(vl_test storage sdk im gtest glog protobuf gflags pthread)
enable_testing ()
add_custom_target(test_init COMMAND ./init_test.sh $CMAKE_BINARY_DIR)
add_test(NAME vstest COMMAND ./vl_test)
set_target_properties(vl_test PROPERTIES DEPENDS test_init)
# VisualDL # VisualDL
\ No newline at end of file
### How to use
#### Step 1: build frontend
```shell
cd frontend
npm install
npm run build
```
this step will generate a dist directory under frontend
### Step 2: copy frontend/dist to server/visualdl/frontend/dist
```shell
mkdir -p server/visualdl/frontend/dist
cp -r frontend/dist server/visualdl/frontend/dist
```
#### Step 3: build and install Python package
```shell
cd server/
python setup.py bdist_wheel
cd dist
sudo pip install --upgrade visualdl-0.0.1-py2-none-any.whl
```
### Step 3: run
```
# cd to visualdl install dir
cd /usr/local/lib/python2.7/site-packages/visualdl/
python visual_dl.py --port=8888
```
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
INCLUDE(ExternalProject)
SET(GFLAGS_SOURCES_DIR ${THIRD_PARTY_PATH}/gflags)
SET(GFLAGS_INSTALL_DIR ${THIRD_PARTY_PATH}/install/gflags)
SET(GFLAGS_INCLUDE_DIR "${GFLAGS_INSTALL_DIR}/include" CACHE PATH "gflags include directory." FORCE)
IF(WIN32)
set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/gflags.lib" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE)
ELSE(WIN32)
set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/libgflags.a" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE)
ENDIF(WIN32)
INCLUDE_DIRECTORIES(${GFLAGS_INCLUDE_DIR})
ExternalProject_Add(
extern_gflags
${EXTERNAL_PROJECT_LOG_ARGS}
GIT_REPOSITORY "https://github.com/gflags/gflags.git"
GIT_TAG 77592648e3f3be87d6c7123eb81cbad75f9aef5a
PREFIX ${GFLAGS_SOURCES_DIR}
UPDATE_COMMAND ""
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
-DCMAKE_INSTALL_PREFIX=${GFLAGS_INSTALL_DIR}
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DBUILD_TESTING=OFF
-DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE}
${EXTERNAL_OPTIONAL_ARGS}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GFLAGS_INSTALL_DIR}
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
-DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE}
)
ADD_LIBRARY(gflags STATIC IMPORTED GLOBAL)
SET_PROPERTY(TARGET gflags PROPERTY IMPORTED_LOCATION ${GFLAGS_LIBRARIES})
ADD_DEPENDENCIES(gflags extern_gflags)
LIST(APPEND external_project_dependencies gflags)
IF(WITH_C_API)
INSTALL(DIRECTORY ${GFLAGS_INCLUDE_DIR} DESTINATION third_party/gflags)
IF(ANDROID)
INSTALL(FILES ${GFLAGS_LIBRARIES} DESTINATION third_party/gflags/lib/${ANDROID_ABI})
ELSE()
INSTALL(FILES ${GFLAGS_LIBRARIES} DESTINATION third_party/gflags/lib)
ENDIF()
ENDIF()
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
INCLUDE(ExternalProject)
SET(GLOG_SOURCES_DIR ${THIRD_PARTY_PATH}/glog)
SET(GLOG_INSTALL_DIR ${THIRD_PARTY_PATH}/install/glog)
SET(GLOG_INCLUDE_DIR "${GLOG_INSTALL_DIR}/include" CACHE PATH "glog include directory." FORCE)
IF(WIN32)
SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.lib" CACHE FILEPATH "glog library." FORCE)
ELSE(WIN32)
SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.a" CACHE FILEPATH "glog library." FORCE)
ENDIF(WIN32)
INCLUDE_DIRECTORIES(${GLOG_INCLUDE_DIR})
ExternalProject_Add(
extern_glog
${EXTERNAL_PROJECT_LOG_ARGS}
DEPENDS gflags
GIT_REPOSITORY "https://github.com/google/glog.git"
GIT_TAG v0.3.5
PREFIX ${GLOG_SOURCES_DIR}
UPDATE_COMMAND ""
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
-DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR}
-DCMAKE_INSTALL_LIBDIR=${GLOG_INSTALL_DIR}/lib
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DWITH_GFLAGS=ON
-Dgflags_DIR=${GFLAGS_INSTALL_DIR}/lib/cmake/gflags
-DBUILD_TESTING=OFF
-DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE}
${EXTERNAL_OPTIONAL_ARGS}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GLOG_INSTALL_DIR}
-DCMAKE_INSTALL_LIBDIR:PATH=${GLOG_INSTALL_DIR}/lib
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
-DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE}
)
ADD_LIBRARY(glog STATIC IMPORTED GLOBAL)
SET_PROPERTY(TARGET glog PROPERTY IMPORTED_LOCATION ${GLOG_LIBRARIES})
ADD_DEPENDENCIES(glog extern_glog gflags)
LINK_LIBRARIES(glog gflags)
LIST(APPEND external_project_dependencies glog)
IF(WITH_C_API)
INSTALL(DIRECTORY ${GLOG_INCLUDE_DIR} DESTINATION third_party/glog)
IF(ANDROID)
INSTALL(FILES ${GLOG_LIBRARIES} DESTINATION third_party/glog/lib/${ANDROID_ABI})
ELSE()
INSTALL(FILES ${GLOG_LIBRARIES} DESTINATION third_party/glog/lib)
ENDIF()
ENDIF()
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
IF(WITH_TESTING)
ENABLE_TESTING()
INCLUDE(ExternalProject)
SET(GTEST_SOURCES_DIR ${THIRD_PARTY_PATH}/gtest)
SET(GTEST_INSTALL_DIR ${THIRD_PARTY_PATH}/install/gtest)
SET(GTEST_INCLUDE_DIR "${GTEST_INSTALL_DIR}/include" CACHE PATH "gtest include directory." FORCE)
INCLUDE_DIRECTORIES(${GTEST_INCLUDE_DIR})
IF(WIN32)
set(GTEST_LIBRARIES
"${GTEST_INSTALL_DIR}/lib/gtest.lib" CACHE FILEPATH "gtest libraries." FORCE)
set(GTEST_MAIN_LIBRARIES
"${GTEST_INSTALL_DIR}/lib/gtest_main.lib" CACHE FILEPATH "gtest main libraries." FORCE)
ELSE(WIN32)
set(GTEST_LIBRARIES
"${GTEST_INSTALL_DIR}/lib/libgtest.a" CACHE FILEPATH "gtest libraries." FORCE)
set(GTEST_MAIN_LIBRARIES
"${GTEST_INSTALL_DIR}/lib/libgtest_main.a" CACHE FILEPATH "gtest main libraries." FORCE)
ENDIF(WIN32)
IF(WITH_MKLML)
# wait for mklml downloading completed
SET(GTEST_DEPENDS ${MKLML_PROJECT})
ENDIF()
ExternalProject_Add(
extern_gtest
${EXTERNAL_PROJECT_LOG_ARGS}
DEPENDS ${GTEST_DEPENDS}
GIT_REPOSITORY "https://github.com/google/googletest.git"
GIT_TAG "release-1.8.0"
PREFIX ${GTEST_SOURCES_DIR}
UPDATE_COMMAND ""
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
-DCMAKE_INSTALL_PREFIX=${GTEST_INSTALL_DIR}
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DBUILD_GMOCK=ON
-Dgtest_disable_pthreads=ON
-Dgtest_force_shared_crt=ON
-DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE}
${EXTERNAL_OPTIONAL_ARGS}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GTEST_INSTALL_DIR}
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
-DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE}
)
ADD_LIBRARY(gtest STATIC IMPORTED GLOBAL)
SET_PROPERTY(TARGET gtest PROPERTY IMPORTED_LOCATION ${GTEST_LIBRARIES})
ADD_DEPENDENCIES(gtest extern_gtest)
ADD_LIBRARY(gtest_main STATIC IMPORTED GLOBAL)
SET_PROPERTY(TARGET gtest_main PROPERTY IMPORTED_LOCATION ${GTEST_MAIN_LIBRARIES})
ADD_DEPENDENCIES(gtest_main extern_gtest)
LIST(APPEND external_project_dependencies gtest gtest_main)
ENDIF(WITH_TESTING)
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
INCLUDE(ExternalProject)
# Always invoke `FIND_PACKAGE(Protobuf)` for importing function protobuf_generate_cpp
FIND_PACKAGE(Protobuf QUIET)
SET(PROTOBUF_FOUND "OFF")
if(NOT COMMAND protobuf_generate_python) # before cmake 3.4, protobuf_genrerate_python is not defined.
function(protobuf_generate_python SRCS)
# shameless copy from https://github.com/Kitware/CMake/blob/master/Modules/FindProtobuf.cmake
if(NOT ARGN)
message(SEND_ERROR "Error: PROTOBUF_GENERATE_PYTHON() called without any proto files")
return()
endif()
if(PROTOBUF_GENERATE_CPP_APPEND_PATH)
# Create an include path for each file specified
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(ABS_PATH ${ABS_FIL} PATH)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
else()
set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
endif()
if(DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS)
set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}")
endif()
if(DEFINED Protobuf_IMPORT_DIRS)
foreach(DIR ${Protobuf_IMPORT_DIRS})
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
endif()
set(${SRCS})
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(FIL_WE ${FIL} NAME_WE)
if(NOT PROTOBUF_GENERATE_CPP_APPEND_PATH)
get_filename_component(FIL_DIR ${FIL} DIRECTORY)
if(FIL_DIR)
set(FIL_WE "${FIL_DIR}/${FIL_WE}")
endif()
endif()
list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}_pb2.py")
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}_pb2.py"
COMMAND ${Protobuf_PROTOC_EXECUTABLE} --python_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL}
DEPENDS ${ABS_FIL} ${Protobuf_PROTOC_EXECUTABLE}
COMMENT "Running Python protocol buffer compiler on ${FIL}"
VERBATIM )
endforeach()
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
endfunction()
endif()
# Print and set the protobuf library information,
# finish this cmake process and exit from this file.
macro(PROMPT_PROTOBUF_LIB)
SET(protobuf_DEPS ${ARGN})
MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}")
MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}")
MESSAGE(STATUS "Protobuf version: ${PROTOBUF_VERSION}")
INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR})
# Assuming that all the protobuf libraries are of the same type.
IF(${PROTOBUF_LIBRARY} MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$")
SET(protobuf_LIBTYPE STATIC)
ELSEIF(${PROTOBUF_LIBRARY} MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$")
SET(protobuf_LIBTYPE SHARED)
ELSE()
MESSAGE(FATAL_ERROR "Unknown library type: ${PROTOBUF_LIBRARY}")
ENDIF()
ADD_LIBRARY(protobuf ${protobuf_LIBTYPE} IMPORTED GLOBAL)
SET_PROPERTY(TARGET protobuf PROPERTY IMPORTED_LOCATION ${PROTOBUF_LIBRARY})
ADD_LIBRARY(protobuf_lite ${protobuf_LIBTYPE} IMPORTED GLOBAL)
SET_PROPERTY(TARGET protobuf_lite PROPERTY IMPORTED_LOCATION ${PROTOBUF_LITE_LIBRARY})
ADD_LIBRARY(libprotoc ${protobuf_LIBTYPE} IMPORTED GLOBAL)
SET_PROPERTY(TARGET libprotoc PROPERTY IMPORTED_LOCATION ${PROTOC_LIBRARY})
ADD_EXECUTABLE(protoc IMPORTED GLOBAL)
SET_PROPERTY(TARGET protoc PROPERTY IMPORTED_LOCATION ${PROTOBUF_PROTOC_EXECUTABLE})
# FIND_Protobuf.cmake uses `Protobuf_PROTOC_EXECUTABLE`.
# make `protobuf_generate_cpp` happy.
SET(Protobuf_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE})
FOREACH(dep ${protobuf_DEPS})
ADD_DEPENDENCIES(protobuf ${dep})
ADD_DEPENDENCIES(protobuf_lite ${dep})
ADD_DEPENDENCIES(libprotoc ${dep})
ADD_DEPENDENCIES(protoc ${dep})
ENDFOREACH()
LIST(APPEND external_project_dependencies protobuf)
RETURN()
endmacro()
macro(SET_PROTOBUF_VERSION)
EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION)
STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}")
endmacro()
set(PROTOBUF_ROOT "" CACHE PATH "Folder contains protobuf")
if (NOT "${PROTOBUF_ROOT}" STREQUAL "")
find_path(PROTOBUF_INCLUDE_DIR google/protobuf/message.h PATHS ${PROTOBUF_ROOT}/include)
find_library(PROTOBUF_LIBRARY protobuf PATHS ${PROTOBUF_ROOT}/lib)
find_library(PROTOBUF_LITE_LIBRARY protobuf-lite PATHS ${PROTOBUF_ROOT}/lib)
find_library(PROTOBUF_PROTOC_LIBRARY protoc PATHS ${PROTOBUF_ROOT}/lib)
find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS ${PROTOBUF_ROOT}/bin)
if (PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY AND PROTOBUF_LITE_LIBRARY AND PROTOBUF_PROTOC_LIBRARY AND PROTOBUF_PROTOC_EXECUTABLE)
message(STATUS "Using custom protobuf library in ${PROTOBUF_ROOT}.")
SET_PROTOBUF_VERSION()
PROMPT_PROTOBUF_LIB()
else()
message(WARNING "Cannot find protobuf library in ${PROTOBUF_ROOT}.")
endif()
endif()
FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST)
STRING(REPLACE "extern_" "" TARGET_DIR_NAME "${TARGET_NAME}")
SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_DIR_NAME})
SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_DIR_NAME})
SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE)
SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE)
SET(${TARGET_NAME}_LITE_LIBRARY
"${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${CMAKE_STATIC_LIBRARY_SUFFIX}"
PARENT_SCOPE)
SET(${TARGET_NAME}_LIBRARY
"${PROTOBUF_INSTALL_DIR}/lib/libprotobuf${CMAKE_STATIC_LIBRARY_SUFFIX}"
PARENT_SCOPE)
SET(${TARGET_NAME}_PROTOC_LIBRARY
"${PROTOBUF_INSTALL_DIR}/lib/libprotoc${CMAKE_STATIC_LIBRARY_SUFFIX}"
PARENT_SCOPE)
SET(${TARGET_NAME}_PROTOC_EXECUTABLE
"${PROTOBUF_INSTALL_DIR}/bin/protoc${CMAKE_EXECUTABLE_SUFFIX}"
PARENT_SCOPE)
SET(OPTIONAL_CACHE_ARGS "")
SET(OPTIONAL_ARGS "")
IF(BUILD_FOR_HOST)
SET(OPTIONAL_ARGS "-Dprotobuf_WITH_ZLIB=OFF")
ELSE()
SET(OPTIONAL_ARGS
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
"-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}"
"-Dprotobuf_WITH_ZLIB=ON"
"-DZLIB_ROOT:FILEPATH=${ZLIB_ROOT}"
${EXTERNAL_OPTIONAL_ARGS})
SET(OPTIONAL_CACHE_ARGS "-DZLIB_ROOT:STRING=${ZLIB_ROOT}")
ENDIF()
ExternalProject_Add(
${TARGET_NAME}
${EXTERNAL_PROJECT_LOG_ARGS}
PREFIX ${PROTOBUF_SOURCES_DIR}
UPDATE_COMMAND ""
DEPENDS zlib
GIT_REPOSITORY "https://github.com/google/protobuf.git"
GIT_TAG "9f75c5aa851cd877fb0d93ccc31b8567a6706546"
CONFIGURE_COMMAND
${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/${TARGET_NAME}/cmake
${OPTIONAL_ARGS}
-Dprotobuf_BUILD_TESTS=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR}
-DCMAKE_INSTALL_LIBDIR=lib
CMAKE_CACHE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${PROTOBUF_INSTALL_DIR}
-DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE}
-DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
${OPTIONAL_CACHE_ARGS}
)
ENDFUNCTION()
SET(PROTOBUF_VERSION 3.1)
IF(CMAKE_CROSSCOMPILING)
build_protobuf(protobuf_host TRUE)
LIST(APPEND external_project_dependencies protobuf_host)
SET(PROTOBUF_PROTOC_EXECUTABLE ${protobuf_host_PROTOC_EXECUTABLE}
CACHE FILEPATH "protobuf executable." FORCE)
ENDIF()
IF(NOT PROTOBUF_FOUND)
build_protobuf(extern_protobuf FALSE)
SET(PROTOBUF_INCLUDE_DIR ${extern_protobuf_INCLUDE_DIR}
CACHE PATH "protobuf include directory." FORCE)
SET(PROTOBUF_LITE_LIBRARY ${extern_protobuf_LITE_LIBRARY}
CACHE FILEPATH "protobuf lite library." FORCE)
SET(PROTOBUF_LIBRARY ${extern_protobuf_LIBRARY}
CACHE FILEPATH "protobuf library." FORCE)
SET(PROTOBUF_PROTOC_LIBRARY ${extern_protobuf_PROTOC_LIBRARY}
CACHE FILEPATH "protoc library." FORCE)
IF(WITH_C_API)
INSTALL(DIRECTORY ${PROTOBUF_INCLUDE_DIR} DESTINATION third_party/protobuf)
IF(ANDROID)
INSTALL(FILES ${PROTOBUF_LIBRARY} DESTINATION third_party/protobuf/lib/${ANDROID_ABI})
ELSE()
INSTALL(FILES ${PROTOBUF_LIBRARY} DESTINATION third_party/protobuf/lib)
ENDIF()
ENDIF()
IF(CMAKE_CROSSCOMPILING)
PROMPT_PROTOBUF_LIB(protobuf_host extern_protobuf)
ELSE()
SET(PROTOBUF_PROTOC_EXECUTABLE ${extern_protobuf_PROTOC_EXECUTABLE}
CACHE FILEPATH "protobuf executable." FORCE)
PROMPT_PROTOBUF_LIB(extern_protobuf)
ENDIF()
ENDIF(NOT PROTOBUF_FOUND)
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
include(ExternalProject)
set(PYBIND_SOURCE_DIR ${THIRD_PARTY_PATH}/pybind)
include_directories(${PYBIND_SOURCE_DIR}/src/extern_pybind/include)
ExternalProject_Add(
extern_pybind
${EXTERNAL_PROJECT_LOG_ARGS}
GIT_REPOSITORY "https://github.com/pybind/pybind11.git"
GIT_TAG "v2.1.1"
PREFIX ${PYBIND_SOURCE_DIR}
UPDATE_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
if(${CMAKE_VERSION} VERSION_LESS "3.3.0")
set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/pybind_dummy.c)
file(WRITE ${dummyfile} "const char * dummy_pybind = \"${dummyfile}\";")
add_library(pybind STATIC ${dummyfile})
else()
add_library(pybind INTERFACE)
endif()
add_dependencies(pybind extern_pybind)
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
INCLUDE(python_module)
FIND_PACKAGE(PythonInterp 2.7)
FIND_PACKAGE(PythonLibs 2.7)
# Fixme: Maybe find a static library. Get SHARED/STATIC by FIND_PACKAGE.
ADD_LIBRARY(python SHARED IMPORTED GLOBAL)
SET_PROPERTY(TARGET python PROPERTY IMPORTED_LOCATION ${PYTHON_LIBRARIES})
SET(py_env "")
IF(PYTHONINTERP_FOUND)
find_python_module(pip REQUIRED)
find_python_module(numpy REQUIRED)
find_python_module(wheel REQUIRED)
ENDIF(PYTHONINTERP_FOUND)
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR})
INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR})
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
INCLUDE(ExternalProject)
SET(ZLIB_SOURCES_DIR ${THIRD_PARTY_PATH}/zlib)
SET(ZLIB_INSTALL_DIR ${THIRD_PARTY_PATH}/install/zlib)
SET(ZLIB_ROOT ${ZLIB_INSTALL_DIR} CACHE FILEPATH "zlib root directory." FORCE)
SET(ZLIB_INCLUDE_DIR "${ZLIB_INSTALL_DIR}/include" CACHE PATH "zlib include directory." FORCE)
IF(WIN32)
SET(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib" CACHE FILEPATH "zlib library." FORCE)
ELSE(WIN32)
SET(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/libz.a" CACHE FILEPATH "zlib library." FORCE)
ENDIF(WIN32)
INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR})
ExternalProject_Add(
zlib
${EXTERNAL_PROJECT_LOG_ARGS}
GIT_REPOSITORY "https://github.com/madler/zlib.git"
GIT_TAG "v1.2.8"
PREFIX ${ZLIB_SOURCES_DIR}
UPDATE_COMMAND ""
CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR}
-DBUILD_SHARED_LIBS=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DCMAKE_MACOSX_RPATH=ON
-DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE}
${EXTERNAL_OPTIONAL_ARGS}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${ZLIB_INSTALL_DIR}
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
-DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE}
)
LIST(APPEND external_project_dependencies zlib)
IF(WITH_C_API)
INSTALL(DIRECTORY ${ZLIB_INCLUDE_DIR} DESTINATION third_party/zlib)
IF(ANDROID)
INSTALL(FILES ${ZLIB_LIBRARIES} DESTINATION third_party/zlib/lib/${ANDROID_ABI})
ELSE()
INSTALL(FILES ${ZLIB_LIBRARIES} DESTINATION third_party/zlib/lib)
ENDIF()
ENDIF()
# Find if a Python module is installed
# Found at http://www.cmake.org/pipermail/cmake/2011-January/041666.html
# To use do: find_python_module(PyQt4 REQUIRED)
function(find_python_module module)
string(TOUPPER ${module} module_upper)
if(NOT PY_${module_upper})
if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED")
set(${module}_FIND_REQUIRED TRUE)
else()
set(${module}_FIND_REQUIRED FALSE)
endif()
# A module's location is usually a directory, but for binary modules
# it's a .so file.
execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c"
"import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))"
RESULT_VARIABLE _${module}_status
OUTPUT_VARIABLE _${module}_location
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT _${module}_status)
set(PY_${module_upper} ${_${module}_location} CACHE STRING
"Location of Python module ${module}")
endif(NOT _${module}_status)
endif(NOT PY_${module_upper})
find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper})
if(NOT PY_${module_upper}_FOUND AND ${module}_FIND_REQUIRED)
message(FATAL_ERROR "python module ${module} is not found")
endif()
execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c"
"import sys, ${module}; sys.stdout.write(${module}.__version__)"
OUTPUT_VARIABLE _${module}_version
RESULT_VARIABLE _${module}_status
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT _${module}_status)
set(PY_${module_upper}_VERSION ${_${module}_version} CACHE STRING
"Version of Python module ${module}")
endif(NOT _${module}_status)
set(PY_${module_upper}_FOUND ${PY_${module_upper}_FOUND} PARENT_SCOPE)
set(PY_${module_upper}_VERSION ${PY_${module_upper}_VERSION} PARENT_SCOPE)
endfunction(find_python_module)
# 初步计划
支持类似TB中summary ops等可视化
- summary ops
- scalar
- histogram
- graph
- image
- audio
- text
# 参考文献
1. [tensorboard survey](https://github.com/VisualDL/VisualDL/wiki/tensorboard_survey)
# 后端架构设计
总体上划分为3层,由上至下分别为:
- Service Layer
- `Server` 提供服务
- `Frontend API` 后端前端的交互接口
- Logic Layer
- SDK, 负责为其他应用提供使用接口
- python SDK,方便基于 python 的应用的读取
- C SDK, 方便应用嵌入
- Information Maintainer, 负责周期性将数据预读到内存中,方便Server直接读取
- Storage Layer
- 负责Log 的结构化存储
- Protobuf API, 底层的存储结构
具体架构图如下
<p align="center">
<img src="./images/visualDL-backend-architecture.png"/>
</p>
以下展开一些基本的想法
## Service Layer
### server
- 基于简便的server框架实现,提供两种服务
- 作为main程序,支持一个前端网页
- 提供一系列的HTTP接口,以JSON格式支持与前端的交互
### Frontend API
- `Frontend API` 以单独模块的方式封装了与前端交互的接口逻辑,server依赖 `Frontend API` 与前端交互
## Logic Layer
### Information Maintainer (IM)
IM提供具体的数据处理以及计算,比如数据抽样,方差计算(histogram)等,
为了支持类似 embedding 等比较计算复杂的可视化,中间的计算结果以异步的方式更新。
IM 与 Server 的关系是
- IM 异步更新共享内存,Server 直接读取共享内存
- IM需要提供接口方便Server读取数据
### SDK
- Python SDK 用来支持任何Python界面的应用。
- 比如 Paddle, Tensorflow 等平台能够很方便地使用 Python SDK 初步支持可视化
- C SDK 用来方便嵌入到 C/C++ 的应用中
- 类似Paddle的平台可以通过嵌入 C 的方式实现原生可视化
## Storage Layer
### Protobuf API
- 决定了底层的数据格式,protobuf 提供了序列化的公共界面
- SDK通过Protobuf API操作底层数据
# 数据格式抽象 # 数据格式抽象
初版需要支持如下几种基础组件:
- 基本组件
- scalar
- histogram
- image
- audio
- text
- 高级组件
- graph
下面将对这些组件的数据类型进行抽象。
# 宏观信息
后端的日志会在每个周期追加到 `logdir` 中相应的log文件中,在追加信息的过程中,
除了graph,其他所有组件均需要时间步等信息,依次下面列出这个过程必须的一些元信息:
- `step_id` 追加的时间步
- `UNIX time` 方便追踪速度
- `tag` 类似TF,每个参与可视化的信息来源必须有一个唯一的命名(可以系统自动生成)
- tag会作为特定数据流从前端到后端统一的标识
# 各组件数据抽象
这里会抽象各组件数据必须存储的信息,为了便于沟通,使用了类似 `JSON` 的格式,
实际使用中,前后端可以自由选择数据格式,以及数据压缩。
## 抽样
每个 `tag` 会对应一个log文件,其中会包含全量信息(可以优化),但前端可视化并不需要全量信息,
这里后端就可以用 `蓄水池抽样` 来对全量数据进行抽样,组织好数据push给前端。
## 增量更新数据
考虑到前端可视化的动态更新,当前后端的全量数据应该只在第一次访问时才需要push给前端,
中间状态,后端前端交互的都只需要传递增量数据,具体增量数据格式类似
```javascript
[
[
"step_id": 200,
"data": <data_type>
]
]
```
## 统一的数据表示
上述除了group之外的所有组件的数据,都可以用下述数据格式表示
```
[
// data of each time-step
[
[
"step_id": 12,
"unix_time": 1234,
"data": [0.1, 0.2, ...]
],
// ... other steps
]
// meta info
[
// one of image, text, audio, scalar or histogram
"type": "image",
"size": [320, 270],
"tag": "some-name"
]
]
```
下面具体到单个组件,详细验证上述结构适用于每个组件
## scalar
```javascript
[
// step_id is hidden in this array's offsets
[
"unix_time" : 23323,
"data": 23
],
[
"unix_time" : 23325,
"data": 13
]
]
```
当然,上面的数据格式在最终存储时,可以折叠为
```
[
[23323, 23],
[23325, 13]
]
```
这里只关注必要的信息,具体的优化暂不考虑,后续不再赘述。
## histogram
histogram 需要得到tensor的分布,因此会把完整的tensor 的数值存下,具体的分布可以后端实时(增量)计算
后端存储的数据格式
```javascript
[
[
"step_id": 12,
"data": [0.12, 0.37, ...]
],
[
"step_id": 13,
"data": [0.13, 0.38, ...]
]
]
```
后端会计算好每个时间步的数据分布push给前端,具体数据格式如下
```javascript
[
[
"step_id": 12,
// n-dim distribution
"data": [0.12, 0.23, ...]
]
]
```
## image
考虑到图像为 `RGB` 格式,会对应至多3个矩阵,每个图片存储的数据格式如下
```javascript
[
"step_id": 12,
"type": "RGB",
"size": [345, 120],
"data": [0.1, 0.2, ...]
]
```
## audio
采样会是出的音频数据会是一个数组,因此类似如下格式
```javascript
[
"step_id": 12,
"type": "audio",
"size": [234],
"data": [...]
]
```
## text
后端直接生成 text便可
```javascript
[
"step_id": 12,
"data": "xx" // some text here
]
```
## graph
graph 会复杂一些,可能无法借用上述类型
```javascript
[
"nodes": [
[
[
"name": "node1",
"group": "scope1"
],
[
"name": "node2",
"group": "scope2"
]
]
],
// one edge
[
"from" : 0,
"to": 1,
"type": "normal"
],
[
"from" : 0,
"to": 1,
"type": "data"
]
]
```
# 可视化哪些信息(参数)
# 可视化形式
# 交互设计
# 前端增量
# 前端采样
{
"presets": [
["es2015", {"modules": false}],
"stage-0"
],
"plugins": ["transform-class-properties"]
}
node_modules/**/*
dep/**/*
test/**/*
mock/**/*
example/**/*
output/**/*
{
"files": [
"./src/**/*.san",
"./src/**/*.js",
"./template/**/*.html"
],
"eslint": {
"rules": {
"fecs-esnext-ext": [
"2",
[
"js",
"san"
]
],
"fecs-valid-jsdoc": [
"0"
]
}
},
"csshint": {},
"htmlcs": {},
"jformatter": {},
"esformatter": {},
"csscomb": {}
}
/node_modules
/.vscode
package-lock.json
# API
## runs
current runs
url: /data/runs
response:
```
["train", "test"]
```
## logdir
url: /data/logdir
response:
```
{
"logdir": "./tmp/tensorflow/mnist/logs/mnist_with_summaries/"
}
```
## data/plugins_listing
url: data/plugins_listing
\ No newline at end of file
此差异已折叠。
/**
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: {
"test": {
"layer2/biases/summaries/mean": {
"displayName": "layer2/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/min": {
"displayName": "layer1/weights/summaries/min",
"description": ""
},
"layer2/biases/summaries/stddev_1": {
"displayName": "layer2/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/mean": {
"displayName": "layer1/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/mean": {
"displayName": "layer1/weights/summaries/mean",
"description": ""
},
"dropout/dropout_keep_probability": {
"displayName": "dropout/dropout_keep_probability",
"description": ""
},
"layer1/weights/summaries/max": {
"displayName": "layer1/weights/summaries/max",
"description": ""
}, "layer2/weights/summaries/mean": {
"displayName": "layer2/weights/summaries/mean",
"description": ""
}, "layer2/weights/summaries/stddev_1": {
"displayName": "layer2/weights/summaries/stddev_1",
"description": ""
},
"layer2/weights/summaries/min": {
"displayName": "layer2/weights/summaries/min",
"description": ""
},
"layer1/biases/summaries/max": {
"displayName": "layer1/biases/summaries/max",
"description": ""
},
"layer2/weights/summaries/max": {
"displayName": "layer2/weights/summaries/max",
"description": ""
},
"accuracy_1": {
"displayName": "accuracy_1",
"description": ""
},
"layer2/biases/summaries/min": {
"displayName": "layer2/biases/summaries/min",
"description": ""
},
"cross_entropy_1": {
"displayName": "cross_entropy_1",
"description": ""
},
"layer2/biases/summaries/max": {
"displayName": "layer2/biases/summaries/max",
"description": ""
},
"layer1/weights/summaries/stddev_1": {
"displayName": "layer1/weights/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/stddev_1": {
"displayName": "layer1/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/min": {
"displayName": "layer1/biases/summaries/min",
"description": ""
}
},
"train": {
"layer2/biases/summaries/mean": {
"displayName": "layer2/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/min": {
"displayName": "layer1/weights/summaries/min",
"description": ""
},
"layer2/biases/summaries/stddev_1": {
"displayName": "layer2/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/mean": {
"displayName": "layer1/biases/summaries/mean",
"description": ""
},
"layer1/weights/summaries/mean": {
"displayName": "layer1/weights/summaries/mean",
"description": ""
},
"dropout/dropout_keep_probability": {
"displayName": "dropout/dropout_keep_probability",
"description": ""
},
"layer1/weights/summaries/max": {
"displayName": "layer1/weights/summaries/max",
"description": ""
},
"layer2/weights/summaries/mean": {
"displayName": "layer2/weights/summaries/mean",
"description": ""
},
"layer2/weights/summaries/stddev_1": {
"displayName": "layer2/weights/summaries/stddev_1",
"description": ""
},
"layer2/weights/summaries/min": {
"displayName": "layer2/weights/summaries/min",
"description": ""
},
"layer1/biases/summaries/max": {
"displayName": "layer1/biases/summaries/max",
"description": ""
},
"layer2/weights/summaries/max": {
"displayName": "layer2/weights/summaries/max",
"description": ""
},
"accuracy_1": {
"displayName": "accuracy_1",
"description": ""
},
"layer2/biases/summaries/min": {
"displayName": "layer2/biases/summaries/min",
"description": ""
},
"cross_entropy_1": {
"displayName": "cross_entropy_1",
"description": ""
},
"layer2/biases/summaries/max": {
"displayName": "layer2/biases/summaries/max",
"description": ""
},
"layer1/weights/summaries/stddev_1": {
"displayName": "layer1/weights/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/stddev_1": {
"displayName": "layer1/biases/summaries/stddev_1",
"description": ""
},
"layer1/biases/summaries/min": {
"displayName": "layer1/biases/summaries/min",
"description": ""
}
}
}
}
};
};
/**
* get mock data
*
* @param {string} path request path
* @param {Object} queryParam query params
* @param {Object} postParam post params
* @return {Object}
*/
module.exports = function (path, queryParam, postParam) {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: ["train", "test", "model"]
}
};
};
{
"name": "front",
"version": "1.0.1",
"description": "VisualDL frontend",
"author": "ecomfe",
"private": true,
"scripts": {
"release": "cross-env NODE_ENV=production node ./tool/build.js",
"build": "cross-env NODE_ENV=dev node ./tool/build.js",
"dev": "cross-env NODE_ENV=dev node tool/dev-server.js",
"lint": "./node_modules/fecs/bin/fecs --rule",
"precommit": "npm run lint",
"prepush": "npm run lint"
},
"engines": {
"node": ">= 6.4.0"
},
"dependencies": {
"axios": "^0.16.1",
"echarts": "^3.8.5",
"file-saver": "^1.3.3",
"lodash": "^4.17.4",
"normalize.css": "^6.0.0",
"qs": "^6.5.1",
"san": "3.2.3",
"san-mui": "^1.0.4",
"san-router": "^1.1.1",
"san-store": "^1.0.1",
"san-update": "^2.1.0",
"xlsx": "^0.11.3"
},
"devDependencies": {
"autoprefixer": "^6.7.7",
"autoresponse": "^0.2.0",
"babel-core": "^6.25.0",
"babel-loader": "^6.2.7",
"babel-plugin-transform-class-properties": "^6.19.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-stage-0": "^6.16.0",
"babel-register": "^6.0.0",
"babel-runtime": "^6.26.0",
"case-sensitive-paths-webpack-plugin": "^2.1.1",
"chalk": "^1.1.3",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^4.0.0",
"css-loader": "^0.28.0",
"express": "^4.16.2",
"extract-text-webpack-plugin": "^2.1.0",
"fecs": "^1.5.3",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.6.1",
"glob": "^7.1.1",
"html-loader": "^0.4.4",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.4",
"husky": "^0.14.3",
"json-loader": "^0.5.4",
"opn": "^5.1.0",
"optimize-css-assets-webpack-plugin": "^1.3.2",
"ora": "^1.1.0",
"postcss-loader": "^1.3.3",
"raw-loader": "^0.5.1",
"rimraf": "^2.6.2",
"san-loader": "^0.0.4",
"style-loader": "^0.16.1",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1",
"url-loader": "^0.5.8",
"webpack": "^2.4.1",
"webpack-dev-middleware": "^1.12.1",
"webpack-dev-server": "^2.4.2",
"webpack-hot-middleware": "^2.19.1",
"webpack-merge": "^4.1.0",
"yargs": "^8.0.2"
}
}
<template>
<div id="app">
<ui-app-menu
on-item-click="menuChange($event)"
></ui-app-menu>
<div id="content-container" class="visual-dl-content-container">
<div id="app-content" class="visual-dl-app-content">
<div id="content"></div>
</div>
</div>
</div>
</template>
<script>
import AppMenu from './common/ui/AppMenu';
import {router} from 'san-router';
import {routeTo} from './common/util/routeTo';
export default {
components: {
'ui-app-menu': AppMenu
},
initData() {
return {};
},
attached() {
router.start();
},
menuChange({value, url, title}) {
routeTo(url);
}
};
</script>
<style lang="stylus">
// @import './style/variables';
// +prefix-classes(prefix)
</style>
<template>
<div class="visual-dl-app-menu">
<san-appbar title="VisualDL">
<san-menu slot="right">
<san-menu-item
san-for="item in items"
class="{{selected === item.value ? 'sm-menu-item-selected' : ''}}"
on-click="handleItemClick(item)"
title="{{item.title}}" />
</san-menu>
</san-appbar>
</div>
</template>
<script>
import Appbar from 'san-mui/AppBar';
import {MenuItem, Menu} from 'san-mui/Menu';
export default {
components: {
'san-appbar': Appbar,
'san-menu': Menu,
'san-menu-item': MenuItem
},
initData() {
return {
selected: '1',
items: [
{
value: '1',
url: '/scalars',
title: 'SCALARS'
},
{
value: '2',
url: '/image',
title: 'IMAGES'
}
]
};
},
handleItemClick(item) {
this.data.set('selected', item.value);
this.fire('item-click', item);
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes(prefix)
.app-menu
width 100%
.visual-dl-app-menu
.sm-appbar-title
flex none
.sm-appbar-right
width 100%
.sm-menu
width 100%
height 100%
display flex
flex-direction row
.sm-menu-item-selected
background #e4e4e4
</style>
<template>
<div class="visual-dl-charts">
<div class="visual-dl-chart-box" style="{{computedStyle}}">
</div>
<div class="visual-dl-chart-actions">
<ui-dropdown-menu
hintText="download type"
items="{{runsItems}}"
value="{=downloadType=}"
/>
<san-button on-click="handleDownLoad">
<san-icon>file_download</san-icon>
</san-button>
</div>
</div>
</template>
<script>
import Button from 'san-mui/Button';
import Icon from 'san-mui/Icon';
import DropDownMenu from '../../../common/ui/DropDownMenu';
import {generateJsonAndDownload} from '../../../common/util/downLoadFile';
import echarts from 'echarts';
import axios from 'axios';
import {getPluginScalarsScalars} from '../../../service';
export default {
components: {
'ui-dropdown-menu': DropDownMenu,
'san-button': Button,
'san-icon': Icon
},
computed: {
computedStyle() {
let width = this.data.get('width');
let height = this.data.get('height');
return 'height:' + height + 'px;'
+ 'width:' + width + 'px;';
}
},
initData() {
return {
width: 400,
height: 300,
// line config
options: {},
data: [
{
name: 'train',
value: []
}
],
// choose run type for download file
downloadType: ''
};
},
inited() {
this.watch('runsItems', val => {
this.initDownloadType();
});
},
attached() {
let tagInfo = this.data.get('tagInfo');
this.initCharts(tagInfo);
},
initDownloadType() {
let runsItems = this.data.get('runsItems') || [];
if (runsItems.length === 0) {
return;
}
this.data.set('downloadType', runsItems.find((item, index) => index === 0).value);
},
initCharts(tagInfo) {
this.createChart();
this.setChartsOptions(tagInfo);
this.setChartsData(tagInfo);
},
createChart() {
let el = this.el.getElementsByClassName('visual-dl-chart-box')[0];
this.myChart = echarts.init(el);
},
setChartsOptions({tagList, tag}) {
let seriesOption = tagList.map(item => {
return {
name: item.run,
type: 'line',
showSymbol: false,
hoverAnimation: false,
data: [],
smooth: true
};
});
let legendOptions = tagList.map(item => item.run);
let option = {
title: {
text: tag,
textStyle: {
fontSize: '12'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
animation: false
},
position: [10, 300]
},
toolbox: {
show: true,
showTitle: true,
feature: {
dataZoom: {},
restore: {},
saveAsImage: {}
},
left: 40,
top: 270
},
legend: {
data: legendOptions,
top: 30
},
grid: {
top: 70,
bottom: 50
},
xAxis: [
{
type: 'value',
boundaryGap: false
},
{
type: 'value',
boundaryGap: false
}
],
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
min(value) {
return value.min;
},
max(value) {
return value.max;
},
axisLabel: {
formatter(value, index) {
// keep 0.11111 to 0.111
return value.toString().slice(0, 5);
}
}
},
series: seriesOption
};
this.myChart.setOption(option);
},
setChartsData({tagList, tag}) {
let requestList = tagList.map(item => {
let params = {
run: item.run,
tag: tag
};
return getPluginScalarsScalars(params);
});
axios.all(requestList).then(resArray => {
let seriesData = resArray.map(res => {
return {
data: res.data,
encode: {
// map 1 to xAixs。
x: [1],
// map 2 to yAixs。
y: [2]
}
};
});
this.myChart.setOption({
series: seriesData
});
});
},
getChartOptions() {
return this.myChart.getOption() || {};
},
handleDownLoad() {
let options = this.getChartOptions();
let series = options.series || [];
let downloadType = this.data.get('downloadType');
let sery = series.find(item => item.name === downloadType) || {};
let tagInfo = this.data.get('tagInfo');
let fileName = tagInfo.tag.replace(/\//g, '-');
generateJsonAndDownload(sery.data, fileName);
}
};
</script>
<style lang="stylus">
.visual-dl-charts
float left
.visual-dl-chart-actions
.sm-form-item
width 300px
display inline-block
</style>
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<div class="group-box">
<san-checkbox
san-if="showAll"
class="checkbox-all"
label="全选"
value="all"
on-change="handleAllChange($event)"
checked="{{All}}"
></san-checkbox>
<div class="san-form-check-group">
<san-checkbox
san-for="item in items"
label="{{item.name}}"
value="{{item.value}}"
disabled="{{item.disabled}}"
checked="{{value}}"
on-change="handleChange($event, item)"
></san-checkbox>
</div>
</div>
</div>
</template>
<script>
import Checkbox from 'san-mui/Checkbox';
import {DataTypes} from 'san';
export default {
components: {
'san-checkbox': Checkbox
},
dataTypes: {
items: DataTypes.array,
value: DataTypes.array
},
initData() {
return {
allValue: [],
items: {},
value: [],
All: [],
showAll: false
};
},
computed: {
allValue() {
let items = this.data.get('items') || [];
return items.map(item => item.value);
}
},
attached() {
this.jugdeAll();
this.watch('value', val => {
this.dispatch('UI:form-item-change');
this.jugdeAll();
});
},
handleChange(e, item) {
let checked = e.target.checked;
let valueItem = item.value;
if (checked) {
this.addValue(valueItem);
}
else {
this.removeValue(valueItem);
}
this.dispatch('UI:form-item-change');
this.jugdeAll();
},
handleAllChange(e) {
let checked = e.target.checked;
let allValue = this.data.get('allValue') || [];
if (checked) {
this.data.set('value', allValue.slice());
this.fire('valueChange', allValue.slice());
}
else {
this.data.set('value', []);
this.fire('valueChange', []);
}
this.dispatch('UI:form-item-change');
},
addValue(itemValue) {
let value = this.data.get('value') || [];
if (!value.includes(itemValue)) {
value.push(itemValue);
this.data.set('value', value.slice());
this.fire('valueChange', value.slice());
}
},
removeValue(itemValue) {
let value = this.data.get('value') || [];
if (value.includes(itemValue)) {
let index = value.indexOf(itemValue);
value.splice(index, 1);
this.data.set('value', value.slice());
this.fire('valueChange', value.slice());
}
},
jugdeAll() {
let allValue = this.data.get('allValue') || [];
let value = this.data.get('value') || [];
let isAll = allValue.every(val => value.includes(val));
if (isAll) {
this.data.set('All', ['all']);
}
else {
this.data.set('All', []);
}
}
};
</script>
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<san-drop-down-menu
error="{{error}}"
disabled="{{disabled}}"
value="{=value=}"
maxHeight="{{200}}"
autoWidth="{{false}}">
<san-menu-item
s-for="item in items"
on-change="menuItemChange(item)"
value="{{item.value}}"
label="{{item.name}}" />
</sm-drop-down-menu>
</div>
</template>
<script>
import {DataTypes} from 'san';
import {MenuItem, DropDownMenu} from 'san-mui/Menu';
export default {
components: {
'san-menu-item': MenuItem,
'san-drop-down-menu': DropDownMenu
},
dataTypes: {
value: DataTypes.string,
items: DataTypes.array,
disabled: DataTypes.bool
},
menuItemChange(item) {
let value = item.value;
this.fire('valueChange', value);
this.dispatch('UI:form-item-change');
},
initData() {
return {
value: '',
items: []
};
}
};
</script>
<template>
<div class="visaul-dl-expand-panel">
<h3
class="visaul-dl-expand-head"
on-click="handleHeadClick()"
>
<span>{{title}}</span>
</h3>
<div
style="display:{{displayStyle}}"
class="visaul-dl-expand-panel-content"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
computed: {
displayStyle() {
let isShow = this.data.get('isShow');
return isShow ? 'block' : 'none';
}
},
initData() {
return {
isShow: false
};
},
handleHeadClick() {
this.toogleShow();
},
toogleShow() {
let isShow = this.data.get('isShow');
this.data.set('isShow', !isShow);
}
};
</script>
<style lang="stylus">
.visaul-dl-expand-panel
.visaul-dl-expand-head
border solid 1px #e4e4e4
line-height 50px
height 50px
padding 0 20px
cursor pointer
.visaul-dl-expand-panel-content
padding 0 20px
.visaul-dl-expand-panel-content:after
content: "";
clear: both;
display: block;
</style>
import NotificationItem from './NotificationItem';
let seed = 1;
// instances
const instances = [];
/**
* caculate top dist
*
* @param {number} topDist top dist
* @return {number} final top dist
*/
function calcTopDist(topDist = 16) {
for (let i = 0, len = instances.length; i < len; i++) {
topDist += (instances[i].vm.el.offsetHeight + 16);
}
return topDist;
}
/**
* Notification main func
*
* @param {Object} options options
* @return {NotificationItem} instance
*/
const notification = function (options = {}) {
options.top = calcTopDist(options.offset);
const {onClose, onClick} = options;
delete options.onClick;
delete options.onClose;
delete options.offset;
const instance = {
vm: new NotificationItem({
data: options
}),
id: `notification_${seed++}`
};
if (typeof onClick === 'function') {
instance.vm.on('itemClick', onClick);
}
instance.vm.on('close', () => {
notification.close(instance.id, onClose);
});
instance.vm.attach(document.body);
instances.push(instance);
return instance.vm;
};
/**
* close
*
* @param {string} id instance id
* @param {Function} onClose cusmtom func
*/
notification.close = function (id, onClose) {
let index;
let removedHeight;
let len = instances.length;
for (let i = 0; i < len; i++) {
if (id === instances[i].id) {
if (typeof onClose === 'function') {
onClose(instances[i]);
}
index = i;
removedHeight = instances[i].vm.el.offsetHeight;
// distroy instance
instances[i].vm.dispose();
instances[i] = null;
// reomve instance fron instances
instances.splice(i, 1);
break;
}
}
// change the left notification's height
if (len > 1) {
for (let i = index; i < len - 1; i++) {
instances[i].vm.el.style.top = `${parseInt(instances[i].vm.el.style.top, 10) - removedHeight - 16}px`;
}
}
};
// fout type func
['success', 'warning', 'info', 'error'].forEach(type => {
notification[type] = options => {
if (typeof options === 'string') {
options = {
message: options
};
}
options = options || {};
options.type = type;
return notification(options);
};
});
export default notification;
## Notification
### base usage
```san Notification
<template>
<div>
<san-button
variants="raised info"
on-click="handleClick01">
auto close
</san-button>
<san-button
variants="raised info"
on-click="handleClick02">
no auto close
</san-button>
</div>
</template>
<script>
import Notification from '../src/Notification';
import '../src/Notification/Notification.styl';
import Button from '../src/Button';
import '../src/Button/Button.styl';
export default {
components: {
'san-button': Button
},
// auto close
handleClick01() {
Notification({
message: 'welcome',
title: 'success'
})
},
// no auto close
handleClick02() {
Notification({
message: 'welcome',
title: 'success',
duration: 0
})
}
}
</script>
```
### with icon
```san Notification
<template>
<div>
<san-button
variants="raised info"
on-click="handleClick01">
success
</san-button>
<san-button
variants="raised info"
on-click="handleClick02">
warning
</san-button>
<san-button
variants="raised info"
on-click="handleClick03">
info
</san-button>
<san-button
variants="raised info"
on-click="handleClick04">
error
</san-button>
<san-button
variants="raised info"
on-click="handleClick05">
simplify
</san-button>
</div>
</template>
<script>
import Notification from '../src/Notification';
import '../src/Notification/Notification.styl';
import Button from '../src/Button';
import '../src/Button/Button.styl';
export default {
components: {
'san-button': Button
},
handleClick01() {
Notification({
message: 'welcome',
title: 'success',
type: 'success'
})
},
handleClick02() {
Notification({
message: 'warning',
title: 'warning',
type: 'warning'
})
},
handleClick03() {
Notification({
message: 'info',
title: 'info',
type: 'info'
})
},
handleClick04() {
Notification({
message: '不可以攻击己方水晶',
title: '错误',
type: 'error'
})
},
handleClick05() {
Notification.success('simplify')
}
}
</script>
```
### width offset
```san Notification
<template>
<div>
<san-button
variants="raised info"
on-click="handleClick01">
width offset
</san-button>
</div>
</template>
<script>
import Notification from '../src/Notification';
import '../src/Notification/Notification.styl';
import Button from '../src/Button';
import '../src/Button/Button.styl';
export default {
components: {
'san-button': Button
},
handleClick01() {
Notification({
message: 'offset 100px',
title: 'success',
offset:100
})
}
}
</script>
```
## API
### Props
| name | type | fefault | desc |
| --- | --- | --- | --- |
| title | String | | title |
| message | String | | content |
| customClass |String| | custom class |
| offset | Number | | offset to the top |
| onClick | Function | | on-click callback |
| onClose | Function | | on-close callback |
| duration | Number | 3000 | duration |
| type | String | | include success,error,warning,info, others not use |
.sm-notification
position: fixed
display: flex
z-index: 9999
right: 16px
top 16px
width: 330px
padding: 20px
box-sizing border-box
border-radius 2px
background-color #fff
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
transition: all .4s;
overflow: hidden
&.state-hidden
transform translateX(346px)
&.state-open
transform translateX(0)
&-group
color: #000
&-type
margin auto 0
margin-right 20px
&.type-success
color: #13ce66
&.type-info
color: #50bfff
&.type-warning
color: #f7ba2a
&.type-error
color: #ff4949
&-title
color: #1f2d3d
font-size 16px
&-content
color: #8d9baf
padding: 5px 0
&-close
position: absolute
cursor: pointer
right 20px
top: 20px
&-btn
transition all .3s
color #bfcbd9
&:hover
color: #777F8C
import {Component} from 'san';
import Icon from 'san-mui/Icon';
const typeMap = {
success: 'check_circle',
info: 'info',
warning: 'error',
error: 'cancel'
};
export default class NotificationItem extends Component {
static template = `
<div
class="sm-notification {{customClass}} {{closed ? 'state-hidden' : 'state-open'}}"
style="top: {{top}}px"
on-mouseenter="clearTimer"
on-mouseleave="startTimer"
on-click="handleClick">
<san-icon
san-if="type"
class="sm-notification-type type-{{type}}"
size="50">{{iconType}}</san-icon>
<div class="sm-notification-group" >
<p class="sm-notification-title">{{title}}</p>
<div class="sm-notification-content">{{message}}</div>
<div on-click="close($event)" class="sm-notification-close">
<san-icon size="20" class="sm-notification-close-btn">close</san-icon>
</div>
</div>
</div>
`;
initData() {
return {
title: '',
message: '',
duration: 3000,
closed: true
};
}
static components = {
'san-icon': Icon
};
static computed = {
iconType() {
return typeMap[this.data.get('type')];
}
};
handleClick() {
this.fire('itemClick');
}
/**
* close
*
* @param {Object} e event Object
*/
close(e) {
if (e) {
e.stopPropagation();
}
this.data.set('closed', true);
this.el.addEventListener('transitionend', () => {
this.fire('close');
});
}
/**
* clear timer
*/
clearTimer() {
clearTimeout(this.timer);
}
/**
* start timet
*/
startTimer() {
const duration = this.data.get('duration');
if (duration > 0) {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, duration);
}
}
attached() {
if (this.el.parentNode !== document.body) {
document.body.appendChild(this.el);
}
requestAnimationFrame(() => {
this.data.set('closed', false);
});
this.startTimer();
}
detached() {
this.clearTimer();
}
}
import Notification from './Notification';
import NotificationItem from './NotificationItem';
export {
NotificationItem,
Notification as default
};
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<div class="group-box">
<san-radio
on-change="handleChange($event)"
san-for="item in items"
label="{{item.name}}"
value="{{item.value}}"
disabled="{{item.disabled}}"
checked="{=value=}"
></san-radio>
</div>
</div>
</template>
<script>
import Radio from 'san-mui/Radio';
import {DataTypes} from 'san';
export default {
components: {
'san-radio': Radio
},
dataTypes: {
items: DataTypes.array,
value: DataTypes.string
},
initData() {
return {
items: {},
value: [],
label: ''
};
},
attached(value) {
this.watch('value', val => {
this.dispatch('UI:form-item-change', val);
});
},
handleChange(val) {
this.fire('valueChange', val);
}
};
</script>
\ No newline at end of file
<template>
<div class="sm-form-item">
<label class="label">{{label}}</label>
<div class="input-box">
<san-slider
on-change="handleSlideChange($event)"
value="{{value}}"
min="{{min}}"
max="{{max}}"
step="{{step}}"
/>
<san-input-number
min="{{min}}"
max="{{max}}"
step="{{step}}"
value="{=value=}"
on-change="handlerChange($event)"
size="small">
</san-input-number>
</div>
</div>
</template>
<script>
import Slider from 'san-mui/Slider';
import InputNumber from 'san-mui/InputNumber';
export default {
components: {
'san-slider': Slider,
'san-input-number': InputNumber
},
initData() {
return {
value: '0',
label: '',
max: 0,
min: 0,
step: 0
};
},
handleSlideChange(val) {
this.data.set('value', val.toString());
}
};
</script>
<style lang="stylus">
.sm-form-item
.input-box
margin-top 10px
.sm-slider
display inline-block
margin-right 20px
margin-top 4px
float left
width 100px
.sm-slider-thumb
transform translate(0, -50%)
.sm-inputNumber
width 130px
display inline-block
</style>
// initual style
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; } body, button, input, select, textarea { font:12px/1.5tahoma, arial, \5b8b\4f53; } h1, h2, h3, h4, h5, h6{ font-size:100%; } address, cite, dfn, em, var { font-style:normal; } code, kbd, pre, samp { font-family:couriernew, courier, monospace; } small{ font-size:12px; } ul, ol { list-style:none; } a { text-decoration:none; } a:hover { text-decoration:underline; } sup { vertical-align:text-top; } sub{ vertical-align:text-bottom; } legend { color:#000; } fieldset, img { border:0; } button, input, select, textarea { font-size:100%; } table { border-collapse:collapse; border-spacing:0; }
@import './Notification/Notification.styl'
// modify some styles of san-mui
.sm-icon
width 100%
height 100%
overflow hidden
.sm-radio,
.sm-checkbox
margin-top 2px
margin-bottom 4px
margin-right 10px
float left
width 100%
.sm-radio-wrapper,
.sm-checkbox-wrapper
justify-content start
.sm-radio-label,
.sm-checkbox-label
font-size 12px
.sm-radio-icon
.sm-checkbox-icon
width 20px
height 20px
margin-right 10px
.sm-radio-ripple-wrapper
.sm-checkbox-ripple-wrapper
width 20px
height 20px
top 0px
left 0px
.sm-checkbox-svg-icon,
.sm-radio-svg-icon
width 20px
height 20px
.sm-checkbox-icon-uncheck
.sm-radio-icon-uncheck
color #58666e
.sm-radio-label,
.sm-checkbox-label
color #58666e
.sm-dropdown-menu
.sm-dropdown-menu-icon
.sm-icon
color #58666e
.sm-popover-content
.sm-menu-item
line-height 30px
font-size 14px
color #58666e
.sm-menu-item.state-selected
color #ff4081
.sm-text-field-input
color #58666e
.sm-form-item
margin-top 10px
.label
color rgba(0,0,0,0.54)
font-size 12px
line-height 20px
.group-box
overflow hidden
.collapse-transition
transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
.sm-pagination
margin 30px 0
font-size 12px
\ No newline at end of file
import XLSX from 'xlsx';
import FileSaver from 'file-saver';
const aoaToSheet = XLSX.utils.aoa_to_sheet;
const saveAs = FileSaver.saveAs;
function s2ab(s) {
if (typeof ArrayBuffer !== 'undefined') {
let buf = new ArrayBuffer(s.length);
let view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) {
view[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
}
let buf = new Array(s.length);
for (let i = 0; i !== s.length; ++i) {
buf[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
}
/**
* download Excel
*
* @desc transform data like [['A', 'B', 'C'], ['1', '2', '3'],[['1-1', '2-1', '3-1']]] to xlsx and download
* @param {Array} data the data for the xlsx
* @param {string} name filename
*/
export const generateXLSXandAutoDownload = function (data, name) {
let wopts = {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
};
let ws = aoaToSheet(data);
let wb = {
SheetNames: ['Export'],
Sheets: {},
Props: {}
};
wb.Sheets.Export = ws;
let wbout = XLSX.write(wb, wopts);
saveAs(
new Blob(
[s2ab(wbout)],
{
type: 'application/octet-stream'
}
),
name + '.xlsx' || 'sheetjs.xlsx'
);
};
/**
* download json
*
* @desc download json
* @param {Array} data the data for the xlsx
* @param {string} name filename
*/
export const generateJsonAndDownload = function (data, name) {
saveAs(
new Blob(
[s2ab(JSON.stringify(data, null, ' '))],
{
type: 'application/octet-stream'
}
),
name + '.json' || 'json.json'
);
};
import axios from 'axios';
import qs from 'qs';
import Notification from '../ui/Notification';
const STATUS = 'status';
const STATUSINFO = 'msg';
const instance = axios.create({
baseURL: '/',
timeout: 30000
});
const responseErrorStatus = response => {
const data = response.data;
if (data[STATUS] !== 0) {
Notification.error(data[STATUSINFO]);
return Promise.reject(data);
}
return data;
};
const responseNetError = error => {
Notification.error('net error');
return Promise.reject(error);
};
// post from
const formInstance = axios.create({
baseURL: '/',
timeout: 3000,
transformRequest: [data => qs.stringify(data)],
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json,application/vnd.ms-excel'
}
});
formInstance.interceptors.response.use(responseErrorStatus, responseNetError);
instance.interceptors.response.use(responseErrorStatus, responseNetError);
export const makeService = (url, opt = {method: 'get'}) => (params = {}) => {
if (opt.method === 'delete' || opt.method === 'get') {
params = {params};
}
return instance[opt.method](url, params);
};
export const makeFormService = (url, method = 'post') => (params = {}) => formInstance[method](url, params);
import {router} from 'san-router';
export function routeTo(url, params = {}) {
let paramsArr = Object.keys(params).map(key => `${key}=${params[key]}`);
let urlParams = (url.indexOf('?') > -1 ? '&' : '?') + paramsArr.join('&');
router.locator.redirect(urlParams.length > 1 ? `${url}${urlParams}` : url);
}
<template>
<article>
<h1>
{{text}}
</h1>
</article>
</template>
<script>
export default {
initData() {
return {
text: 'welcome'
};
}
};
</script>
import {router} from 'san-router';
import HomePage from './Home';
router.add({
target: '#content',
rule: '/',
Component: HomePage
});
import 'normalize.css/normalize.css';
import 'san-mui/index.css';
import './common/ui/ui-common.styl';
import './home/index';
import './scalars/index';
import App from './App';
new App({
data: {
titleName: 'VisualDL'
}
}).attach(document.getElementById('root'));
<template>
<div class="visual-dl-scalar-container">
<div class="visual-dl-scalar-left">
<div class="visual-dl-scalar-config-container">
<ui-config
runsItems="{{runsItems}}"
config="{=config=}"
></ui-config>
</div>
</div>
<div class="visual-dl-scalar-right">
<ui-chart-page
config="{{config}}"
runsItems="{{runsItems}}"
tagList="{{filteredTagsList}}"
title="Tags matching {{config.groupNameReg}}"
></ui-chart-page>
<ui-chart-page
san-for="item in groupedTags"
config="{{config}}"
runsItems="{{runsItems}}"
tagList="{{item.tags}}"
title="{{item.group}}"
></ui-chart-page>
</div>
</div>
</template>
<script>
import {getPluginScalarsTags, getRuns} from '../service';
import config from './ui/config';
import chartPage from './ui/chartPage';
import {debounce, flatten, uniq} from 'lodash';
export default {
components: {
'ui-config': config,
'ui-chart-page': chartPage
},
computed: {
runsItems() {
let runsArray = this.data.get('runsArray') || [];
return runsArray.map(item => {
return {
name: item,
value: item
};
});
},
tagsList() {
let tags = this.data.get('tags');
let runs = Object.keys(tags);
let tagsArray = runs.map(run => Object.keys(tags[run]));
let allUniqTags = uniq(flatten(tagsArray));
// get the data for every chart
return allUniqTags.map(tag => {
let tagList = runs.map(run => {
return {
run,
tag: tags[run][tag]
};
});
return {
tagList,
tag,
group: tag.split('/')[0]
};
});
},
groupedTags() {
let tagsList = this.data.get('tagsList') || [];
// put data in group
let groupData = {};
tagsList.forEach(item => {
let group = item.group;
if (groupData[group] === undefined) {
groupData[group] = [];
groupData[group].push(item);
}
else {
groupData[group].push(item);
}
});
// to array
let groups = Object.keys(groupData);
return groups.map(group => {
return {
group,
tags: groupData[group]
};
});
}
},
initData() {
return {
runsArray: [],
tags: [],
config: {
groupNameReg: '.*',
smoothing: '0.5',
horizontal: '1',
sortingMethod: '2',
link: [],
chart: []
}
};
},
inited() {
getPluginScalarsTags().then(({errno, data}) => {
this.data.set('tags', data);
// filter when inited
let groupNameReg = this.data.get('config.groupNameReg');
this.filterTagsList(groupNameReg);
});
getRuns().then(({errno, data}) => {
this.data.set('runsArray', data);
});
// need debounce, can't use computed
this.watch('config.groupNameReg', debounce(this.filterTagsList, 300));
},
filterTagsList(groupNameReg) {
let tagsList = this.data.get('tagsList') || [];
let regExp = new RegExp(groupNameReg);
let filtedTagsList = tagsList.filter(item => regExp.test(item.tag));
this.data.set('filteredTagsList', filtedTagsList);
}
};
</script>
<style lang="stylus">
@import '../style/variables';
+prefix-classes('visual-dl-scalar-')
.container
padding-left 300px
position relative
.left
width 280px
min-height 300px
border solid 1px #e4e4e4
position absolute
left 0
.right
width 100%
border solid 1px #e4e4e4
min-height 300px
padding 20px
</style>
import {router} from 'san-router';
import Scalar from './Scalars';
router.add({
target: '#content',
rule: '/scalars',
Component: Scalar
});
<template>
<div class="visaul-dl-chart-page">
<ui-expand-panel title="{{title}}">
<div class="visaul-dl-chart-box">
<ui-chart
san-for="tag in filteredTagList"
tagInfo="{{tag}}"
config="{{config}}"
runsItems="{{runsItems}}"
></ui-chart>
</div>
<ui-pagination
san-if="total > pageSize"
on-pageChange="handlePageChange($event)"
current="{{currentPage}}"
pageSize="{{pageSize}}"
total="{{total}}"
showSizeChanger="{{false}}"
/>
</ui-expand-panel>
</div>
</template>
<script>
import ExpandPanel from '../../common/ui/ExpandPanel';
import chart from '../../common/ui/Charts/chart';
import Pagination from 'san-mui/Pagination';
export default {
components: {
'ui-chart': chart,
'ui-expand-panel': ExpandPanel,
'ui-pagination': Pagination
},
computed: {
filteredTagList() {
let tagList = this.data.get('tagList') || [];
let currentPage = this.data.get('currentPage');
let pageSize = this.data.get('pageSize');
return tagList.slice((currentPage - 1) * pageSize, currentPage * pageSize);
},
total() {
let tagList = this.data.get('tagList') || [];
return tagList.length;
}
},
initData() {
return {
// current page
currentPage: 1,
// item per page
pageSize: 8
};
},
handlePageChange({pageNum}) {
this.data.set('currentPage', pageNum);
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-')
.visaul-dl-chart-page
.visaul-dl-chart-box:after
content: "";
clear: both;
display: block;
</style>
<template>
<div class="visual-dl-scalar-config-com">
<san-text-field
hintText="input a tag group name to search"
label="Group name RegExp"
inputValue="{=config.groupNameReg=}"
/>
<ui-slider
label="Smoothing"
value="{=config.smoothing=}"
min="{{0}}"
max="{{1}}"
step="{{0.001}}"
/>
<ui-radio-group
label="Horizontal"
value="{{config.horizontal}}"
items="{{horizontalItems}}"
/>
<ui-dropdown-menu
label="Tooltip sorting method"
items="{{sortingMethodItems}}"
value="{=config.sortingMethod=}"
/>
<ui-checkbox-group
value="{=config.link=}"
items="{{lnksItems}}"
/>
<ui-checkbox-group
value="{=config.chart=}"
items="{{chartItems}}"
/>
<ui-checkbox-group
label="Runs"
items="{{runsItems}}"
/>
<san-button class="visual-dl-scalar-run-toggle" variants="raised secondery">Toggle All Runs</san-button>
</div>
</template>
<script>
import TextField from 'san-mui/TextField';
import Slider from '../../common/ui/Slider';
import RadioGroup from '../../common/ui/RadioGroup';
import DropDownMenu from '../../common/ui/DropDownMenu';
import CheckBoxGroup from '../../common/ui/CheckBoxGroup';
import Button from 'san-mui/Button';
export default {
components: {
'san-text-field': TextField,
'ui-slider': Slider,
'ui-radio-group': RadioGroup,
'ui-dropdown-menu': DropDownMenu,
'ui-checkbox-group': CheckBoxGroup,
'san-button': Button
},
initData() {
return {
config: {
groupName: 'aa',
smoothing: '0.5',
horizontal: '1',
sortingMethod: '2',
link: [],
chart: []
},
horizontalItems: [
{
name: 'Step',
value: '1'
},
{
name: 'Relative',
value: '2'
},
{
name: 'Wall',
value: '3'
}
],
sortingMethodItems: [
{
name: 'default',
value: '1'
},
{
name: 'descending',
value: '2'
},
{
name: 'ascending',
value: '3'
},
{
name: 'nearest',
value: '4'
}
],
runsItems: [],
lnksItems: [
{
value: '1',
name: 'Show data download links'
}
],
chartItems: [
{
value: '1',
name: 'Ignore outliers in chart scaling'
}
]
};
}
};
</script>
<style lang="stylus">
@import '../../style/variables';
+prefix-classes('visual-dl-scalar-')
.config-com
width 90%
margin 0 auto
.run-toggle
width 100%
margin-top 20px
</style>
import {makeService} from './common/util/http';
export const getPluginScalarsTags = makeService('/data/plugin/scalars/tags');
export const getRuns = makeService('/data/runs');
export const getPluginScalarsScalars = makeService('/data/plugin/scalars/scalars');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>VisualDL</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
</head>
<body>
<div id="root"></div>
</body>
</html>
function noopReplace (val) { return val; }
function HtmlReplacePlugin(options) {
this.replacer = options.replacer || noopReplace;
}
HtmlReplacePlugin.prototype.apply = function(compiler) {
var replacer = this.replacer;
compiler.plugin('compilation', function(compilation) {
compilation.plugin('html-webpack-plugin-after-html-processing', function(htmlPluginData, callback) {
htmlPluginData.html = replacer(htmlPluginData.html, htmlPluginData);
callback(null, htmlPluginData);
});
});
};
module.exports = HtmlReplacePlugin;
const webpack = require('webpack');
const rm = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const HtmlReplacePlugin = require('./HtmlReplacePlugin');
// env 'production'
process.env.WEBPACK_ENV = 'production';
let webpackConfig = require('./webpack.prod.config');
let spinner = ora('building for production...');
spinner.start();
let feRoots = {
'index': './'
};
webpackConfig.plugins = webpackConfig.plugins.concat([
new HtmlReplacePlugin({
replacer: function(html, opt) {
var name = opt.outputName.replace(/\.html$/, '');
var feRoot = feRoots[name];
if (feRoot) {
html = html
.replace(/href="/g, 'href="' + feRoot)
.replace(/src="/g, 'src="' + feRoot);
}
return html;
}
})
]);
rm(webpackConfig.output.path, err => {
if (err) throw err;
webpack(webpackConfig, function(err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n');
console.log(chalk.cyan(' Build complete.\n'));
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
));
})
});
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
process.env.NODE_ENV = 'dev';
let devPort = 8999;
let opn = require('opn');
let express = require('express');
let webpack = require('webpack');
let proxyMiddleware = require('http-proxy-middleware');
let webpackConfig = require('./webpack.dev.config');
let autoresponse = require('autoresponse');
let path = require('path');
let port = devPort;
let autoOpenBrowser = false;
let app = express();
let compiler = webpack(webpackConfig);
let devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
disableHostCheck: true,
quiet: false,
noInfo: false,
stats: {
colors: true
},
headers: {'Access-Control-Allow-Origin': '*'}
});
let hotMiddleware = require('webpack-hot-middleware')(compiler, {
heartbeat: 2000
});
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({
action: 'reload'
});
cb();
});
});
// autoresponse
let AutoresponseMatchs = ['data'];
let matchsReg = new RegExp(AutoresponseMatchs.join('\|'));
let excludeReg = /\.(html|js|map)$/;
let isAutoresponseRequest = (path) => {
return !excludeReg.test(path) && matchsReg.test(path);
}
app.use(autoresponse({
logLevel: 'debug',
root: path.dirname(__dirname),
rules: [
{
match: isAutoresponseRequest,
method: ['get', 'post', , 'delete']
}
]
}));
// serve webpack bundle output
app.use(devMiddleware);
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware);
let uri = 'http://localhost:' + port;
let _resolve;
let readyPromise = new Promise(resolve => {
_resolve = resolve;
});
console.log('> Starting dev server...');
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n');
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri);
}
_resolve();
});
let server = app.listen(port);
module.exports = {
ready: readyPromise,
close() {
server.close();
}
};
const path = require('path');
const projectPath = path.resolve(__dirname, '..');
const HtmlWebpackPlugin = require('html-webpack-plugin');
/**
* default apps
*
* @type {Array}
*/
let defaultApps = [
{
name: 'index',
feRoot: '/dist'
}
];
/**
* get entry js file
*
* @param {Array} apps appname
* @return {string} file path
*/
function getModules(apps) {
let modules = {};
apps.forEach(function (item) {
let app = item.name;
modules[app] = path.join(projectPath, 'src/' + app + '.js');
});
return modules;
}
/**
* get HtmlWebpackPlugin
*
* @param {string} app appname
* @param {boolan} template use template
* @return {HtmlWebpackPlugin} HtmlWebpackPlugin
*/
function getTemplate(app, template) {
let templateUrl = 'template/index.html';
if (template) {
templateUrl = `ejs-render-loader!template/${template}.ejs`;
}
return new HtmlWebpackPlugin({
filename: app + '.html',
template: templateUrl
});
}
/**
* get entry config
*
* @param {string} app appname
* @param {boolan} template use template
* @return {Object} config
*/
function getEntry(app, template) {
let buildApps = defaultApps.filter(function (item) {
let name = item.name;
return name === app;
});
buildApps = buildApps.length > 0 ? buildApps : defaultApps;
return {
module: getModules(buildApps),
template: buildApps.map(item => getTemplate(item.name, template))
};
}
module.exports.get = getEntry;
module.exports.entry = defaultApps;
const webpack = require('webpack');
const path = require('path');
const projectPath = path.resolve(__dirname, '..');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const argv = require('yargs').argv;
const isDev = process.env.NODE_ENV === 'dev';
const entry = require('./entry');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
function getLoaders(isDev, ext) {
let arr = ['css-loader'];
if (ext) {
arr.push(ext + '-loader');
}
if (isDev) {
arr.unshift('style-loader');
return arr;
}
return ExtractTextPlugin.extract({
use: arr,
fallback: 'style-loader'
});
}
/**
* entry config
*
* @type {Object}
*/
const ENTR_CONFIG = entry.get(argv.app, argv.template);
/**
* webpack config
*
* @type {Object}
*/
const config = {
entry: ENTR_CONFIG.module,
output: {
path: path.resolve(projectPath, 'dist'),
filename: '[name].[hash].js'
},
resolve: {
alias: {
'san-mui': 'san-mui/lib',
axios: 'axios/dist/axios.min.js'
},
extensions: ['.js', '.json', '.styl', '.css', '.html', '.san']
},
module: {
noParse: [
/node_modules\/(san|axios)\//
],
rules: [
{
test: /\.san$/,
loader: 'san-loader',
options: {
loaders: {
stylus: getLoaders(isDev, 'stylus')
}
}
},
{
test: /\.js$/,
exclude: /node_modules/,
include: [
path.resolve(projectPath, 'src')
],
loader: 'babel-loader'
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.html/,
loader: 'html-loader',
options: {
minimize: false
}
},
{
test: /\.css$/,
use: getLoaders(isDev)
},
{
test: /\.styl$/,
use: getLoaders(isDev, 'stylus')
},
{
test: /\.(gif|png|jpe?g)$/i,
loader: 'file-loader',
options: {
name: 'images/[name].[hash].[ext]'
}
},
{
test: /\.woff2?$/,
loader: 'url-loader',
options: {
name: 'fonts/[name].[hash].[ext]',
limit: '10000',
mimetype: 'application/font-woff'
}
},
{
test: /\.(ttf|eot|svg)$/,
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash].[ext]'
}
}
]
},
plugins: [
new CaseSensitivePathsPlugin(),
new webpack.LoaderOptionsPlugin({
test: /\.(styl|san)$/
})
]
};
// template config
config.plugins = config.plugins.concat(ENTR_CONFIG.template);
module.exports = config;
const webpack = require('webpack');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
let merge = require('webpack-merge');
let baseWebpackConfig = require('./webpack.config');
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./tool/dev-client'].concat(baseWebpackConfig.entry[name]);
});
/**
* dev config
*
* @type {Object}
*/
module.exports = merge(baseWebpackConfig, {
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"dev"'
}
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new FriendlyErrorsPlugin()
]
});
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const path = require('path');
const autoprefixer = require('autoprefixer');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
const bizCss = new ExtractTextPlugin('biz.[chunkhash].css');
let merge = require('webpack-merge');
let baseWebpackConfig = require('./webpack.config');
const autoPrefixOptions = {
browsers: [
'iOS >= 7',
'Android >= 4.0',
'ExplorerMobile >= 10',
'ie >= 9'
]
};
/**
* pro config
*
* @type {Object}
*/
module.exports = merge(baseWebpackConfig, {
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': 'production'
}
}),
new webpack.LoaderOptionsPlugin({
test: /\.(styl|san)$/,
san: {
autoprefixer: autoPrefixOptions
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.[chunkhash].js',
minChunks: function (module, count) {
const resPath = module.resource;
return resPath && /\.js$/.test(resPath)
&& resPath.indexOf(
path.join(__dirname, '../node_modules')
) === 0;
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
'screw_ie8': true, // no ie6/7/8
'warnings': false
},
comments: false,
sourceMap: false
}),
bizCss,
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
})
]
});
from setuptools import setup
packages = ['visualdl', 'visualdl.frontend.dist', 'visualdl.mock']
setup(
name="visualdl",
version="0.0.1",
packages=packages,
package_data={'visualdl.frontend.dist': ['*', 'fonts/*']},
include_package_data=True,
install_requires=['flask>=0.12.1'],
url='http://www.baidu.com/',
license='Apache 2.0')
import log
__all__ = ['log']
import logging
logger = logging
logger.basicConfig(
format='[%(levelname)s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
logger.getLogger().setLevel(logging.INFO)
def sequence_data():
return [[1465980660.726887, 1, 0.07000000029802322],
[1465980660.767164, 2, 0.18000000715255737],
[1465980660.799817, 3, 0.3199999928474426],
[1465980660.831853, 4, 0.33000001311302185],
[1465980660.86555, 5, 0.3400000035762787],
[1465980660.898716, 6, 0.6000000238418579],
[1465980660.930804, 7, 0.6299999952316284],
[1465980660.963156, 8, 0.6299999952316284],
[1465980660.995727, 9, 0.6299999952316284],
[1465980661.603699, 11, 0.75],
[1465980661.641232, 12, 0.7900000214576721],
[1465980661.674311, 13, 0.7099999785423279],
[1465980661.704281, 14, 0.7099999785423279],
[1465980661.737259, 15, 0.7200000286102295],
[1465980661.768047, 16, 0.75],
[1465980661.801236, 17, 0.8600000143051147],
[1465980661.832749, 18, 0.7799999713897705],
[1465980661.862822, 19, 0.8399999737739563],
[1465980662.481258, 21, 0.699999988079071],
[1465980662.521448, 22, 0.6700000166893005],
[1465980662.557197, 23, 0.7699999809265137],
[1465980662.593898, 24, 0.7900000214576721],
[1465980662.629991, 25, 0.7200000286102295],
[1465980662.671198, 26, 0.8100000023841858],
[1465980662.711186, 27, 0.7599999904632568],
[1465980662.750267, 28, 0.7799999713897705],
[1465980662.791909, 29, 0.8299999833106995],
[1465980663.47027, 31, 0.8100000023841858],
[1465980663.538732, 32, 0.8500000238418579],
[1465980663.57077, 33, 0.8600000143051147],
[1465980663.60126, 34, 0.8199999928474426],
[1465980663.631059, 35, 0.7900000214576721],
[1465980663.665972, 36, 0.7799999713897705],
[1465980663.697275, 37, 0.9100000262260437],
[1465980663.726395, 38, 0.8700000047683716],
[1465980663.760169, 39, 0.9200000166893005],
[1465980664.45205, 41, 0.8299999833106995],
[1465980664.484207, 42, 0.7599999904632568],
[1465980664.515375, 43, 0.7699999809265137],
[1465980664.547608, 44, 0.8299999833106995],
[1465980664.580122, 45, 0.949999988079071],
[1465980664.611019, 46, 0.8999999761581421],
[1465980664.642956, 47, 0.8700000047683716],
[1465980664.674636, 48, 0.8500000238418579],
[1465980664.705622, 49, 0.8899999856948853],
[1465980665.379549, 51, 0.8399999737739563],
[1465980665.422869, 52, 0.8500000238418579],
[1465980665.466136, 53, 0.8199999928474426],
[1465980665.508361, 54, 0.9300000071525574],
[1465980665.544331, 55, 0.9399999976158142],
[1465980665.589887, 56, 0.8700000047683716],
[1465980665.633466, 57, 0.9300000071525574],
[1465980665.674978, 58, 0.7799999713897705],
[1465980665.716878, 59, 0.9300000071525574],
[1465980666.653456, 61, 0.8799999952316284],
[1465980666.697294, 62, 0.9300000071525574],
[1465980666.742066, 63, 0.8700000047683716],
[1465980666.780127, 64, 0.8299999833106995],
[1465980666.818287, 65, 0.9200000166893005],
[1465980666.855386, 66, 0.9399999976158142],
[1465980666.897352, 67, 0.9300000071525574],
[1465980666.931322, 68, 0.8899999856948853],
[1465980666.96562, 69, 0.8600000143051147],
[1465980667.619625, 71, 0.8700000047683716],
[1465980667.655166, 72, 0.9200000166893005], [
1465980667.687101, 73, 0.8199999928474426
], [1465980667.720176, 74, 0.8100000023841858],
[1465980667.751985, 75, 0.8500000238418579], [
1465980667.785244, 76, 0.8600000143051147
], [1465980667.820445, 77, 0.9200000166893005], [
1465980667.857163, 78, 0.8899999856948853
], [1465980667.891868, 79, 0.8999999761581421], [
1465980668.56409, 81, 0.8500000238418579
], [1465980668.599529, 82, 0.8299999833106995], [
1465980668.630751, 83, 0.8500000238418579
], [1465980668.665135, 84, 0.8199999928474426], [
1465980668.697928, 85, 0.8199999928474426
], [1465980668.730525, 86, 0.8799999952316284], [
1465980668.769772, 87, 0.9200000166893005
], [1465980668.803344, 88, 0.8299999833106995], [
1465980668.834414, 89, 0.800000011920929
], [1465980669.814826, 91, 0.8600000143051147], [
1465980669.851511, 92, 0.8899999856948853
], [1465980669.891407, 93, 0.8799999952316284], [
1465980669.927507, 94, 0.9399999976158142
], [1465980669.968384, 95, 0.9300000071525574], [
1465980670.007071, 96, 0.8500000238418579
], [1465980670.044314, 97, 0.8500000238418579], [
1465980670.083472, 98, 0.9100000262260437
], [1465980670.214597, 99, 0.8600000143051147], [
1465980670.934513, 101, 0.8799999952316284
], [1465980670.971317, 102, 0.8700000047683716], [
1465980671.003626, 103, 0.8600000143051147
], [1465980671.037037, 104, 0.8399999737739563], [
1465980671.070037, 105, 0.9200000166893005
], [1465980671.104992, 106, 0.8600000143051147], [
1465980671.137882, 107, 0.8100000023841858
], [1465980671.173917, 108, 0.7400000095367432], [
1465980671.205898, 109, 0.8799999952316284
], [1465980671.833723, 111, 0.9100000262260437]]
def data():
return {
"test": {
"min/layer2/weights": {
"displayName": "min/layer2/weights",
"description": ""
}
},
"train": {
"min/layer2/weights": {
"displayName": "min/layer2/weights",
"description": ""
}
}
}
""" entry point of visual_dl
"""
import json
import os
import sys
from optparse import OptionParser
from flask import Flask, redirect
from flask import request
from flask import send_from_directory
from flask import Response
from visualdl.log import logger
import visualdl.mock.data as mock_data
import visualdl.mock.tags as mock_tags
app = Flask(__name__, static_url_path="")
def option_parser():
"""
:return:
"""
parser = OptionParser(usage="usage: visual_dl visual_dl.py "\
"-p port [options]")
parser.add_option(
"-p",
"--port",
type=int,
default=8040,
action="store",
dest="port",
help="rest api service port")
parser.add_option(
"--logdir", action="store", dest="logdir", help="log file directory")
return parser.parse_args()
options, args = option_parser()
server_path = os.path.abspath(os.path.dirname(sys.argv[0]))
static_file_path = "./frontend/dist/"
mock_data_path = "./mock_data/"
# return data
# status, msg, data
def gen_result(status, msg, data):
"""
:param status:
:param msg:
:return:
"""
result = dict()
result['status'] = status
result['msg'] = msg
result['data'] = data
return result
@app.route("/")
def index():
return redirect('/static/index.html', code=302)
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory(
os.path.join(server_path, static_file_path), filename)
@app.route('/data/logdir')
def logdir():
result = gen_result(0, "", {"logdir": options.logdir})
return Response(json.dumps(result), mimetype='application/json')
@app.route('/data/runs')
def runs():
is_debug = bool(request.args.get('debug'))
result = gen_result(0, "", ["train", "test"])
return Response(json.dumps(result), mimetype='application/json')
@app.route("/data/plugin/scalars/tags")
def tags():
is_debug = bool(request.args.get('debug'))
result = gen_result(0, "", mock_tags.data())
return Response(json.dumps(result), mimetype='application/json')
@app.route('/data/plugin/scalars/scalars')
def scalars():
run = request.args.get('run')
tag = request.args.get('tag')
is_debug = bool(request.args.get('debug'))
result = gen_result(0, "", mock_data.sequence_data())
return Response(json.dumps(result), mimetype='application/json')
if __name__ == '__main__':
logger.info(" port=" + str(options.port))
app.run(debug=False, host="0.0.0.0", port=options.port)
#!/bin/bash
set -ex
sudo pip install numpy
#sudo apt-get install --only-upgrade cmake -y
mkdir -p build
cd build
cmake ..
make
make test
#if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./travis/run_on_pull_requests; fi
#if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./travis/run_on_non_pull_requests; fi
# This file is used by clang-format to autoformat paddle source code
#
# The clang-format is part of llvm toolchain.
# It need to install llvm and clang to format source code style.
#
# The basic usage is,
# clang-format -i -style=file PATH/TO/SOURCE/CODE
#
# The -style=file implicit use ".clang-format" file located in one of
# parent directory.
# The -i means inplace change.
#
# The document of clang-format is
# http://clang.llvm.org/docs/ClangFormat.html
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
---
Language: Cpp
BasedOnStyle: Google
IndentWidth: 2
TabWidth: 2
ContinuationIndentWidth: 4
AccessModifierOffset: -2 # The private/protected/public has no indent in class
Standard: Cpp11
AllowAllParametersOfDeclarationOnNextLine: true
BinPackParameters: false
BinPackArguments: false
...
add_library(sdk ${PROJECT_SOURCE_DIR}/visualdl/logic/sdk.cc)
add_library(im ${PROJECT_SOURCE_DIR}/visualdl/logic/im.cc)
add_dependencies(im storage_proto)
add_dependencies(sdk storage_proto)
## pybind
add_library(core SHARED ${PROJECT_SOURCE_DIR}/visualdl/logic/pybind.cc)
add_dependencies(core pybind python im storage sdk protobuf glog)
target_link_libraries(core PRIVATE pybind python im storage sdk protobuf glog)
set_target_properties(core PROPERTIES PREFIX "" SUFFIX ".so")
#include <glog/logging.h>
#include <ctime>
#include "visualdl/logic/im.h"
namespace visualdl {
/*
* @num_samples: number of instances to sample
* @size: counter of the records.
* @returns: id of the instance to replace, if drop this instance, return -1.
*/
int ReserviorSample(int num_samples, int num_records) {
if (num_records <= num_samples) {
return num_records;
}
std::srand(std::time(0));
float prob = static_cast<float>(std::rand()) / RAND_MAX;
float receive_prob = static_cast<float>(num_samples) / num_records;
if (prob < receive_prob) {
int offset2replace = std::rand() % num_samples;
return offset2replace;
}
return -1;
}
void IM::SetPersistDest(const std::string &path) {
CHECK(storage_->mutable_data()->dir().empty())
<< "duplicate set storage's path";
storage_->mutable_data()->set_dir(path);
}
storage::Tablet *IM::AddTablet(const std::string &tag, int num_samples) {
auto tablet = storage_->NewTablet(tag, num_samples);
return tablet;
}
void IM::AddRecord(const std::string &tag, const storage::Record &data) {
auto *tablet = storage_->tablet(tag);
CHECK(tablet) << "no tablet called " << tag;
auto num_records = tablet->total_records();
const auto num_samples = tablet->num_samples();
int offset;
// use reservoir sampling or not
if (num_samples > 0) {
offset = ReserviorSample(num_samples, num_records + 1);
if (offset < 0) return;
} else {
offset = num_records;
}
storage::Record *record;
if (offset >= num_records) {
record = tablet->add_records();
} else {
record = tablet->mutable_records(offset);
}
*record = data;
tablet->set_total_records(num_records + 1);
}
void IM::Clear() {
auto *data = storage().mutable_data();
data->clear_tablets();
data->clear_dir();
data->clear_timestamp();
}
void IM::PersistToDisk() {
CHECK(!storage_->data().dir().empty()) << "path of storage should be set";
// TODO make dir first
// MakeDir(storage_.data().dir());
storage_->PersistToDisk(storage_->data().dir());
}
} // namespace visualdl
#ifndef VISUALDL_LOGIC_IM_H
#define VISUALDL_LOGIC_IM_H
#include <glog/logging.h>
#include <memory>
#include <mutex>
#include <string>
#include "visualdl/storage/storage.h"
#include "visualdl/utils/concurrency.h"
namespace visualdl {
/*
* IM(Information Maintainer) maintain the Storage singleton in memory,
* pre-compute some the statistical information to help visualizaton.
*
* There should be two processes and each have an IM, one is the web server
* which hold one IM to read the storage, the other is the SDK(python or C++),
* it will get an IM to write latest changes to storage.
*
* An IM have an underlying Storage object, which might be a memory based
* storage or a disk based one, both has the same interfaces those defined by
* class StorageBase.
*
* The SDK's IM will maintain the changes and periodically write to disk, and
* the web server's IM will periodically read latest storage from disk.
*/
class IM final {
public:
IM() { storage_.reset(new MemoryStorage(&executor_)); }
// IM(StorageBase::Type type, StorageBase::Mode mode);
~IM() { executor_.Quit(); }
void MaintainRead(const std::string &dir, int msecs) {
LOG(INFO) << "start maintain read";
dynamic_cast<MemoryStorage *>(storage_.get())
->StartReadService(dir, msecs, &lock_);
}
void MaintainWrite(const std::string &dir, int msecs) {
dynamic_cast<MemoryStorage *>(storage_.get())
->StartWriteService(dir, msecs, &lock_);
}
/*
* Set the disk path to store the Storage object.
*/
void SetPersistDest(const std::string &path);
storage::Tablet *AddTablet(const std::string &tag, int num_samples);
/*
* @tag: tag of the target Tablet.
* @record: a record
*
* NOTE pass in the serialized protobuf message will trigger copying, but
* simpler to support different Tablet data formats.
*/
void AddRecord(const std::string &tag, const storage::Record &record);
/*
* delete all the information.
*/
void Clear();
/*
* Save the Storage Protobuf to disk.
*/
void PersistToDisk();
StorageBase &storage() { return *storage_; }
cc::PeriodExector &executor() { return executor_; }
std::mutex &handler() { return lock_; }
private:
// read write lock for protobuf in memory
// TODO(ChunweiYan) mutex too heavy here, might change to a message queue to
// reduce the frequency of visiting disk
std::mutex lock_;
std::unique_ptr<StorageBase> storage_;
cc::PeriodExector executor_;
};
} // namespace visualdl
#endif // VISUALDL_BACKEND_LOGIC_IM_H
#include "visualdl/logic/im.h"
#include "gtest/gtest.h"
#include "visualdl/storage/storage.h"
namespace visualdl {
class ImTester : public ::testing::Test {
protected:
void SetUp() override {}
IM im;
};
TEST_F(ImTester, AddTablet) {
im.Clear();
im.AddTablet("tag0", 20);
}
TEST_F(ImTester, AddRecord) {
im.Clear();
im.AddTablet("tag0", 20);
for (int i = 0; i < 100; i++) {
storage::Record rcd;
rcd.set_dtype(storage::DataType::kInt32s);
for (int j = 0; j < 10; j++) {
rcd.add_data()->add_i32s(i * 20 + j);
}
im.AddRecord("tag0", rcd);
}
ASSERT_EQ(im.storage().tablet("tag0")->records_size(), 100UL);
}
} // namespace visualdl
#include <ctype.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "visualdl/logic/sdk.h"
namespace py = pybind11;
namespace vs = visualdl;
PYBIND11_PLUGIN(core) {
py::module m("core", "C++ core of VisualDL");
// m.doc() = "visualdl python core API";
py::class_<vs::TabletHelper>(m, "Tablet")
// other member setter and getter
.def("record_buffer", &vs::TabletHelper::record_buffer)
.def("records_size", &vs::TabletHelper::records_size)
.def("buffer", &vs::TabletHelper::buffer)
.def("human_readable_buffer", &vs::TabletHelper::human_readable_buffer)
.def("set_buffer",
(void (vs::TabletHelper::*)(const std::string&)) &
vs::TabletHelper::SetBuffer)
// scalar interface
.def("as_int32_scalar",
[](vs::TabletHelper& self, vs::ImHelper& im) {
return vs::components::ScalarHelper<int32_t>(self, &im.handler());
})
.def("as_int64_scalar",
[](vs::TabletHelper& self, vs::ImHelper& im) {
return vs::components::ScalarHelper<int64_t>(&self.data(),
&im.handler());
})
.def("as_float_scalar",
[](vs::TabletHelper& self, vs::ImHelper& im) {
return vs::components::ScalarHelper<float>(&self.data(),
&im.handler());
})
.def("as_double_scalar", [](vs::TabletHelper& self, vs::ImHelper& im) {
return vs::components::ScalarHelper<double>(&self.data(),
&im.handler());
});
py::class_<vs::StorageHelper>(m, "Storage")
.def("timestamp", &vs::StorageHelper::timestamp)
.def("dir", &vs::StorageHelper::dir)
.def("set_dir", &vs::StorageHelper::SetDir)
.def("tablets_size", &vs::StorageHelper::tablets_size)
.def("buffer", &vs::StorageHelper::buffer)
.def("human_readable_buffer", &vs::StorageHelper::human_readable_buffer)
.def("set_buffer",
(void (vs::StorageHelper::*)(const std::string&)) &
vs::StorageHelper::SetBuffer);
py::class_<vs::ImHelper>(m, "Im")
.def("__init__",
[](vs::ImHelper& instance) { new (&instance) vs::ImHelper(); })
.def("storage", &vs::ImHelper::storage)
.def("tablet", &vs::ImHelper::tablet)
.def("add_tablet", &vs::ImHelper::AddTablet)
.def("persist_to_disk", &vs::ImHelper::PersistToDisk)
.def("clear_tablets", &vs::ImHelper::ClearTablets)
.def("start_read_service",
&vs::ImHelper::StartReadService,
"start a thread to maintain read service")
.def("start_write_service",
&vs::ImHelper::StartWriteSerice,
"start a thread to maintain write service")
.def("stop_service",
&vs::ImHelper::StopService,
"stop the service thread");
// interfaces for components begin
// different data type of scalar conponent
#define ADD_SCALAR_TYPED_INTERFACE(T, name__) \
py::class_<vs::components::ScalarHelper<T>>(m, #name__) \
.def("add_record", &vs::components::ScalarHelper<T>::AddRecord) \
.def("set_captions", &vs::components::ScalarHelper<T>::SetCaptions) \
.def("get_records", &vs::components::ScalarHelper<T>::GetRecords) \
.def("get_captions", &vs::components::ScalarHelper<T>::GetCaptions) \
.def("get_ids", &vs::components::ScalarHelper<T>::GetIds) \
.def("get_record_size", &vs::components::ScalarHelper<T>::GetSize) \
.def("get_timestamps", &vs::components::ScalarHelper<T>::GetTimestamps);
ADD_SCALAR_TYPED_INTERFACE(int32_t, ScalarInt32);
ADD_SCALAR_TYPED_INTERFACE(int64_t, ScalarInt64);
ADD_SCALAR_TYPED_INTERFACE(float, ScalarFloat);
ADD_SCALAR_TYPED_INTERFACE(double, ScalarDouble);
#undef ADD_SCALAR_TYPED_INTERFACE
}
#include "visualdl/logic/sdk.h"
#include <google/protobuf/text_format.h>
namespace visualdl {
#define IMPL_ENTRY_SET_OR_ADD(method__, ctype__, dtype__, opr__) \
template <> \
void EntryHelper<ctype__>::method__(ctype__ v) { \
entry->set_dtype(storage::DataType::dtype__); \
entry->opr__(v); \
}
IMPL_ENTRY_SET_OR_ADD(Set, int32_t, kInt32, set_i32);
IMPL_ENTRY_SET_OR_ADD(Set, int64_t, kInt64, set_i64);
IMPL_ENTRY_SET_OR_ADD(Set, bool, kBool, set_b);
IMPL_ENTRY_SET_OR_ADD(Set, float, kFloat, set_f);
IMPL_ENTRY_SET_OR_ADD(Set, double, kDouble, set_d);
IMPL_ENTRY_SET_OR_ADD(Add, int32_t, kInt32s, add_i32s);
IMPL_ENTRY_SET_OR_ADD(Add, int64_t, kInt64s, add_i64s);
IMPL_ENTRY_SET_OR_ADD(Add, float, kFloats, add_fs);
IMPL_ENTRY_SET_OR_ADD(Add, double, kDoubles, add_ds);
IMPL_ENTRY_SET_OR_ADD(Add, std::string, kStrings, add_ss);
IMPL_ENTRY_SET_OR_ADD(Add, bool, kBools, add_bs);
#define IMPL_ENTRY_GET(T, fieldname__) \
template <> \
T EntryHelper<T>::Get() const { \
return entry->fieldname__(); \
}
IMPL_ENTRY_GET(int32_t, i32);
IMPL_ENTRY_GET(int64_t, i64);
IMPL_ENTRY_GET(float, f);
IMPL_ENTRY_GET(double, d);
IMPL_ENTRY_GET(std::string, s);
IMPL_ENTRY_GET(bool, b);
#define IMPL_ENTRY_GET_MULTI(T, fieldname__) \
template <> \
std::vector<T> EntryHelper<T>::GetMulti() const { \
return std::vector<T>(entry->fieldname__().begin(), \
entry->fieldname__().end()); \
}
IMPL_ENTRY_GET_MULTI(int32_t, i32s);
IMPL_ENTRY_GET_MULTI(int64_t, i64s);
IMPL_ENTRY_GET_MULTI(float, fs);
IMPL_ENTRY_GET_MULTI(double, ds);
IMPL_ENTRY_GET_MULTI(std::string, ss);
IMPL_ENTRY_GET_MULTI(bool, bs);
std::string StorageHelper::human_readable_buffer() const {
std::string buffer;
google::protobuf::TextFormat::PrintToString(*data_, &buffer);
return buffer;
}
std::string TabletHelper::human_readable_buffer() const {
std::string buffer;
google::protobuf::TextFormat::PrintToString(*data_, &buffer);
return buffer;
}
// implementations for components
namespace components {
template <typename T>
void ScalarHelper<T>::SetCaptions(const std::vector<std::string> &captions) {
ACQUIRE_HANDLER(handler_);
CHECK_EQ(data_->captions_size(), 0UL) << "the captions can set only once";
for (int i = 0; i < captions.size(); i++) {
data_->add_captions(captions[i]);
}
}
template <typename T>
void ScalarHelper<T>::AddRecord(int id, const std::vector<T> &values) {
ACQUIRE_HANDLER(handler_);
CHECK_NOTNULL(data_);
CHECK_GT(data_->captions_size(), 0UL) << "captions should be set first";
CHECK_EQ(data_->captions_size(), values.size())
<< "number of values in a record should be compatible with the "
"captions";
// add record data
auto *record = data_->add_records();
auto *data = record->add_data();
EntryHelper<T> entry_helper(data);
for (auto v : values) {
entry_helper.Add(v);
}
// set record id
record->set_id(id);
// set record timestamp
record->set_timestamp(time(NULL));
}
template <typename T>
std::vector<std::vector<T>> ScalarHelper<T>::GetRecords() const {
ACQUIRE_HANDLER(handler_);
std::vector<std::vector<T>> result;
EntryHelper<T> entry_helper;
for (int i = 0; i < data_->records_size(); i++) {
auto *entry = data_->mutable_records(i)->mutable_data(0);
entry_helper(entry);
auto datas = entry_helper.GetMulti();
result.push_back(std::move(datas));
}
return result;
}
template <typename T>
std::vector<int> ScalarHelper<T>::GetIds() const {
ACQUIRE_HANDLER(handler_);
CHECK_NOTNULL(data_);
std::vector<int> result;
for (int i = 0; i < data_->records_size(); i++) {
result.push_back(data_->records(i).id());
}
return result;
}
template <typename T>
std::vector<int> ScalarHelper<T>::GetTimestamps() const {
ACQUIRE_HANDLER(handler_);
CHECK_NOTNULL(data_);
std::vector<int> result;
for (int i = 0; i < data_->records_size(); i++) {
result.push_back(data_->records(i).timestamp());
}
return result;
}
template <typename T>
std::vector<std::string> ScalarHelper<T>::GetCaptions() const {
ACQUIRE_HANDLER(handler_);
return std::vector<std::string>(data_->captions().begin(),
data_->captions().end());
}
template class ScalarHelper<int32_t>;
template class ScalarHelper<int64_t>;
template class ScalarHelper<float>;
template class ScalarHelper<double>;
} // namespace components
} // namespace visualdl
#ifndef VISUALDL_LOGIC_SDK_H
#define VISUALDL_LOGIC_SDK_H
#include <glog/logging.h>
#include <time.h>
#include <map>
#include "visualdl/logic/im.h"
namespace visualdl {
/*
* Utility helper for storage::Entry.
*/
template <typename T>
struct EntryHelper {
// use pointer to avoid copy
storage::Entry *entry{nullptr};
EntryHelper() {}
explicit EntryHelper(storage::Entry *entry) : entry(entry) {}
void operator()(storage::Entry *entry) { this->entry = entry; }
/*
* Set a single value.
*/
void Set(T v);
/*
* Add a value to repeated message field.
*/
void Add(T v);
/*
* Get a single value.
*/
T Get() const;
/*
* Get repeated field.
*/
std::vector<T> GetMulti() const;
};
class TabletHelper {
public:
// basic member getter and setter
std::string record_buffer(int idx) const {
return data_->records(idx).SerializeAsString();
}
size_t records_size() const { return data_->records_size(); }
std::string buffer() const { return data_->SerializeAsString(); }
std::string human_readable_buffer() const;
void SetBuffer(const storage::Tablet &t) { *data_ = t; }
void SetBuffer(const std::string &b) { data_->ParseFromString(b); }
storage::Tablet &data() const { return *data_; }
// constructor that enable concurrency.
TabletHelper(storage::Tablet *t) : data_(t) {}
// data updater that resuage of one instance.
TabletHelper &operator()(storage::Tablet *t) {
data_ = t;
return *this;
}
private:
storage::Tablet *data_;
};
class StorageHelper {
public:
StorageHelper(storage::Storage *s) : data_(s) {}
StorageHelper &operator()(storage::Storage *s) {
data_ = s;
return *this;
}
void SetBuffer(const storage::Storage &buffer) { *data_ = buffer; }
void SetBuffer(const std::string &buffer) { data_->ParseFromString(buffer); }
void SetDir(const std::string &dir) {
CHECK(data_) << "no storage instance hold";
data_->set_dir(dir);
}
int64_t timestamp() const { return data_->timestamp(); }
std::string dir() const { return data_->dir(); }
int tablets_size() const { return data_->tablets_size(); }
std::string buffer() const { return data_->SerializeAsString(); }
std::string human_readable_buffer() const;
private:
storage::Storage *data_{nullptr};
};
class ImHelper {
public:
// TODO(ChunweiYan) decouple helper with resource.
ImHelper() { im_.reset(new IM); }
ImHelper(std::unique_ptr<IM> im) : im_(std::move(im)) {}
StorageHelper storage() {
return StorageHelper(im_->storage().mutable_data());
}
TabletHelper tablet(const std::string &tag) {
return TabletHelper(im_->storage().tablet(tag));
}
TabletHelper AddTablet(const std::string &tag, int num_samples) {
return TabletHelper(im_->AddTablet(tag, num_samples));
}
std::mutex &handler() { return im_->handler(); }
void ClearTablets() { im_->storage().mutable_data()->clear_tablets(); }
void StartReadService(const std::string &dir, int msecs) {
im_->SetPersistDest(dir);
im_->MaintainRead(dir, msecs);
}
void StartWriteSerice(const std::string &dir, int msecs) {
im_->SetPersistDest(dir);
im_->MaintainWrite(dir, msecs);
}
void StopService() { im_->executor().Quit(); }
void PersistToDisk() const { im_->PersistToDisk(); }
private:
std::unique_ptr<IM> im_;
};
namespace components {
#define ACQUIRE_HANDLER(handler) std::lock_guard<std::mutex> ____(*handler);
/*
* Read and write support for Scalar component.
*/
template <typename T>
class ScalarHelper {
public:
ScalarHelper(storage::Tablet *tablet, std::mutex *handler = nullptr)
: data_(tablet), handler_(handler) {}
ScalarHelper(TabletHelper &tablet, std::mutex *handler = nullptr)
: data_(&tablet.data()), handler_(handler) {}
void SetCaptions(const std::vector<std::string> &captions);
void AddRecord(int id, const std::vector<T> &values);
std::vector<std::vector<T>> GetRecords() const;
std::vector<int> GetIds() const;
std::vector<int> GetTimestamps() const;
std::vector<std::string> GetCaptions() const;
size_t GetSize() const { return data_->records_size(); }
private:
storage::Tablet *data_;
std::mutex *handler_;
};
} // namespace components
} // namespace visualdl
#endif // VISUALDL_BACKEND_LOGIC_SDK_H
#include "visualdl/logic/sdk.h"
#include <gtest/gtest.h>
namespace visualdl {
struct ScalarTestHelper {
ImHelper rim;
ImHelper wim;
const std::string dir = "./tmp/sdk_test.test";
void operator()(std::function<void()> read, std::function<void()> write) {
wim.StartWriteSerice(dir, 200);
write();
// should wait for the write service create log's path
std::this_thread::sleep_for(std::chrono::milliseconds(400));
rim.StartReadService(dir, 100);
// should wait for the read service to load "tag0" tablet into memory
std::this_thread::sleep_for(std::chrono::milliseconds(600));
read();
}
};
TEST(Scalar, set_caption) {
ScalarTestHelper helper;
const std::vector<std::string> captions({"train", "test"});
auto write = [&] {
auto tablet = helper.wim.AddTablet("tag0", -1);
components::ScalarHelper<float> scalar(tablet, &helper.wim.handler());
scalar.SetCaptions(captions);
};
auto read = [&] {
auto mytablet = helper.rim.tablet("tag0");
components::ScalarHelper<float> myscalar(mytablet, &helper.rim.handler());
auto mycaptions = myscalar.GetCaptions();
ASSERT_EQ(captions, mycaptions);
};
helper(read, write);
}
TEST(Scalar, add_records) {
ScalarTestHelper helper;
const std::vector<std::string> captions({"train", "test"});
const size_t nsteps = 100;
auto write = [&] {
auto tablet = helper.wim.AddTablet("tag0", -1);
components::ScalarHelper<float> scalar(tablet, &helper.wim.handler());
scalar.SetCaptions(captions);
for (int i = 0; i < nsteps; i++) {
scalar.AddRecord(i * 10, std::vector<float>({(float)i, (float)i + 1}));
}
};
auto read = [&] {
auto mytablet = helper.rim.tablet("tag0");
components::ScalarHelper<float> myscalar(mytablet, &helper.rim.handler());
auto records = myscalar.GetRecords();
ASSERT_EQ(records.size(), nsteps);
for (int i = 0; i < nsteps; i++) {
ASSERT_EQ(records[i], std::vector<float>({(float)i, (float)i + 1}));
}
};
helper(read, write);
}
} // namespace visualdl
function(py_test TARGET_NAME)
set(options STATIC static SHARED shared)
set(oneValueArgs "")
set(multiValueArgs SRCS DEPS ARGS)
cmake_parse_arguments(py_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
add_test(NAME ${TARGET_NAME}
COMMAND env PYTHONPATH=${CMAKE_BINARY_DIR}/visualdl/logic
${PYTHON_EXECUTABLE} -u ${py_test_SRCS} ${py_test_ARGS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endfunction()
py_test(test_summary SRCS test_summary.py)
__all__ = [
'set_storage',
'scalar',
]
import core
dtypes = ("float", "double", "int32", "int64")
def IM(dir, mode="read", msecs=500):
im = core.Im()
READ = "read"
WRITE = "write"
if mode == READ:
im.start_read_service(dir, msecs)
else:
im.start_write_service(dir, msecs)
return im
class _Scalar(object):
'''
Python syntax wrapper for the core.ScalarHelper object.
'''
def __init__(self, core_object):
self._core_object = core_object
def add(self, id, vs):
'''
add a scalar record
:param id: int
id in the x-corrdinate
:param vs: list
values
:return: None
'''
self._core_object.add_record(id, vs)
def set_captions(self, cs):
'''
set the captions, one caption for one line.
:param cs: list of str
:return: None
'''
self._core_object.set_captions(cs)
@property
def captions(self):
return self._core_object.get_captions()
@property
def records(self):
'''
get all the records, format like
[
[0.1, 0.2], # first record
[0.2, 0.3], # second record
# ...
]
:return: list of list
'''
return self._core_object.get_records()
@property
def ids(self):
'''
get all the ids for the records
:return: list of int
'''
return self._core_object.get_ids()
@property
def timestamps(self):
'''
get all the timestamps for the records
:return: list of int
'''
return self._core_object.get_timestamps()
@property
def size(self):
return self._core_object.get_record_size()
def scalar(im, tag, dtype='float'):
'''
create a scalar component.
:param tag: str
name of this component.
:param dtype: string
the data type that will be used in underlying storage.
:return: object of core.Tablet
'''
assert dtype in dtypes, "invalid dtype(%s), should be one of %s" % (
dtype, str(dtypes))
tablet = im.add_tablet(tag, -1)
dtype2obj = {
'float': tablet.as_float_scalar,
'double': tablet.as_double_scalar,
'int32': tablet.as_int32_scalar,
'int64': tablet.as_int64_scalar,
}
obj = dtype2obj[dtype](im)
return _Scalar(obj)
import summary
import numpy as np
import unittest
import time
class StorageTester(unittest.TestCase):
def test_storage(self):
summary.set_writable_storage("./tmp_dir")
time.sleep(5)
summary.stop_service()
if __name__ == '__main__':
unittest.main()
import summary
import numpy as np
import unittest
import random
import time
once_flag = False
class ScalarTester(unittest.TestCase):
def setUp(self):
self.dir = "tmp/summary.test"
# clean path
try:
os.rmdir(self.dir)
except:
pass
self.im = summary.IM(self.dir, "write", 200)
self.tablet_name = "scalar0"
self.scalar = summary.scalar(self.im, self.tablet_name)
self.py_captions = ["train cost", "test cost"]
self.scalar.set_captions(self.py_captions)
self.py_records = []
self.py_ids = []
# write
for i in range(10):
record = [0.1 * i, 0.2 * i]
id = i * 10
self.py_records.append(record)
self.py_ids.append(id)
self.scalar.add(id, record)
def test_records(self):
self.assertEqual(self.scalar.size, len(self.py_records))
for i, record in enumerate(self.scalar.records):
self.assertTrue(np.isclose(record, self.py_records[i]).all())
def test_ids(self):
self.assertEqual(len(self.py_ids), self.scalar.size)
for i, id in enumerate(self.scalar.ids):
self.assertEqual(self.py_ids[i], id)
def test_captions(self):
self.assertEqual(self.scalar.captions, self.py_captions)
def test_read_records(self):
time.sleep(1)
im = summary.IM(self.dir, "read", 200)
time.sleep(1)
scalar = summary.scalar(im, self.tablet_name)
records = scalar.records
self.assertEqual(len(self.py_records), scalar.size)
for i, record in enumerate(self.scalar.records):
self.assertTrue(np.isclose(record, records[i]).all())
def test_read_ids(self):
time.sleep(0.6)
im = summary.IM(self.dir, "read", msecs=200)
time.sleep(0.6)
scalar = summary.scalar(im, self.tablet_name)
self.assertEqual(len(self.py_ids), scalar.size)
for i, id in enumerate(scalar.ids):
self.assertEqual(self.py_ids[i], id)
def test_read_captions(self):
time.sleep(0.6)
im = summary.IM(self.dir, "read", msecs=200)
time.sleep(0.6)
scalar = summary.scalar(im, self.tablet_name)
self.assertEqual(scalar.captions, self.py_captions)
def test_mix_read_write(self):
write_im = summary.IM(self.dir, "write", msecs=200)
time.sleep(0.6)
read_im = summary.IM(self.dir, "read", msecs=200)
scalar_writer = summary.scalar(write_im, self.tablet_name)
scalar_reader = summary.scalar(read_im, self.tablet_name)
scalar_writer.set_captions(["train cost", "test cost"])
for i in range(1000):
scalar_writer.add(i, [random.random(), random.random()])
scalar_reader.records
for i in range(500):
scalar_writer.add(i, [random.random(), random.random()])
scalar_reader.records
for i in range(500):
scalar_writer.add(i, [random.random(), random.random()])
for i in range(10):
scalar_reader.records
scalar_reader.captions
if __name__ == '__main__':
unittest.main()
import summary
import numpy as np
import unittest
import time
class StorageTester(unittest.TestCase):
def test_read_storage(self):
summary.set_readable_storage("./tmp")
time.sleep(1)
scalar = summary.read_scalar('tag01')
time.sleep(5)
summary.stop_service()
if __name__ == '__main__':
unittest.main()
## add storage_proto as target
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS storage.proto)
add_library(storage_proto ${PROTO_SRCS})
add_dependencies(storage_proto protobuf)
## add storage as target
add_library(storage storage.cc storage.h ${PROTO_SRCS} ${PROTO_HDRS})
add_dependencies(storage storage_proto)
此差异已折叠。
此差异已折叠。
此差异已折叠。
#include "visualdl/storage/storage.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
namespace visualdl {
using namespace std;
class MemoryStorageTest : public ::testing::Test {
public:
void SetUp() override { storage_.SetStorage("./tmp"); }
MemoryStorage storage_;
};
TEST_F(MemoryStorageTest, SetStorage) {
string dir = "./tmp";
storage_.SetStorage(dir);
ASSERT_EQ(storage_.data().dir(), dir);
}
TEST_F(MemoryStorageTest, AddTablet) {
// TODO need to escape tag as name
string tag = "add%20tag0";
storage_.NewTablet(tag, -1);
auto* tablet = storage_.tablet(tag);
ASSERT_TRUE(tablet != nullptr);
ASSERT_EQ(tablet->tag(), tag);
}
TEST_F(MemoryStorageTest, PersistToDisk) {
const std::string dir = "./tmp/201.test";
storage_.SetStorage(dir);
string tag = "add%20tag0";
storage_.NewTablet(tag, -1);
storage_.PersistToDisk(dir);
LOG(INFO) << "persist to disk";
MemoryStorage other;
other.LoadFromDisk(dir);
LOG(INFO) << "read from disk";
ASSERT_EQ(other.data().SerializeAsString(),
storage_.data().SerializeAsString());
}
} // namespace visualdl
#include "gtest/gtest.h"
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
#ifndef VISUALDL_UTILS_LOG_H
#define VISUALDL_UTILS_LOG_H
#include <stdexcept>
namespace visualdl {
namespace log {
class NotImplementedException : public std::logic_error {
public:
NotImplementedException() : std::logic_error{"Function not implemented"} {}
};
} // namespace log
} // namespace visualdl
#endif
#include "visualdl/utils/concurrency.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
namespace visualdl {
int counter = 0;
TEST(concurrency, test) {
cc::PeriodExector executor;
cc::PeriodExector::task_t task = [&]() {
LOG(INFO) << "Hello " << counter++;
if (counter > 5) return false;
return true;
};
executor(std::move(task), 200);
}
} // namespace visualdl
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册