mirror of
https://github.com/wolfpld/tracy.git
synced 2026-09-01 07:58:31 +00:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
538af5c89c | ||
|
|
f52bf6a718 | ||
|
|
70e4170e26 | ||
|
|
c6f1cb7585 | ||
|
|
00e152b97a | ||
|
|
17a4080713 | ||
|
|
c756e7418e | ||
|
|
71f0c992f7 | ||
|
|
06f4e13012 | ||
|
|
a66cc184e8 | ||
|
|
c9abfce098 | ||
|
|
a15e08f135 | ||
|
|
fdff78584a | ||
|
|
893439772f | ||
|
|
6d794801c6 | ||
|
|
b36c8f2fc5 | ||
|
|
2f2d8a7ba9 | ||
|
|
d498829518 | ||
|
|
e87b7c873e | ||
|
|
6109ab27b5 | ||
|
|
7357b9fa52 | ||
|
|
46f2489f92 | ||
|
|
ac970771ee | ||
|
|
d87cbed7eb | ||
|
|
252a3dae43 | ||
|
|
c9d3ec0088 | ||
|
|
e5d20e1377 | ||
|
|
da0d2328f5 | ||
|
|
8db1a298c4 | ||
|
|
37410933db | ||
|
|
afe1ec5faf | ||
|
|
d440f5e055 | ||
|
|
ae70255b4e | ||
|
|
b887def929 | ||
|
|
7aee673792 | ||
|
|
81f3c71ef3 | ||
|
|
d8a011630d | ||
|
|
1f1d3ab856 | ||
|
|
2e566513dd | ||
|
|
2a40f6f990 | ||
|
|
0e7040d73b | ||
|
|
bd1d783017 | ||
|
|
d352907e5d | ||
|
|
4e520cfc18 | ||
|
|
ac9ea9dde7 | ||
|
|
38b19d2be6 | ||
|
|
601d57de35 | ||
|
|
8e8f3073a7 | ||
|
|
8b41c38ed9 | ||
|
|
d6ef0f0fa4 | ||
|
|
031ef02614 | ||
|
|
0f65035ef3 | ||
|
|
253b68dcb6 | ||
|
|
32aa7ee1f5 | ||
|
|
5ed42cfc6d | ||
|
|
b2b8dc51ce | ||
|
|
f207007548 | ||
|
|
94e58d4e24 |
6
.github/workflows/appimage.yml
vendored
6
.github/workflows/appimage.yml
vendored
@@ -17,6 +17,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
|
||||
|
||||
jobs:
|
||||
build-appimage:
|
||||
@@ -40,6 +41,11 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Cache pandoc
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.PANDOC_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
|
||||
- name: Cache wayland
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
||||
6
.github/workflows/emscripten.yml
vendored
6
.github/workflows/emscripten.yml
vendored
@@ -16,6 +16,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
|
||||
|
||||
jobs:
|
||||
build-emscripten:
|
||||
@@ -37,6 +38,11 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Cache pandoc
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.PANDOC_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
|
||||
- name: Profiler GUI
|
||||
run: |
|
||||
cmake -G Ninja -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=MinSizeRel -DGIT_REV=${{ github.sha }} -DCMAKE_TOOLCHAIN_FILE=${{env.EMSDK}}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
|
||||
|
||||
5
.github/workflows/linux-cli.yml
vendored
5
.github/workflows/linux-cli.yml
vendored
@@ -57,6 +57,10 @@ jobs:
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel
|
||||
- name: Build monitor
|
||||
run: |
|
||||
cmake -B monitor/build -S monitor -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build monitor/build --parallel
|
||||
- name: Stage release files
|
||||
run: |
|
||||
mkdir -p linux-cli
|
||||
@@ -67,6 +71,7 @@ jobs:
|
||||
cp import/build/tracy-import-chrome linux-cli/
|
||||
cp import/build/tracy-import-fuchsia linux-cli/
|
||||
cp merge/build/tracy-merge linux-cli/
|
||||
cp monitor/build/tracy-monitor linux-cli/
|
||||
strip linux-cli/tracy-*
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
11
.github/workflows/linux.yml
vendored
11
.github/workflows/linux.yml
vendored
@@ -16,6 +16,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
@@ -33,6 +34,11 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Cache pandoc
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.PANDOC_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
|
||||
- name: Determine build parallelism
|
||||
run: |
|
||||
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
|
||||
@@ -72,6 +78,10 @@ jobs:
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build $CMAKE_PARALLEL
|
||||
- name: Monitor utility
|
||||
run: |
|
||||
cmake -B monitor/build -S monitor -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build monitor/build $CMAKE_PARALLEL
|
||||
- name: Library (cmake)
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
|
||||
@@ -96,6 +106,7 @@ jobs:
|
||||
cp import/build/tracy-import-chrome bin
|
||||
cp import/build/tracy-import-fuchsia bin
|
||||
cp merge/build/tracy-merge bin
|
||||
cp monitor/build/tracy-monitor bin
|
||||
strip bin/tracy-*
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
6
.github/workflows/macos.yml
vendored
6
.github/workflows/macos.yml
vendored
@@ -17,6 +17,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
@@ -29,6 +30,11 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Cache pandoc
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.PANDOC_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config meson
|
||||
- name: Trust git repo
|
||||
|
||||
18
.github/workflows/windows.yml
vendored
18
.github/workflows/windows.yml
vendored
@@ -17,6 +17,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
@@ -29,9 +30,26 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Cache pandoc
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.PANDOC_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
|
||||
- uses: microsoft/setup-msbuild@v2
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- name: Install meson
|
||||
run: python -m pip install --upgrade meson
|
||||
- name: Library (meson)
|
||||
env:
|
||||
CCACHE_DISABLE: '1'
|
||||
run: |
|
||||
$vs = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||
foreach ($line in & cmd /c "`"$vs\VC\Auxiliary\Build\vcvarsall.bat`" x64 && set") {
|
||||
if ($line -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') }
|
||||
}
|
||||
meson setup -Dtracy_enable=true build-meson
|
||||
meson compile -C build-meson
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Build profiler
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,6 +17,7 @@ manual/t*.bbl
|
||||
manual/t*.blg
|
||||
manual/t*.fdb_latexmk
|
||||
manual/t*.fls
|
||||
manual/tracy.md
|
||||
profiler/build-appimage
|
||||
.deps/
|
||||
.dirstamp
|
||||
|
||||
@@ -288,7 +288,8 @@ fs.writeFileSync('/tmp/toolbar-zoom.png', buf);
|
||||
| 1012–1036 | 2142681495 | ▶ next frame — focuses the timeline on the next frame (verified: view range 25.82 s → 203.38 ms) |
|
||||
| 1052–1072 | 2961929287 | ▼ frame set selection (switch the active frame set) |
|
||||
|
||||
Button names and functions: the user manual, *Control menu* (`manual/tracy.md`). The
|
||||
Button names and functions: the user manual, *Control menu* (`manual/tracy.md`,
|
||||
generated into the profiler build directory by the build, e.g. `build/profiler/manual/tracy.md`). The
|
||||
web build omits *Connection* (live capture only) and *Tracy Assist* (desktop only).
|
||||
|
||||
## User scale (DPI zoom)
|
||||
|
||||
@@ -11,7 +11,7 @@ function(add_git_ref target)
|
||||
if(Git_FOUND)
|
||||
add_custom_target(git-ref
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp.tmp
|
||||
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} log -1 "--format=namespace tracy { static inline const char* GitRef = %x22%h%x22; }" ${GIT_REV} >> GitRef.hpp.tmp || echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp.tmp
|
||||
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} log -1 "--format=namespace tracy { static inline const char* GitRef = %x22%h%x22; }" ${GIT_REV} >> GitRef.hpp.tmp || ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp.tmp
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
|
||||
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
|
||||
VERBATIM
|
||||
|
||||
88
cmake/manual.cmake
Normal file
88
cmake/manual.cmake
Normal file
@@ -0,0 +1,88 @@
|
||||
set(PANDOC_VERSION 3.9.0.2)
|
||||
|
||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||
|
||||
find_program(TRACY_PANDOC pandoc)
|
||||
if(TRACY_PANDOC)
|
||||
execute_process(
|
||||
COMMAND ${TRACY_PANDOC} --version
|
||||
OUTPUT_VARIABLE _pandoc_version
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE _pandoc_rc
|
||||
ERROR_QUIET)
|
||||
string(REGEX MATCH "pandoc [0-9.]+" _pandoc_version "${_pandoc_version}")
|
||||
if(NOT _pandoc_rc EQUAL 0 OR NOT _pandoc_version STREQUAL "pandoc ${PANDOC_VERSION}")
|
||||
set(TRACY_PANDOC "")
|
||||
endif()
|
||||
endif()
|
||||
if(TRACY_PANDOC)
|
||||
message(STATUS "Using system pandoc ${PANDOC_VERSION}: ${TRACY_PANDOC}")
|
||||
else()
|
||||
if(WIN32)
|
||||
if(NOT CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
|
||||
endif()
|
||||
set(_pandoc_asset pandoc-${PANDOC_VERSION}-windows-x86_64.zip)
|
||||
set(_pandoc_hash c97542f2800f446e788d9f74237856d995421ad1bb3cc8324286840c5f272d3a)
|
||||
set(_pandoc_exe pandoc-${PANDOC_VERSION}/pandoc.exe)
|
||||
elseif(APPLE)
|
||||
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)$")
|
||||
set(_pandoc_asset pandoc-${PANDOC_VERSION}-arm64-macOS.zip)
|
||||
set(_pandoc_hash 6e9eca844076bcbb599bbeebbba78a70f93b5307782b85c2c272872812c88875)
|
||||
set(_pandoc_exe pandoc-${PANDOC_VERSION}-arm64/bin/pandoc)
|
||||
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
set(_pandoc_asset pandoc-${PANDOC_VERSION}-x86_64-macOS.zip)
|
||||
set(_pandoc_hash b9fbceabccbc8f34ac021a50483fc32f8160568d0b4b2c22d81bb29e3054fd82)
|
||||
set(_pandoc_exe pandoc-${PANDOC_VERSION}-x86_64/bin/pandoc)
|
||||
else()
|
||||
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
|
||||
endif()
|
||||
else()
|
||||
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$")
|
||||
set(_pandoc_asset pandoc-${PANDOC_VERSION}-linux-arm64.tar.gz)
|
||||
set(_pandoc_hash b6d21e8f9c3b15744f5a7ab40248019157ed7793875dbe0383d4c82ff572b528)
|
||||
set(_pandoc_exe pandoc-${PANDOC_VERSION}/bin/pandoc)
|
||||
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
set(_pandoc_asset pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz)
|
||||
set(_pandoc_hash a69abfababda8a56969a254b09f9553a7be89ddec00d4e0fe9fd585d71a67508)
|
||||
set(_pandoc_exe pandoc-${PANDOC_VERSION}/bin/pandoc)
|
||||
else()
|
||||
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Shared cache: $PANDOC_CACHE_DIR, else the platform user cache directory.
|
||||
if(DEFINED ENV{PANDOC_CACHE_DIR})
|
||||
set(_pandoc_cache $ENV{PANDOC_CACHE_DIR})
|
||||
elseif(WIN32)
|
||||
set(_pandoc_cache $ENV{LOCALAPPDATA}/tracy/pandoc)
|
||||
elseif(APPLE)
|
||||
set(_pandoc_cache $ENV{HOME}/Library/Caches/tracy/pandoc)
|
||||
elseif(DEFINED ENV{XDG_CACHE_HOME})
|
||||
set(_pandoc_cache $ENV{XDG_CACHE_HOME}/tracy/pandoc)
|
||||
else()
|
||||
set(_pandoc_cache $ENV{HOME}/.cache/tracy/pandoc)
|
||||
endif()
|
||||
|
||||
set(TRACY_PANDOC ${_pandoc_cache}/${_pandoc_exe})
|
||||
if(NOT EXISTS ${TRACY_PANDOC})
|
||||
# Concurrent configures on a cold cache can collide; worst case the
|
||||
# hash check fails and a rerun succeeds.
|
||||
message(STATUS "Downloading pandoc ${PANDOC_VERSION} (${_pandoc_asset})")
|
||||
file(MAKE_DIRECTORY ${_pandoc_cache})
|
||||
file(DOWNLOAD
|
||||
https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/${_pandoc_asset}
|
||||
${_pandoc_cache}/pandoc.archive
|
||||
EXPECTED_HASH SHA256=${_pandoc_hash}
|
||||
STATUS _pandoc_download)
|
||||
list(GET _pandoc_download 0 _pandoc_download_rc)
|
||||
if(NOT _pandoc_download_rc STREQUAL "0")
|
||||
file(REMOVE ${_pandoc_cache}/pandoc.archive)
|
||||
message(FATAL_ERROR "Failed to download pandoc ${PANDOC_VERSION}: ${_pandoc_download}")
|
||||
endif()
|
||||
file(ARCHIVE_EXTRACT
|
||||
INPUT ${_pandoc_cache}/pandoc.archive
|
||||
DESTINATION ${_pandoc_cache})
|
||||
file(REMOVE ${_pandoc_cache}/pandoc.archive)
|
||||
endif()
|
||||
endif()
|
||||
23
extra/tracy-version.py
Executable file
23
extra/tracy-version.py
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# Derive the project version from the single source of truth:
|
||||
# public/common/TracyVersion.hpp. Prints X.Y.Z, exits nonzero on failure.
|
||||
import re
|
||||
import sys
|
||||
|
||||
header = sys.argv[1]
|
||||
try:
|
||||
with open(header, encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
except OSError as exc:
|
||||
print('tracy-version: cannot read %s: %s' % (header, exc), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
numbers = {}
|
||||
for name in ('Major', 'Minor', 'Patch'):
|
||||
match = re.search(r'%s\s*=\s*(\d+)' % name, text)
|
||||
if not match:
|
||||
print('tracy-version: could not parse version from %s' % header, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
numbers[name] = match.group(1)
|
||||
|
||||
print('%s.%s.%s' % (numbers['Major'], numbers['Minor'], numbers['Patch']))
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Derive the project version from the single source of truth:
|
||||
# public/common/TracyVersion.hpp. Prints X.Y.Z, exits nonzero on failure.
|
||||
|
||||
header="$1"
|
||||
|
||||
[ -r "$header" ] || {
|
||||
echo "tracy-version: cannot read $header" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
major=$(sed -n 's/.*Major = \([0-9][0-9]*\).*/\1/p' "$header")
|
||||
minor=$(sed -n 's/.*Minor = \([0-9][0-9]*\).*/\1/p' "$header")
|
||||
patch=$(sed -n 's/.*Patch = \([0-9][0-9]*\).*/\1/p' "$header")
|
||||
|
||||
if [ -n "$major" ] && [ -n "$minor" ] && [ -n "$patch" ]; then
|
||||
printf '%s.%s.%s\n' "$major" "$minor" "$patch"
|
||||
else
|
||||
echo "tracy-version: could not parse version from $header" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
The LaTeX source file (tracy.tex) and the resulting PDF file (tracy.pdf) are the only authorative version of the user manual. Do NOT modify the Markdown user manual (tracy.md) by hand. It is only meant to be updated via the latex2md.sh script.
|
||||
@@ -1,35 +0,0 @@
|
||||
/\\begin\{bclogo\}\[/ {
|
||||
in_bclogo = 1
|
||||
bclogo_type = ""
|
||||
next
|
||||
}
|
||||
in_bclogo && /logo=/ {
|
||||
if (/\\bcbombe/) bclogo_type = "bcbombe"
|
||||
else if (/\\bcattention/) bclogo_type = "bcattention"
|
||||
else if (/\\bclampe/) bclogo_type = "bclampe"
|
||||
else if (/\\bcquestion/) bclogo_type = "bcquestion"
|
||||
next
|
||||
}
|
||||
in_bclogo && /noborder|couleur/ {
|
||||
next
|
||||
}
|
||||
in_bclogo {
|
||||
line = $0
|
||||
sub(/^[ \t]*\]?\{/, "", line)
|
||||
sub(/\}.*$/, "", line)
|
||||
bclogo_title = line
|
||||
|
||||
if (bclogo_type == "bcbombe") prefix = "IMPORTANT"
|
||||
else if (bclogo_type == "bcattention") prefix = "CAUTION"
|
||||
else if (bclogo_type == "bclampe") prefix = "TIP"
|
||||
else prefix = "NOTE"
|
||||
|
||||
printf "\\begin{quote}\\textbf{%s:%s}\\par\n", prefix, bclogo_title
|
||||
in_bclogo = 0
|
||||
next
|
||||
}
|
||||
/\\end\{bclogo\}/ {
|
||||
printf "\\end{quote}\n"
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Replace Font Awesome icon macros in LaTeX with Unicode codepoints."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
def pascal_to_snake(name):
|
||||
"""Convert PascalCase to UPPER_SNAKE_CASE."""
|
||||
result = name[0]
|
||||
for i in range(1, len(name)):
|
||||
if name[i].isupper() and name[i - 1].islower():
|
||||
result += '_'
|
||||
result += name[i]
|
||||
return result.upper()
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print(f"Usage: {sys.argv[0]} <header_path> <tex_path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
header_path = sys.argv[1]
|
||||
tex_path = sys.argv[2]
|
||||
|
||||
# Parse header: ICON_FA_SNAKE_CASE -> Unicode char
|
||||
icons = {}
|
||||
with open(header_path) as f:
|
||||
for line in f:
|
||||
m = re.match(
|
||||
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
|
||||
)
|
||||
if m:
|
||||
snake = m.group(1)
|
||||
parts = snake.split('_')
|
||||
pascal = ''.join(p.capitalize() for p in parts)
|
||||
codepoint = int(m.group(3), 16)
|
||||
icons[pascal] = chr(codepoint)
|
||||
|
||||
# Read tex file
|
||||
with open(tex_path) as f:
|
||||
text = f.read()
|
||||
|
||||
# Find all \faXxx used in the text (uppercase first letter excludes \fancyhead etc.)
|
||||
used = set()
|
||||
for m in re.finditer(r'\\fa([A-Z][a-zA-Z0-9]*)', text):
|
||||
used.add(m.group(1))
|
||||
|
||||
# Replace each used icon, longest names first to avoid prefix conflicts
|
||||
for name in sorted(used, key=lambda n: (-len(n), n)):
|
||||
if name not in icons:
|
||||
print(f"Warning: \\fa{name} not found in header", file=sys.stderr)
|
||||
continue
|
||||
char = icons[name]
|
||||
# Order matters: more specific patterns first
|
||||
text = text.replace(f'\\fa{name}{{}}~', f'{char} ')
|
||||
text = text.replace(f'\\fa{name}{{}}', char)
|
||||
text = text.replace(f'\\fa{name}~', f'{char} ')
|
||||
text = text.replace(f'\\fa{name}', char)
|
||||
|
||||
# Write back
|
||||
with open(tex_path, 'w') as f:
|
||||
f.write(text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Append icon legend blocks to each markdown section containing Font Awesome icons."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def _extract_icons(lines):
|
||||
"""Return deduplicated icon chars from lines, in order of first appearance."""
|
||||
seen = set()
|
||||
icons = []
|
||||
for line in lines:
|
||||
for ch in line:
|
||||
cp = ord(ch)
|
||||
if 0xE000 <= cp <= 0xF8FF and ch not in seen:
|
||||
seen.add(ch)
|
||||
icons.append(ch)
|
||||
return icons
|
||||
|
||||
|
||||
def _append_legend(result_lines, icons, icon_names):
|
||||
"""Append a legend block for the given icons."""
|
||||
result_lines.append('')
|
||||
result_lines.append('-----')
|
||||
result_lines.append('')
|
||||
for ch in icons:
|
||||
name = icon_names.get(ch, f'Unknown(U+{ord(ch):04X})')
|
||||
result_lines.append(f'{ch} - {name} icon')
|
||||
result_lines.append('')
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print(f"Usage: {sys.argv[0]} <header_path> <md_path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
header_path = sys.argv[1]
|
||||
md_path = sys.argv[2]
|
||||
|
||||
# Build char -> name mapping from header
|
||||
icon_names = {}
|
||||
with open(header_path) as f:
|
||||
for line in f:
|
||||
m = re.match(
|
||||
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
|
||||
)
|
||||
if m:
|
||||
snake = m.group(1)
|
||||
parts = snake.split('_')
|
||||
pascal = ' '.join(p.capitalize() for p in parts)
|
||||
codepoint = int(m.group(3), 16)
|
||||
icon_names[chr(codepoint)] = pascal
|
||||
|
||||
with open(md_path, encoding='utf-8') as f:
|
||||
lines = f.read().split('\n')
|
||||
|
||||
# Build chunk boundaries: header lines and EOF
|
||||
chunk_starts = [i for i, line in enumerate(lines) if line.startswith('#')]
|
||||
|
||||
# Also add index 0 as a chunk start if there's pre-header content
|
||||
if chunk_starts and chunk_starts[0] > 0:
|
||||
chunk_starts.insert(0, 0)
|
||||
|
||||
result_lines = []
|
||||
for ci, start in enumerate(chunk_starts):
|
||||
end = chunk_starts[ci + 1] if ci + 1 < len(chunk_starts) else len(lines)
|
||||
icons = _extract_icons(lines[start:end])
|
||||
result_lines.extend(lines[start:end])
|
||||
if icons:
|
||||
_append_legend(result_lines, icons, icon_names)
|
||||
|
||||
with open(md_path, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(result_lines))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
232
manual/latex2md.py
Normal file
232
manual/latex2md.py
Normal file
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the Markdown user manual from the LaTeX source.
|
||||
|
||||
Cross-platform replacement for the previous sh/sed/awk pipeline. pandoc is
|
||||
invoked as a subprocess; its Lua filter (filter.lua, next to the .tex file)
|
||||
stays with pandoc.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PANDOC_MARKDOWN = 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables'
|
||||
|
||||
NAMES = (
|
||||
('quicklook', 'A quick look at Tracy Profiler'),
|
||||
('firststeps', 'First steps'),
|
||||
('client', 'Client markup'),
|
||||
('capturing', 'Capturing the data'),
|
||||
('analyzingdata', 'Analyzing captured data'),
|
||||
('tracyassist', 'Tracy Assist'),
|
||||
('csvexport', 'Exporting zone statistics to CSV'),
|
||||
('importingdata', 'Importing external profiling data'),
|
||||
('configurationfiles', 'Configuration files'),
|
||||
)
|
||||
|
||||
|
||||
def parse_icon_header(path):
|
||||
"""Return (pascal_name -> char, char -> 'Word Word') maps from the icon header."""
|
||||
fa = {}
|
||||
names = {}
|
||||
with open(path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
m = re.match(
|
||||
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
|
||||
)
|
||||
if m:
|
||||
parts = m.group(1).split('_')
|
||||
ch = chr(int(m.group(3), 16))
|
||||
fa[''.join(p.capitalize() for p in parts)] = ch
|
||||
names[ch] = ' '.join(p.capitalize() for p in parts)
|
||||
return fa, names
|
||||
|
||||
|
||||
def substitute_icons(text, fa):
|
||||
used = set(re.findall(r'\\fa([A-Z][a-zA-Z0-9]*)', text))
|
||||
for name in sorted(used, key=lambda n: (-len(n), n)):
|
||||
if name not in fa:
|
||||
print(f"Warning: \\fa{name} not found in header", file=sys.stderr)
|
||||
continue
|
||||
ch = fa[name]
|
||||
text = text.replace(f'\\fa{name}{{}}~', f'{ch} ')
|
||||
text = text.replace(f'\\fa{name}{{}}', ch)
|
||||
text = text.replace(f'\\fa{name}~', f'{ch} ')
|
||||
text = text.replace(f'\\fa{name}', ch)
|
||||
return text
|
||||
|
||||
|
||||
def pre_sed(text):
|
||||
text = text.replace('\\menu[,]', '')
|
||||
text = text.replace('\\keys', '')
|
||||
text = text.replace('\\ctrl', 'Ctrl')
|
||||
text = text.replace('\\shift', 'Shift')
|
||||
text = text.replace('\\Alt', 'Alt')
|
||||
text = text.replace('\\del', 'Delete')
|
||||
return text
|
||||
|
||||
|
||||
def post_sed(text):
|
||||
for m in ('LMB', 'MMB', 'RMB', 'Scroll'):
|
||||
text = text.replace(f'\\{m}{{}}~', '')
|
||||
text = text.replace('\\textsigma', 'σ')
|
||||
for l in 'abc':
|
||||
text = text.replace(f'@\\circled{{{l}}}@', f'({l})')
|
||||
for l in 'abc':
|
||||
text = text.replace(f'\\circled{{{l}}}', f'({l})')
|
||||
text = text.replace('@\\ldots@', '…')
|
||||
text = re.sub(r',?escapeinside=\{\}\{\}', '', text)
|
||||
for label, name in NAMES:
|
||||
text = text.replace(f'\\nameref{{{label}}}', name)
|
||||
return text
|
||||
|
||||
|
||||
def bclogo2quote(text):
|
||||
"""Convert \\begin{bclogo} admonition blocks to \\begin{quote}\\textbf{PREFIX:...}."""
|
||||
prefix = {'bcbombe': 'IMPORTANT', 'bcattention': 'CAUTION', 'bclampe': 'TIP'}
|
||||
out = []
|
||||
in_bclogo = False
|
||||
bclogo_type = ''
|
||||
for line in text.split('\n'):
|
||||
if not in_bclogo and r'\begin{bclogo}[' in line:
|
||||
in_bclogo = True
|
||||
bclogo_type = ''
|
||||
continue
|
||||
if in_bclogo and 'logo=' in line:
|
||||
if r'\bcbombe' in line:
|
||||
bclogo_type = 'bcbombe'
|
||||
elif r'\bcattention' in line:
|
||||
bclogo_type = 'bcattention'
|
||||
elif r'\bclampe' in line:
|
||||
bclogo_type = 'bclampe'
|
||||
elif r'\bcquestion' in line:
|
||||
bclogo_type = 'bcquestion'
|
||||
continue
|
||||
if in_bclogo and ('noborder' in line or 'couleur' in line):
|
||||
continue
|
||||
if in_bclogo:
|
||||
title = re.sub(r'\}.*$', '', re.sub(r'^[ \t]*\]?\{', '', line))
|
||||
out.append(f"\\begin{{quote}}\\textbf{{{prefix.get(bclogo_type, 'NOTE')}:{title}}}\\par")
|
||||
in_bclogo = False
|
||||
continue
|
||||
if r'\end{bclogo}' in line:
|
||||
out.append('\\end{quote}')
|
||||
continue
|
||||
out.append(line)
|
||||
return '\n'.join(out)
|
||||
|
||||
|
||||
def run_pandoc(pandoc, filter_path, tex):
|
||||
args = [
|
||||
pandoc,
|
||||
'--wrap=none',
|
||||
'--reference-location=block',
|
||||
'--number-sections',
|
||||
'-f', 'latex',
|
||||
'-t', PANDOC_MARKDOWN,
|
||||
'-s',
|
||||
'-L', filter_path,
|
||||
'-',
|
||||
]
|
||||
proc = subprocess.run(args, input=tex.encode('utf-8'), capture_output=True)
|
||||
if proc.returncode != 0:
|
||||
sys.stderr.write(proc.stderr.decode('utf-8', 'replace'))
|
||||
sys.exit(proc.returncode)
|
||||
return proc.stdout.decode('utf-8')
|
||||
|
||||
|
||||
def tablecaption(text):
|
||||
"""Pandoc emits table captions as ": ..." lines; italicize them instead."""
|
||||
out = []
|
||||
incap = False
|
||||
for line in text.split('\n'):
|
||||
if not incap and line.startswith(': '):
|
||||
incap = True
|
||||
line = '_' + line[2:]
|
||||
if incap and not line.endswith('\\'):
|
||||
out.append(line + '_')
|
||||
incap = False
|
||||
continue
|
||||
out.append(line)
|
||||
return '\n'.join(out)
|
||||
|
||||
|
||||
def admonitions(text):
|
||||
for kind in ('IMPORTANT', 'TIP', 'CAUTION', 'NOTE'):
|
||||
text = re.sub(
|
||||
rf'^> \*\*{kind}:([^*]*)\*\*',
|
||||
f'> [!{kind}]\n> **\\1**',
|
||||
text,
|
||||
flags=re.M,
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
def icon_explain(text, names):
|
||||
"""Append an icon legend to each top-level section containing FA icons."""
|
||||
lines = text.split('\n')
|
||||
chunk_starts = [i for i, line in enumerate(lines) if line.startswith('#')]
|
||||
if chunk_starts and chunk_starts[0] > 0:
|
||||
chunk_starts.insert(0, 0)
|
||||
out = []
|
||||
for ci, start in enumerate(chunk_starts):
|
||||
end = chunk_starts[ci + 1] if ci + 1 < len(chunk_starts) else len(lines)
|
||||
chunk = lines[start:end]
|
||||
icons = []
|
||||
seen = set()
|
||||
for line in chunk:
|
||||
for ch in line:
|
||||
cp = ord(ch)
|
||||
if 0xE000 <= cp <= 0xF8FF and ch not in seen:
|
||||
seen.add(ch)
|
||||
icons.append(ch)
|
||||
out.extend(chunk)
|
||||
if icons:
|
||||
out.append('')
|
||||
out.append('-----')
|
||||
out.append('')
|
||||
for ch in icons:
|
||||
out.append(f"{ch} - {names.get(ch, f'Unknown(U+{ord(ch):04X})')} icon")
|
||||
out.append('')
|
||||
return '\n'.join(out)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description='Generate the Markdown user manual')
|
||||
ap.add_argument('--tex', required=True, help='path to tracy.tex')
|
||||
ap.add_argument('--out', required=True, help='path of the generated tracy.md')
|
||||
ap.add_argument('--pandoc', required=True, help='path to the pandoc executable')
|
||||
ap.add_argument('--icons', required=True, help='path to IconsFontAwesome7.h')
|
||||
args = ap.parse_args()
|
||||
|
||||
fa, names = parse_icon_header(args.icons)
|
||||
|
||||
with open(args.tex, encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
text = pre_sed(text)
|
||||
text = substitute_icons(text, fa)
|
||||
text = post_sed(text)
|
||||
text = bclogo2quote(text)
|
||||
|
||||
md = run_pandoc(
|
||||
args.pandoc,
|
||||
os.path.join(os.path.dirname(os.path.abspath(args.tex)), 'filter.lua'),
|
||||
text,
|
||||
)
|
||||
|
||||
md = tablecaption(md)
|
||||
md = admonitions(md)
|
||||
md = icon_explain(md, names)
|
||||
|
||||
out_dir = os.path.dirname(os.path.abspath(args.out))
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
with open(args.out, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(md)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cp -f tracy.tex _tmp.tex
|
||||
sed -i -e 's@\\menu\[,\]@@g' _tmp.tex
|
||||
sed -i -e 's@\\keys@@g' _tmp.tex
|
||||
sed -i -e 's@\\ctrl@Ctrl@g' _tmp.tex
|
||||
sed -i -e 's@\\shift@Shift@g' _tmp.tex
|
||||
sed -i -e 's@\\Alt@Alt@g' _tmp.tex
|
||||
sed -i -e 's@\\del@Delete@g' _tmp.tex
|
||||
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome7.h _tmp.tex
|
||||
sed -i -e 's@\\LMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\textsigma@σ@g' _tmp.tex
|
||||
|
||||
# Resolve \circled{} markers and lstlisting escapeinside (@...@) snippets, which
|
||||
# pandoc would otherwise emit verbatim or drop, to their Unicode equivalents.
|
||||
sed -i -e 's|@\\circled{a}@|(a)|g' -e 's|@\\circled{b}@|(b)|g' -e 's|@\\circled{c}@|(c)|g' _tmp.tex
|
||||
sed -i -e 's|\\circled{a}|(a)|g' -e 's|\\circled{b}|(b)|g' -e 's|\\circled{c}|(c)|g' _tmp.tex
|
||||
sed -i -e 's|@\\ldots@|…|g' _tmp.tex
|
||||
# pandoc doens't recognize this lstlisting option and emits it verbatim
|
||||
sed -i -e 's|,\?escapeinside={}{}||g' _tmp.tex
|
||||
|
||||
sed -i -e 's@\\nameref{quicklook}@A quick look at Tracy Profiler@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{firststeps}@First steps@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{client}@Client markup@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{capturing}@Capturing the data@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{analyzingdata}@Analyzing captured data@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{tracyassist}@Tracy Assist@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{csvexport}@Exporting zone statistics to CSV@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{importingdata}@Importing external profiling data@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{configurationfiles}@Configuration files@g' _tmp.tex
|
||||
|
||||
awk -f bclogo2quote.awk _tmp.tex > _tmp_quoted.tex
|
||||
mv _tmp_quoted.tex _tmp.tex
|
||||
|
||||
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -t 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables' -s _tmp.tex -o tracy.md
|
||||
|
||||
awk -f tablecaption.awk tracy.md > _tmp_caption.md
|
||||
mv _tmp_caption.md tracy.md
|
||||
|
||||
sed -i -e 's/^> \*\*IMPORTANT:\([^*]*\)\*\*/> [!IMPORTANT]\
|
||||
> **\1**/' tracy.md
|
||||
sed -i -e 's/^> \*\*TIP:\([^*]*\)\*\*/> [!TIP]\
|
||||
> **\1**/' tracy.md
|
||||
sed -i -e 's/^> \*\*CAUTION:\([^*]*\)\*\*/> [!CAUTION]\
|
||||
> **\1**/' tracy.md
|
||||
sed -i -e 's/^> \*\*NOTE:\([^*]*\)\*\*/> [!NOTE]\
|
||||
> **\1**/' tracy.md
|
||||
|
||||
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome7.h tracy.md
|
||||
|
||||
rm -f _tmp.tex
|
||||
@@ -1,16 +0,0 @@
|
||||
# Pandoc emits table captions as a line beginning with ": ", which GitHub
|
||||
# renders literally instead of as a caption. Strip the marker and italicize
|
||||
# the caption instead. Captions may span several physical lines when they
|
||||
# contain a hard line break (a trailing backslash). Underscores are used for
|
||||
# the emphasis so captions that already contain "*...*" markup are left intact.
|
||||
!incap && /^: / {
|
||||
incap = 1
|
||||
$0 = "_" substr($0, 3)
|
||||
}
|
||||
incap && !/\\$/ {
|
||||
print $0 "_"
|
||||
incap = 0
|
||||
next
|
||||
}
|
||||
incap { print; next }
|
||||
{ print }
|
||||
5389
manual/tracy.md
5389
manual/tracy.md
File diff suppressed because it is too large
Load Diff
@@ -804,6 +804,8 @@ Suppose for some reason you want to use another port\footnote{For example, other
|
||||
|
||||
If a custom port is not specified and the default listening port is already occupied, the profiler will automatically try to listen on a number of other ports.
|
||||
|
||||
Since the port in use may differ from the default one, the client application may query the actual data connection port using the \texttt{TracyPort} macro. It returns the port number once the listening socket has been bound, or zero otherwise.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
@@ -3329,6 +3331,43 @@ Each client is captured to a separate file in the output directory, named accord
|
||||
|
||||
Press \keys{\ctrl + C} to stop discovery and gracefully shut down all active captures. Each capture thread will finish writing its trace file before the daemon exits.
|
||||
|
||||
\subsection{External monitoring}
|
||||
\label{externalmonitoring}
|
||||
|
||||
Sometimes, the application you want to profile cannot be built with the Tracy client. This is often the case with third-party or closed-source software, running system services, or programs written in languages without Tracy bindings. For such situations, there is the \texttt{tracy-monitor} utility, contained in the \texttt{monitor} directory. The monitor profiles a process from the outside, without modifying it in any way, on Linux (x86-64 and ARM64).
|
||||
|
||||
The monitor samples the target in the same manner as the compiled-in client (see section~\ref{sampling}), so a capture contains:
|
||||
|
||||
\begin{itemize}
|
||||
\item Sampled call stacks of every thread of the target, resolved to function names, source file and line, including inlined functions. As with regular call stack sampling (chapter~\ref{collectingcallstacks}), the target's executable and shared libraries should carry symbol information for the resolution to work, and programs compiled without frame pointers (typical of optimized builds) will yield call stacks truncated to the innermost function.
|
||||
\item Machine code of the profiled functions, so that the disassembly view (section~\ref{executableretrieval}) is fully functional, exactly as in a regular capture.
|
||||
\item System-wide CPU usage plots.
|
||||
\item Depending on your privileges and system configuration: kernel frames in the call stacks, context switches and wait stacks (sections~\ref{contextswitches} and \ref{waitstacks}), per-thread CPU time, power consumption (RAPL) plots, and hardware vsync events.
|
||||
\end{itemize}
|
||||
|
||||
The monitor can either start the program you want to profile, or attach to an already running one:
|
||||
|
||||
\begin{lstlisting}[language=sh]
|
||||
$ tracy-monitor ./my_program arg1 arg2
|
||||
$ tracy-monitor -p 12345
|
||||
$ tracy-monitor -n my_program
|
||||
\end{lstlisting}
|
||||
|
||||
In the first form, the monitor launches the program and the profiling begins before its first instruction executes, so the entire lifetime of the program is covered. In this mode, stopping the monitor (\keys{\ctrl + C}) also terminates the profiled program.
|
||||
|
||||
The \texttt{-p} option attaches to an already running process by its ID, and \texttt{-n} does the same by its name (the name as known to the operating system, truncated to 15 characters; if several processes share the name, the monitor lists their IDs so you can pick one with \texttt{-p}). In attach mode, the target is left running when the monitor is stopped.
|
||||
|
||||
The following options are also available:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{-{}-hz N} -- sampling frequency in Hz (default: 10000, range 1--1000000).
|
||||
\item \texttt{-{}-port N} -- the port on which the monitor accepts connections from the profiler (default: 8086).
|
||||
\end{itemize}
|
||||
|
||||
After starting, the monitor prints a short report listing the data sources that are actually active in your environment (sampling rate, kernel frames, context switches, hardware sampling statistics, power, vsync), together with the reason for any that are unavailable.
|
||||
|
||||
The monitor can also be built with the \texttt{TRACY\_ON\_DEMAND} define (section~\ref{ondemand}): in that case, samples are captured only while a Tracy server is connected, and samples taken before the first connection are discarded.
|
||||
|
||||
\subsection{Merging trace files}
|
||||
\label{mergingtraces}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('tracy', ['cpp'], version: run_command('sh', files('extra/tracy-version.sh'), 'public/common/TracyVersion.hpp', check: true).stdout().strip(), meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
|
||||
project('tracy', ['cpp'], version: run_command(find_program('python3'), files('extra/tracy-version.py'), 'public/common/TracyVersion.hpp', check: true).stdout().strip(), meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
|
||||
|
||||
# internal compiler flags
|
||||
tracy_compile_args = []
|
||||
@@ -227,10 +227,16 @@ tracy_compile_args += tracy_common_args
|
||||
|
||||
tracy_deps = [dependency('threads')] + tracy_public_deps
|
||||
|
||||
tracy_link_args = []
|
||||
if host_machine.system() == 'windows' and (compiler.get_id() == 'gcc' or compiler.get_id() == 'clang')
|
||||
tracy_link_args += ['-lws2_32', '-ldbghelp', '-lsecur32']
|
||||
endif
|
||||
|
||||
tracy = library('tracy', tracy_src, tracy_header_files,
|
||||
dependencies : tracy_deps,
|
||||
include_directories : tracy_public_include_dirs,
|
||||
cpp_args : tracy_compile_args,
|
||||
link_args : tracy_link_args,
|
||||
override_options : override_options,
|
||||
install : true)
|
||||
|
||||
|
||||
@@ -12,10 +12,17 @@ project(
|
||||
VERSION ${TRACY_VERSION_STRING}
|
||||
)
|
||||
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
message(FATAL_ERROR "tracy-monitor is Linux-only")
|
||||
endif()
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|aarch64|arm64)$")
|
||||
message(FATAL_ERROR "tracy-monitor supports x86_64 and aarch64")
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
option(TRACY_ON_DEMAND "On-demand profiling: record samples only while a Tracy server is connected" OFF)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
|
||||
|
||||
set(PROGRAM_FILES
|
||||
@@ -31,6 +38,9 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
TRACY_MANUAL_LIFETIME
|
||||
TRACY_NO_FRAME_IMAGE
|
||||
)
|
||||
if(TRACY_ON_DEMAND)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE TRACY_ON_DEMAND)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Threads::Threads
|
||||
${CMAKE_DL_LIBS}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/manual.cmake)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(embed
|
||||
@@ -48,18 +49,26 @@ ExternalProject_Add(embed
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Embeds a file into the profiler as a compressed C array (data/NAME.cpp/.hpp).
|
||||
# FILE is relative to the current source directory; with BUILD, relative to
|
||||
# the current build directory. With TEXT, line endings are normalized to unix.
|
||||
function(Embed LIST NAME FILE)
|
||||
cmake_parse_arguments(EMBED "TEXT" "" "" ${ARGN})
|
||||
cmake_parse_arguments(EMBED "TEXT;BUILD" "" "" ${ARGN})
|
||||
if(EMBED_TEXT)
|
||||
set(EMBED_FLAGS -t)
|
||||
else()
|
||||
set(EMBED_FLAGS)
|
||||
endif()
|
||||
if(EMBED_BUILD)
|
||||
set(EMBED_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/${FILE})
|
||||
else()
|
||||
set(EMBED_SOURCE ${CMAKE_CURRENT_LIST_DIR}/${FILE})
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT data/${NAME}.cpp data/${NAME}.hpp
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory data
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${EMBED_FLAGS} ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
|
||||
DEPENDS embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${EMBED_FLAGS} ${NAME} ${EMBED_SOURCE} data/${NAME}
|
||||
DEPENDS embed ${EMBED_SOURCE}
|
||||
)
|
||||
list(APPEND ${LIST} data/${NAME}.cpp)
|
||||
return(PROPAGATE ${LIST})
|
||||
@@ -173,8 +182,23 @@ Embed(PROFILER_FILES FontBold src/font/Roboto-Bold.ttf)
|
||||
Embed(PROFILER_FILES FontItalic src/font/Roboto-Italic.ttf)
|
||||
Embed(PROFILER_FILES FontBoldItalic src/font/Roboto-BoldItalic.ttf)
|
||||
Embed(PROFILER_FILES FontEmoji src/font/NotoEmoji-Regular.ttf)
|
||||
set(MANUAL_MD ${CMAKE_CURRENT_BINARY_DIR}/manual/tracy.md)
|
||||
add_custom_command(
|
||||
OUTPUT ${MANUAL_MD}
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/../manual/latex2md.py
|
||||
--tex ${CMAKE_CURRENT_LIST_DIR}/../manual/tracy.tex
|
||||
--out ${MANUAL_MD}
|
||||
--pandoc ${TRACY_PANDOC}
|
||||
--icons ${CMAKE_CURRENT_LIST_DIR}/src/profiler/IconsFontAwesome7.h
|
||||
DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/../manual/tracy.tex
|
||||
${CMAKE_CURRENT_LIST_DIR}/../manual/filter.lua
|
||||
${CMAKE_CURRENT_LIST_DIR}/../manual/latex2md.py
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/profiler/IconsFontAwesome7.h
|
||||
COMMENT "Generating user manual"
|
||||
VERBATIM)
|
||||
|
||||
Embed(PROFILER_FILES Manual ../manual/tracy.md TEXT)
|
||||
Embed(PROFILER_FILES Manual manual/tracy.md TEXT BUILD)
|
||||
|
||||
Embed(PROFILER_FILES Text100Million src/achievements/100Million.md TEXT)
|
||||
Embed(PROFILER_FILES TextConnectToClient src/achievements/ConnectToClient.md TEXT)
|
||||
|
||||
@@ -346,7 +346,13 @@ void DestroyImageCaches()
|
||||
}
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
# include <errno.h>
|
||||
# include <fcntl.h>
|
||||
# include <signal.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/uio.h>
|
||||
# include <unistd.h>
|
||||
|
||||
static constexpr uint32_t ExtPT_LOAD = 1;
|
||||
|
||||
@@ -355,22 +361,43 @@ struct ExternalImageEntry
|
||||
uint64_t startAddress;
|
||||
uint64_t endAddress;
|
||||
uint64_t loadBias;
|
||||
uint64_t mapsOffset;
|
||||
char* path;
|
||||
backtrace_state* btState;
|
||||
bool btAttempted;
|
||||
};
|
||||
|
||||
static FastVector<ExternalImageEntry>* s_extImages = nullptr;
|
||||
static pid_t s_externalPid = 0;
|
||||
static bool s_extImagesSorted = true;
|
||||
static pid_t s_externalTargetPid = 0;
|
||||
static char s_externalTargetName[64] = {};
|
||||
static uint64_t s_externalTargetExeMtime = 0;
|
||||
// Wall-clock second of the last /proc/<pid>/maps re-parse. Used to rate-limit
|
||||
// refreshes so addresses that never resolve (JIT, vDSO, stack) do not trigger
|
||||
// a full re-parse on every symbolization.
|
||||
static int64_t s_lastMapsRefresh = 0;
|
||||
|
||||
static uint64_t ReadElfMinLoadVaddr( const char* path )
|
||||
static int MakeExternalTargetPath( char* buf, size_t bufSize, const char* targetPath )
|
||||
{
|
||||
int fd = open( path, O_RDONLY );
|
||||
const int n = snprintf( buf, bufSize, "/proc/%d/root%s", (int)s_externalTargetPid, targetPath );
|
||||
return ( n < 0 || (size_t)n >= bufSize ) ? -1 : n;
|
||||
}
|
||||
|
||||
static int OpenExternalImageFile( const char* path, uint64_t mapStart, uint64_t mapEnd )
|
||||
{
|
||||
char rootPath[4096];
|
||||
if( MakeExternalTargetPath( rootPath, sizeof( rootPath ), path ) >= 0 )
|
||||
{
|
||||
const int fd = open( rootPath, O_RDONLY );
|
||||
if( fd >= 0 ) return fd;
|
||||
}
|
||||
char mfPath[80];
|
||||
snprintf( mfPath, sizeof( mfPath ), "/proc/%d/map_files/%lx-%lx", (int)s_externalTargetPid, (unsigned long)mapStart, (unsigned long)mapEnd );
|
||||
return open( mfPath, O_RDONLY );
|
||||
}
|
||||
|
||||
static uint64_t ReadElfMinLoadVaddr( const char* path, uint64_t mapStart, uint64_t mapEnd )
|
||||
{
|
||||
const int fd = OpenExternalImageFile( path, mapStart, mapEnd );
|
||||
if( fd < 0 ) return UINT64_MAX;
|
||||
|
||||
elf_ehdr ehdr;
|
||||
@@ -411,6 +438,46 @@ static uint64_t ReadElfMinLoadVaddr( const char* path )
|
||||
return minVaddr;
|
||||
}
|
||||
|
||||
static uint64_t ReadElfSegmentLoadBias( const char* path, uint64_t start, uint64_t end, uint64_t offset, uint64_t pageSize )
|
||||
{
|
||||
const int fd = OpenExternalImageFile( path, start, end );
|
||||
if( fd < 0 ) return UINT64_MAX;
|
||||
|
||||
elf_ehdr ehdr;
|
||||
if( read( fd, &ehdr, sizeof( ehdr ) ) != sizeof( ehdr ) ||
|
||||
ehdr.e_ident[0] != 0x7f || ehdr.e_ident[1] != 'E' ||
|
||||
ehdr.e_ident[2] != 'L' || ehdr.e_ident[3] != 'F' ||
|
||||
ehdr.e_ident[4] != 2 ||
|
||||
ehdr.e_phoff == 0 || ehdr.e_phnum == 0 )
|
||||
{
|
||||
close( fd );
|
||||
return UINT64_MAX;
|
||||
}
|
||||
|
||||
if( lseek( fd, ehdr.e_phoff, SEEK_SET ) == (off_t)-1 )
|
||||
{
|
||||
close( fd );
|
||||
return UINT64_MAX;
|
||||
}
|
||||
|
||||
uint64_t loadBias = UINT64_MAX;
|
||||
for( uint16_t i = 0; i < ehdr.e_phnum; i++ )
|
||||
{
|
||||
elf_phdr phdr;
|
||||
if( read( fd, &phdr, sizeof( phdr ) ) != sizeof( phdr ) ) break;
|
||||
if( phdr.p_type != ExtPT_LOAD ) continue;
|
||||
const uint64_t vaddr = static_cast<uint64_t>( phdr.p_vaddr );
|
||||
if( static_cast<uint64_t>( phdr.p_offset ) - ( vaddr & ( pageSize - 1 ) ) == offset )
|
||||
{
|
||||
loadBias = start - ( vaddr & ~( pageSize - 1 ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close( fd );
|
||||
return loadBias;
|
||||
}
|
||||
|
||||
static void ParseExternalProcMaps( pid_t pid )
|
||||
{
|
||||
char mapPath[64];
|
||||
@@ -418,6 +485,8 @@ static void ParseExternalProcMaps( pid_t pid )
|
||||
FILE* f = fopen( mapPath, "r" );
|
||||
if( !f ) return;
|
||||
|
||||
FastVector<ExternalImageEntry> fresh( 64 );
|
||||
|
||||
char line[1024];
|
||||
while( fgets( line, sizeof( line ), f ) )
|
||||
{
|
||||
@@ -434,46 +503,48 @@ static void ParseExternalProcMaps( pid_t pid )
|
||||
while( *pathname == ' ' || *pathname == '\t' ) pathname++;
|
||||
size_t plen = strlen( pathname );
|
||||
while( plen > 0 && ( pathname[plen-1] == '\n' || pathname[plen-1] == '\r' ) ) plen--;
|
||||
if( plen >= 10 && strncmp( pathname + plen - 10, " (deleted)", 10 ) == 0 ) plen -= 10;
|
||||
pathname[plen] = '\0';
|
||||
|
||||
if( plen == 0 || pathname[0] != '/' ) continue;
|
||||
if( std::find_if( s_extImages->begin(), s_extImages->end(), [start]( const ExternalImageEntry& e ) { return e.startAddress == start; } ) != s_extImages->end() ) continue;
|
||||
|
||||
uint64_t minVaddr = ReadElfMinLoadVaddr( pathname );
|
||||
uint64_t loadBias;
|
||||
if( minVaddr == UINT64_MAX )
|
||||
// list is sorted by start address
|
||||
auto it = std::lower_bound( s_extImages->begin(), s_extImages->end(), start,
|
||||
[]( const ExternalImageEntry& e, uint64_t a ) { return e.startAddress > a; } );
|
||||
if( it != s_extImages->end() && it->startAddress == start
|
||||
&& it->endAddress == end && it->mapsOffset == offset
|
||||
&& strcmp( it->path, pathname ) == 0 )
|
||||
{
|
||||
loadBias = start;
|
||||
fresh.push_next()[0] = *it;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
uint64_t pageSize = sysconf( _SC_PAGESIZE );
|
||||
uint64_t loadBias = ReadElfSegmentLoadBias( pathname, start, end, offset, pageSize );
|
||||
if( loadBias == UINT64_MAX )
|
||||
{
|
||||
uint64_t pageSize = sysconf( _SC_PAGESIZE );
|
||||
uint64_t alignedVaddr = minVaddr & ~(pageSize - 1);
|
||||
loadBias = start - alignedVaddr - offset;
|
||||
uint64_t minVaddr = ReadElfMinLoadVaddr( pathname, start, end );
|
||||
loadBias = ( minVaddr == UINT64_MAX ) ? start : start - ( minVaddr & ~( pageSize - 1 ) ) - offset;
|
||||
}
|
||||
|
||||
ExternalImageEntry entry = {
|
||||
.startAddress = start,
|
||||
.endAddress = end,
|
||||
.loadBias = loadBias,
|
||||
.mapsOffset = offset,
|
||||
.path = (char*)tracy_malloc( plen + 1 ),
|
||||
.btState = nullptr,
|
||||
.btAttempted = false
|
||||
};
|
||||
memcpy( entry.path, pathname, plen + 1 );
|
||||
|
||||
s_extImagesSorted = false;
|
||||
s_extImages->push_next()[0] = entry;
|
||||
fresh.push_next()[0] = entry;
|
||||
}
|
||||
|
||||
fclose( f );
|
||||
|
||||
if( !s_extImagesSorted )
|
||||
{
|
||||
std::sort( s_extImages->begin(), s_extImages->end(),
|
||||
[]( const ExternalImageEntry& a, const ExternalImageEntry& b ) { return a.startAddress > b.startAddress; } );
|
||||
s_extImagesSorted = true;
|
||||
}
|
||||
std::sort( fresh.begin(), fresh.end(),
|
||||
[]( const ExternalImageEntry& a, const ExternalImageEntry& b ) { return a.startAddress > b.startAddress; } );
|
||||
s_extImages->swap( fresh );
|
||||
}
|
||||
|
||||
static const ExternalImageEntry* FindExternalImage( uint64_t address )
|
||||
@@ -495,13 +566,13 @@ static const ExternalImageEntry* FindExternalImageRefresh( uint64_t address )
|
||||
auto entry = FindExternalImage( address );
|
||||
if( entry ) return entry;
|
||||
|
||||
if( s_externalPid != 0 )
|
||||
if( s_externalTargetPid != 0 )
|
||||
{
|
||||
const int64_t now = (int64_t)time( nullptr );
|
||||
if( now != s_lastMapsRefresh )
|
||||
{
|
||||
s_lastMapsRefresh = now;
|
||||
ParseExternalProcMaps( s_externalPid );
|
||||
ParseExternalProcMaps( s_externalTargetPid );
|
||||
return FindExternalImage( address );
|
||||
}
|
||||
}
|
||||
@@ -517,40 +588,37 @@ static backtrace_state* GetExternalBtState( const ExternalImageEntry* entry )
|
||||
auto* e = const_cast<ExternalImageEntry*>( entry );
|
||||
if( e->btAttempted ) return e->btState;
|
||||
e->btAttempted = true;
|
||||
e->btState = backtrace_create_state_for_file( e->path, 0, ExternalBacktraceErrorCb, nullptr );
|
||||
const size_t rootPathSize = strlen( e->path ) + 32;
|
||||
char* rootPath = (char*)tracy_malloc( rootPathSize );
|
||||
const char* statePath = nullptr;
|
||||
char mfPath[80];
|
||||
if( MakeExternalTargetPath( rootPath, rootPathSize, e->path ) >= 0 )
|
||||
{
|
||||
const int probe = open( rootPath, O_RDONLY );
|
||||
if( probe >= 0 )
|
||||
{
|
||||
close( probe );
|
||||
statePath = rootPath;
|
||||
}
|
||||
}
|
||||
if( !statePath )
|
||||
{
|
||||
snprintf( mfPath, sizeof( mfPath ), "/proc/%d/map_files/%lx-%lx", (int)s_externalTargetPid, (unsigned long)e->startAddress, (unsigned long)e->endAddress );
|
||||
const int probe = open( mfPath, O_RDONLY );
|
||||
if( probe >= 0 )
|
||||
{
|
||||
close( probe );
|
||||
statePath = mfPath;
|
||||
}
|
||||
}
|
||||
if( statePath )
|
||||
{
|
||||
e->btState = backtrace_create_state_for_file( statePath, 0, ExternalBacktraceErrorCb, nullptr );
|
||||
}
|
||||
tracy_free( rootPath );
|
||||
return e->btState;
|
||||
}
|
||||
|
||||
struct ExternalResolveData
|
||||
{
|
||||
const char* name;
|
||||
const char* file;
|
||||
uint32_t line;
|
||||
int count;
|
||||
};
|
||||
|
||||
static int ExternalPcInfoCb( void* data, uintptr_t pc, uintptr_t lowaddr, const char* filename, int lineno, const char* function )
|
||||
{
|
||||
auto& rd = *(ExternalResolveData*)data;
|
||||
|
||||
if( rd.count > 0 ) return 1;
|
||||
rd.count++;
|
||||
|
||||
if( function )
|
||||
{
|
||||
const char* demangled = ___tracy_demangle( function );
|
||||
rd.name = demangled ? demangled : function;
|
||||
}
|
||||
else
|
||||
{
|
||||
rd.name = nullptr;
|
||||
}
|
||||
|
||||
rd.file = filename;
|
||||
rd.line = lineno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ExternalSymInfoData
|
||||
{
|
||||
@@ -567,18 +635,143 @@ static void ExternalSymInfoCb( void* data, uintptr_t pc, const char* symname, ui
|
||||
sd.symsize = symsize;
|
||||
}
|
||||
|
||||
void InitExternalImageCache( pid_t pid )
|
||||
bool InitExternalTarget( pid_t targetPid )
|
||||
{
|
||||
s_externalPid = pid;
|
||||
if( kill( targetPid, 0 ) != 0 )
|
||||
{
|
||||
fprintf( stderr, "Tracy: cannot profile pid %d: %s\n", (int)targetPid, strerror( errno ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
char path[64];
|
||||
snprintf( path, sizeof( path ), "/proc/%d/comm", (int)targetPid );
|
||||
FILE* f = fopen( path, "r" );
|
||||
if( !f )
|
||||
{
|
||||
fprintf( stderr, "Tracy: cannot read %s: %s\n", path, strerror( errno ) );
|
||||
return false;
|
||||
}
|
||||
char comm[64] = {};
|
||||
if( !fgets( comm, sizeof( comm ), f ) )
|
||||
{
|
||||
fclose( f );
|
||||
fprintf( stderr, "Tracy: cannot read %s: %s\n", path, strerror( errno ) );
|
||||
return false;
|
||||
}
|
||||
fclose( f );
|
||||
size_t len = strlen( comm );
|
||||
while( len > 0 && ( comm[len-1] == '\n' || comm[len-1] == '\r' ) ) len--;
|
||||
if( len >= sizeof( s_externalTargetName ) ) len = sizeof( s_externalTargetName ) - 1;
|
||||
memcpy( s_externalTargetName, comm, len );
|
||||
s_externalTargetName[len] = '\0';
|
||||
|
||||
snprintf( path, sizeof( path ), "/proc/%d/exe", (int)targetPid );
|
||||
{
|
||||
const int exeFd = open( path, O_RDONLY );
|
||||
if( exeFd < 0 )
|
||||
{
|
||||
fprintf( stderr, "Tracy: cannot read %s: %s\n", path, strerror( errno ) );
|
||||
return false;
|
||||
}
|
||||
struct stat exeSt;
|
||||
if( fstat( exeFd, &exeSt ) == 0 ) s_externalTargetExeMtime = (uint64_t)exeSt.st_mtime;
|
||||
close( exeFd );
|
||||
}
|
||||
|
||||
s_externalTargetPid = targetPid;
|
||||
if( !s_extImages )
|
||||
{
|
||||
s_extImages = (FastVector<ExternalImageEntry>*)tracy_malloc( sizeof( FastVector<ExternalImageEntry> ) );
|
||||
new (s_extImages) FastVector<ExternalImageEntry>( 64 );
|
||||
}
|
||||
ParseExternalProcMaps( pid );
|
||||
ParseExternalProcMaps( targetPid );
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
uint32_t GetExternalTargetPid()
|
||||
{
|
||||
return (uint32_t)s_externalTargetPid;
|
||||
}
|
||||
|
||||
const char* GetExternalTargetName()
|
||||
{
|
||||
return s_externalTargetName;
|
||||
}
|
||||
|
||||
uint64_t GetExternalTargetExeTime()
|
||||
{
|
||||
return s_externalTargetExeMtime;
|
||||
}
|
||||
|
||||
static bool FindExternalMapping( pid_t pid, uint64_t addr, uint64_t& mapStart, uint64_t& mapEnd, uint64_t& fileOff, char* path, size_t pathSize )
|
||||
{
|
||||
char mapPath[64];
|
||||
snprintf( mapPath, sizeof( mapPath ), "/proc/%d/maps", (int)pid );
|
||||
FILE* f = fopen( mapPath, "r" );
|
||||
if( !f ) return false;
|
||||
|
||||
bool found = false;
|
||||
char line[1024];
|
||||
while( fgets( line, sizeof( line ), f ) )
|
||||
{
|
||||
uint64_t start, end, offset;
|
||||
uint32_t devMaj, devMin;
|
||||
uint64_t inode;
|
||||
char perms[8];
|
||||
int consumed = 0;
|
||||
|
||||
if( sscanf( line, "%lx-%lx %7s %lx %x:%x %lu %n", &start, &end, perms, &offset, &devMaj, &devMin, &inode, &consumed ) < 7 ) continue;
|
||||
if( !strchr( perms, 'x' ) ) continue;
|
||||
if( addr < start || addr >= end ) continue;
|
||||
|
||||
char* pathname = line + consumed;
|
||||
while( *pathname == ' ' || *pathname == '\t' ) pathname++;
|
||||
size_t plen = strlen( pathname );
|
||||
while( plen > 0 && ( pathname[plen-1] == '\n' || pathname[plen-1] == '\r' ) ) plen--;
|
||||
pathname[plen] = '\0';
|
||||
if( plen >= 10 && strncmp( pathname + plen - 10, " (deleted)", 10 ) == 0 ) plen -= 10;
|
||||
if( plen == 0 || pathname[0] != '/' ) continue;
|
||||
if( plen >= pathSize ) plen = pathSize - 1;
|
||||
memcpy( path, pathname, plen );
|
||||
path[plen] = '\0';
|
||||
|
||||
mapStart = start;
|
||||
mapEnd = end;
|
||||
fileOff = offset + ( addr - start );
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
fclose( f );
|
||||
return found;
|
||||
}
|
||||
|
||||
size_t ReadExternalTargetMemory( uint64_t addr, uint32_t size, char* buf )
|
||||
{
|
||||
const auto pid = (pid_t)GetExternalTargetPid();
|
||||
if( pid == 0 || size == 0 ) return 0;
|
||||
|
||||
struct iovec local = { buf, size };
|
||||
struct iovec remote = { (void*)addr, size };
|
||||
if( process_vm_readv( pid, &local, 1, &remote, 1, 0 ) == (ssize_t)size ) return size;
|
||||
|
||||
uint64_t mapStart = 0, mapEnd = 0, fileOff = 0;
|
||||
char path[1024] = {};
|
||||
if( FindExternalMapping( pid, addr, mapStart, mapEnd, fileOff, path, sizeof( path ) ) && addr + size <= mapEnd )
|
||||
{
|
||||
const int fd = OpenExternalImageFile( path, mapStart, mapEnd );
|
||||
if( fd >= 0 )
|
||||
{
|
||||
const ssize_t rd = pread( fd, buf, size, (off_t)fileOff );
|
||||
close( fd );
|
||||
if( rd == (ssize_t)size ) return size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // TRACY_HAS_EXTERNAL_TARGET
|
||||
|
||||
|
||||
// when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime,
|
||||
@@ -1445,7 +1638,7 @@ void EndCallstack()
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
static const char* DecodeCallstackPtrFastExternal( uint64_t ptr )
|
||||
{
|
||||
static char ret[1024];
|
||||
@@ -1484,8 +1677,8 @@ const char* DecodeCallstackPtrFast( uint64_t ptr )
|
||||
{
|
||||
static char ret[1024];
|
||||
|
||||
#ifdef __linux__
|
||||
if( s_externalPid != 0 && s_extImages ) return DecodeCallstackPtrFastExternal( ptr );
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( s_externalTargetPid != 0 && s_extImages ) return DecodeCallstackPtrFastExternal( ptr );
|
||||
#endif
|
||||
|
||||
auto vptr = (void*)ptr;
|
||||
@@ -1535,7 +1728,7 @@ static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum*
|
||||
sym.needFree = false;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
static CallstackSymbolData DecodeSymbolAddressExternal( uint64_t ptr )
|
||||
{
|
||||
CallstackSymbolData sym;
|
||||
@@ -1559,8 +1752,8 @@ CallstackSymbolData DecodeSymbolAddress( uint64_t ptr )
|
||||
{
|
||||
CallstackSymbolData sym;
|
||||
|
||||
#ifdef __linux__
|
||||
if( s_externalPid != 0 && s_extImages ) return DecodeSymbolAddressExternal( ptr );
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( s_externalTargetPid != 0 && s_extImages ) return DecodeSymbolAddressExternal( ptr );
|
||||
#endif
|
||||
|
||||
if( cb_bts )
|
||||
@@ -1685,105 +1878,127 @@ void GetSymbolForOfflineResolve(void* address, uint64_t imageBaseAddress, Callst
|
||||
cbEntry.line = 0;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
CallstackEntryData DecodeCallstackPtrExternal( uint64_t ptr )
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
static int ExternalCallstackDataCb( void* data, uintptr_t /*pc*/, uintptr_t lowaddr, const char* fn, int lineno, const char* function )
|
||||
{
|
||||
const auto* extImg = FindExternalImageRefresh( ptr );
|
||||
if( extImg )
|
||||
auto* img = (const ExternalImageEntry*)data;
|
||||
|
||||
cb_data[cb_num].symLen = 0;
|
||||
cb_data[cb_num].symAddr = (uint64_t)img->loadBias + (uint64_t)lowaddr;
|
||||
|
||||
if( !fn && !function )
|
||||
{
|
||||
const char* imageName = extImg->path;
|
||||
|
||||
// Convert VMA (target process virtual address) to ELF virtual address.
|
||||
// elf_vaddr = vma - load_bias
|
||||
// libbacktrace indexes DWARF data by ELF virtual address when
|
||||
// the backtrace_state is created from a file (base_address=0).
|
||||
const auto elfVaddr = (uintptr_t)( ptr - extImg->loadBias );
|
||||
|
||||
auto* bts = GetExternalBtState( extImg );
|
||||
if( bts )
|
||||
// no symbol from pcinfo: name stays null, repaired from the symtab by ResolveExternalCallstack
|
||||
cb_data[cb_num].name = nullptr;
|
||||
cb_data[cb_num].file = nullptr;
|
||||
cb_data[cb_num].line = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( !fn ) fn = "[unknown]";
|
||||
if( !function )
|
||||
{
|
||||
// Try DWARF-based resolution
|
||||
ExternalResolveData rd = {};
|
||||
backtrace_pcinfo( bts, elfVaddr, ExternalPcInfoCb, ExternalBacktraceErrorCb, &rd );
|
||||
function = "[unknown]";
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* demangled = ___tracy_demangle( function );
|
||||
if( demangled ) function = demangled;
|
||||
}
|
||||
|
||||
if( rd.name || rd.file )
|
||||
{
|
||||
cb_num = 1;
|
||||
if( rd.name )
|
||||
{
|
||||
const auto len = std::min<size_t>( strlen( rd.name ), std::numeric_limits<uint16_t>::max() );
|
||||
cb_data[0].name = CopyStringFast( rd.name, len );
|
||||
}
|
||||
else
|
||||
{
|
||||
cb_data[0].name = CopyStringFast( "[unknown]" );
|
||||
}
|
||||
if( rd.file )
|
||||
{
|
||||
cb_data[0].file = NormalizePath( rd.file );
|
||||
if( !cb_data[0].file ) cb_data[0].file = CopyStringFast( rd.file );
|
||||
}
|
||||
else
|
||||
{
|
||||
cb_data[0].file = CopyStringFast( "[unknown]" );
|
||||
}
|
||||
cb_data[0].line = rd.line;
|
||||
cb_data[0].symLen = 0;
|
||||
cb_data[0].symAddr = elfVaddr;
|
||||
const auto len = std::min<size_t>( strlen( function ), std::numeric_limits<uint16_t>::max() );
|
||||
cb_data[cb_num].name = CopyStringFast( function, len );
|
||||
cb_data[cb_num].file = NormalizePath( fn );
|
||||
if( !cb_data[cb_num].file ) cb_data[cb_num].file = CopyStringFast( fn );
|
||||
cb_data[cb_num].line = lineno;
|
||||
}
|
||||
|
||||
// Try to get symbol size info
|
||||
ExternalSymInfoData sid = {};
|
||||
backtrace_syminfo( bts, elfVaddr, ExternalSymInfoCb, ExternalBacktraceErrorCb, &sid );
|
||||
if( sid.symsize > 0 )
|
||||
{
|
||||
cb_data[0].symLen = (uint32_t)sid.symsize;
|
||||
cb_data[0].symAddr = (uint64_t)sid.symval;
|
||||
}
|
||||
if( ++cb_num >= MaxCbTrace )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If DWARF gave us no function name, try the symbol table
|
||||
if( !rd.name && sid.symname )
|
||||
{
|
||||
tracy_free_fast( (void*)cb_data[0].name );
|
||||
const char* demangled = ___tracy_demangle( sid.symname );
|
||||
if( demangled )
|
||||
{
|
||||
cb_data[0].name = CopyStringFast( demangled );
|
||||
}
|
||||
else
|
||||
{
|
||||
cb_data[0].name = CopyStringFast( sid.symname );
|
||||
}
|
||||
}
|
||||
static void ExternalPcinfoErrorCb( void* /*data*/, const char* /*msg*/, int /*errnum*/ )
|
||||
{
|
||||
for( int i=0; i<cb_num; i++ )
|
||||
{
|
||||
tracy_free_fast( (void*)cb_data[i].name );
|
||||
tracy_free_fast( (void*)cb_data[i].file );
|
||||
}
|
||||
cb_num = 0;
|
||||
}
|
||||
|
||||
return { cb_data, 1, imageName ? imageName : "[unknown]" };
|
||||
}
|
||||
static CallstackEntryData ResolveExternalCallstack( const ExternalImageEntry* img, uint64_t vma )
|
||||
{
|
||||
const char* imageName = img->path ? img->path : "[unknown]";
|
||||
|
||||
// DWARF resolution failed; try symtab-only fallback
|
||||
const auto elfVaddr = (uintptr_t)( vma - img->loadBias );
|
||||
auto* bts = GetExternalBtState( img );
|
||||
|
||||
if( bts )
|
||||
{
|
||||
cb_num = 0;
|
||||
backtrace_pcinfo( bts, elfVaddr, ExternalCallstackDataCb, ExternalPcinfoErrorCb, const_cast<ExternalImageEntry*>( img ) );
|
||||
|
||||
if( cb_num > 0 )
|
||||
{
|
||||
ExternalSymInfoData sid = {};
|
||||
backtrace_syminfo( bts, elfVaddr, ExternalSymInfoCb, ExternalBacktraceErrorCb, &sid );
|
||||
if( sid.symname )
|
||||
{
|
||||
cb_num = 1;
|
||||
const char* demangled = ___tracy_demangle( sid.symname );
|
||||
cb_data[0].name = CopyStringFast( demangled ? demangled : sid.symname );
|
||||
cb_data[0].file = CopyStringFast( imageName ? imageName : "[unknown]" );
|
||||
cb_data[0].line = 0;
|
||||
cb_data[0].symLen = (uint32_t)sid.symsize;
|
||||
cb_data[0].symAddr = (uint64_t)sid.symval;
|
||||
return { cb_data, 1, imageName ? imageName : "[unknown]" };
|
||||
cb_data[cb_num-1].symLen = (uint32_t)sid.symsize;
|
||||
cb_data[cb_num-1].symAddr = (uint64_t)img->loadBias + (uint64_t)sid.symval;
|
||||
if( !cb_data[cb_num-1].name )
|
||||
{
|
||||
const char* demangled = ___tracy_demangle( sid.symname );
|
||||
cb_data[cb_num-1].name = CopyStringFast( demangled ? demangled : sid.symname );
|
||||
cb_data[cb_num-1].file = CopyStringFast( imageName );
|
||||
}
|
||||
}
|
||||
else if( !cb_data[cb_num-1].name )
|
||||
{
|
||||
cb_data[cb_num-1].name = CopyStringFast( "[unresolved]" );
|
||||
cb_data[cb_num-1].file = CopyStringFast( imageName );
|
||||
cb_data[cb_num-1].symLen = 0;
|
||||
cb_data[cb_num-1].symAddr = vma;
|
||||
}
|
||||
return { cb_data, uint8_t( cb_num ), imageName };
|
||||
}
|
||||
|
||||
// Fallback: return unresolved with offset
|
||||
cb_num = 1;
|
||||
cb_data[0].name = CopyStringFast( "[unresolved]" );
|
||||
cb_data[0].file = CopyStringFast( imageName ? imageName : "[unknown]" );
|
||||
cb_data[0].line = 0;
|
||||
cb_data[0].symLen = 0;
|
||||
cb_data[0].symAddr = elfVaddr;
|
||||
return { cb_data, 1, imageName ? imageName : "[unknown]" };
|
||||
ExternalSymInfoData sid = {};
|
||||
backtrace_syminfo( bts, elfVaddr, ExternalSymInfoCb, ExternalBacktraceErrorCb, &sid );
|
||||
if( sid.symname )
|
||||
{
|
||||
cb_num = 1;
|
||||
const char* demangled = ___tracy_demangle( sid.symname );
|
||||
cb_data[0].name = CopyStringFast( demangled ? demangled : sid.symname );
|
||||
cb_data[0].file = CopyStringFast( imageName );
|
||||
cb_data[0].line = 0;
|
||||
cb_data[0].symLen = (uint32_t)sid.symsize;
|
||||
cb_data[0].symAddr = (uint64_t)img->loadBias + (uint64_t)sid.symval;
|
||||
return { cb_data, 1, imageName };
|
||||
}
|
||||
}
|
||||
|
||||
cb_num = 1;
|
||||
cb_data[0].name = CopyStringFast( "[unresolved]" );
|
||||
cb_data[0].file = CopyStringFast( imageName );
|
||||
cb_data[0].line = 0;
|
||||
cb_data[0].symLen = 0;
|
||||
cb_data[0].symAddr = vma;
|
||||
return { cb_data, 1, imageName };
|
||||
}
|
||||
|
||||
CallstackEntryData DecodeCallstackPtrExternal( uint64_t ptr )
|
||||
{
|
||||
const auto* extImg = FindExternalImageRefresh( ptr );
|
||||
if( extImg ) return ResolveExternalCallstack( extImg, ptr );
|
||||
|
||||
// Address doesn't belong to any known mapping
|
||||
cb_num = 1;
|
||||
cb_data[0].name = CopyStringFast( "[unknown]" );
|
||||
@@ -1800,8 +2015,8 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr )
|
||||
InitAllocator();
|
||||
if( !IsKernelAddress( ptr ) )
|
||||
{
|
||||
#ifdef __linux__
|
||||
if( s_externalPid != 0 && s_extImages ) return DecodeCallstackPtrExternal( ptr );
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( s_externalTargetPid != 0 && s_extImages ) return DecodeCallstackPtrExternal( ptr );
|
||||
#endif
|
||||
|
||||
const char* imageName = nullptr;
|
||||
|
||||
@@ -7,6 +7,13 @@
|
||||
#include "../common/TracyForceInline.hpp"
|
||||
#include "TracyCallstack.h"
|
||||
|
||||
// External target API: desktop Linux only. The tracy-monitor that drives
|
||||
// it builds for Linux x86_64/aarch64 only, and Android, although __linux__,
|
||||
// lacks process_vm_readv on 32-bit ABIs.
|
||||
#if defined(__linux__) && !defined(__ANDROID__) && defined(TRACY_HAS_CALLSTACK)
|
||||
# define TRACY_HAS_EXTERNAL_TARGET
|
||||
#endif
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
@@ -88,8 +95,13 @@ void InitCallstackCritical();
|
||||
void EndCallstack();
|
||||
const char* GetKernelModulePath( uint64_t addr );
|
||||
|
||||
#ifdef __linux__
|
||||
void InitExternalImageCache( pid_t pid );
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
bool InitExternalTarget( pid_t targetPid );
|
||||
uint32_t GetExternalTargetPid();
|
||||
const char* GetExternalTargetName();
|
||||
uint64_t GetExternalTargetExeTime();
|
||||
|
||||
size_t ReadExternalTargetMemory( uint64_t addr, uint32_t size, char* buf );
|
||||
#endif
|
||||
|
||||
#ifdef TRACY_DEBUGINFOD
|
||||
|
||||
@@ -416,12 +416,11 @@ static int64_t SetupHwTimer()
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t ___tracy_magic_pid_override = 0;
|
||||
char ___tracy_magic_process_name[64] = {};
|
||||
|
||||
static const char* GetProcessName()
|
||||
{
|
||||
if( *___tracy_magic_process_name != 0 ) return ___tracy_magic_process_name;
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( GetExternalTargetPid() != 0 ) return GetExternalTargetName();
|
||||
#endif
|
||||
|
||||
const char* processName = "unknown";
|
||||
#ifdef _WIN32
|
||||
@@ -750,7 +749,10 @@ static const char* GetHostInfo()
|
||||
|
||||
static uint64_t GetPid()
|
||||
{
|
||||
if( ___tracy_magic_pid_override != 0 ) return uint64_t( ___tracy_magic_pid_override );
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
const auto externalPid = GetExternalTargetPid();
|
||||
if( externalPid != 0 ) return uint64_t( externalPid );
|
||||
#endif
|
||||
|
||||
#if defined _WIN32
|
||||
return uint64_t( GetCurrentProcessId() );
|
||||
@@ -918,6 +920,7 @@ std::atomic<bool> s_symbolThreadGone { false };
|
||||
#endif
|
||||
#ifdef TRACY_HAS_SYSTEM_TRACING
|
||||
static std::atomic<Thread*> s_sysTraceThread(nullptr);
|
||||
static std::atomic<bool> s_sysTraceStartFailed(false);
|
||||
#endif
|
||||
|
||||
#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER
|
||||
@@ -1173,12 +1176,17 @@ static void StartSystemTracing( int64_t& samplingPeriod )
|
||||
}
|
||||
else if( SysTraceStart( samplingPeriod ) )
|
||||
{
|
||||
s_sysTraceStartFailed.store( false, std::memory_order_release );
|
||||
Thread* sysTraceThread = (Thread*)tracy_malloc( sizeof( Thread ) );
|
||||
new( sysTraceThread ) Thread( SysTraceWorker, nullptr );
|
||||
Thread* prev = s_sysTraceThread.exchange( sysTraceThread );
|
||||
TRACY_ASSERT( prev == nullptr );
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
s_sysTraceStartFailed.store( true, std::memory_order_release );
|
||||
}
|
||||
}
|
||||
|
||||
static void StopSystemTracing()
|
||||
@@ -1510,6 +1518,26 @@ TRACY_API bool ProfilerAllocatorAvailable() { return !RpThreadShutdown; }
|
||||
|
||||
TRACY_API bool BeginSamplingProfiling() { return GetProfiler().BeginSamplingProfiling(); }
|
||||
TRACY_API void EndSamplingProfiling() { return GetProfiler().EndSamplingProfiling(); }
|
||||
TRACY_API bool IsSystemTracingFailed()
|
||||
{
|
||||
#if defined(TRACY_HAS_SYSTEM_TRACING)
|
||||
return s_sysTraceStartFailed.load( std::memory_order_acquire );
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::atomic<int> s_reservedListenFd( -1 );
|
||||
|
||||
TRACY_API void SetReservedListenSocket( int fd )
|
||||
{
|
||||
TRACY_ASSERT( fd >= 0 );
|
||||
s_reservedListenFd.store( fd, std::memory_order_release );
|
||||
}
|
||||
|
||||
static std::atomic<bool> s_dataPortListening(false);
|
||||
|
||||
TRACY_API bool IsDataPortListening() { return s_dataPortListening.load( std::memory_order_acquire ); }
|
||||
|
||||
constexpr static size_t SafeSendBufferSize = 65536;
|
||||
|
||||
@@ -1524,6 +1552,7 @@ Profiler::Profiler()
|
||||
, m_broadcast( nullptr )
|
||||
, m_noExit( false )
|
||||
, m_userPort( 0 )
|
||||
, m_dataPort( 0 )
|
||||
, m_zoneId( 1 )
|
||||
, m_sectionId( 1 )
|
||||
, m_samplingPeriod( 0 )
|
||||
@@ -1795,13 +1824,22 @@ void Profiler::Worker()
|
||||
#endif
|
||||
|
||||
m_exectime = 0;
|
||||
const auto execname = GetProcessExecutablePath();
|
||||
if( execname )
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( GetExternalTargetPid() != 0 )
|
||||
{
|
||||
struct stat st;
|
||||
if( stat( execname, &st ) == 0 )
|
||||
m_exectime = GetExternalTargetExeTime();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
const auto execname = GetProcessExecutablePath();
|
||||
if( execname )
|
||||
{
|
||||
m_exectime = (uint64_t)st.st_mtime;
|
||||
struct stat st;
|
||||
if( stat( execname, &st ) == 0 )
|
||||
{
|
||||
m_exectime = (uint64_t)st.st_mtime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1880,7 +1918,13 @@ void Profiler::Worker()
|
||||
|
||||
ListenSocket listen;
|
||||
bool isListening = false;
|
||||
if( !dataPortSearch )
|
||||
const int reservedFd = s_reservedListenFd.exchange( -1, std::memory_order_acquire );
|
||||
if( reservedFd != -1 )
|
||||
{
|
||||
listen.Adopt( reservedFd );
|
||||
isListening = true;
|
||||
}
|
||||
else if( !dataPortSearch )
|
||||
{
|
||||
isListening = listen.Listen( dataPort, 4 );
|
||||
}
|
||||
@@ -1896,6 +1940,9 @@ void Profiler::Worker()
|
||||
}
|
||||
}
|
||||
}
|
||||
if( isListening ) dataPort = listen.LocalPort();
|
||||
|
||||
s_dataPortListening.store( isListening, std::memory_order_release );
|
||||
if( !isListening )
|
||||
{
|
||||
for(;;)
|
||||
@@ -1910,6 +1957,7 @@ void Profiler::Worker()
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
||||
}
|
||||
}
|
||||
m_dataPort.store( dataPort, std::memory_order_release );
|
||||
|
||||
#ifndef TRACY_NO_BROADCAST
|
||||
m_broadcast = (UdpBroadcast*)tracy_malloc( sizeof( UdpBroadcast ) );
|
||||
@@ -3741,7 +3789,6 @@ void Profiler::QueueKernelCode( uint64_t symbol, uint32_t size )
|
||||
|
||||
void Profiler::QueueSourceCodeQuery( uint32_t id )
|
||||
{
|
||||
TRACY_ASSERT( m_exectime != 0 );
|
||||
TRACY_ASSERT( m_queryData );
|
||||
m_symbolQueue.emplace( SymbolQueueItem { SymbolQueueItemType::SourceCode, uint64_t( m_queryData ), uint64_t( m_queryImage ), id } );
|
||||
m_queryData = nullptr;
|
||||
@@ -4430,29 +4477,35 @@ void Profiler::HandleParameter( uint64_t payload )
|
||||
|
||||
void Profiler::HandleSymbolCodeQuery( uint64_t symbol, uint32_t size )
|
||||
{
|
||||
#ifdef __linux__
|
||||
// When profiling an external process, symbol addresses are ELF virtual
|
||||
// addresses, not pointers in the monitor's address space. We cannot
|
||||
// read code bytes directly.
|
||||
if( ___tracy_magic_pid_override != 0 )
|
||||
{
|
||||
AckSymbolCodeNotAvailable();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if( symbol >> 63 != 0 )
|
||||
{
|
||||
QueueKernelCode( symbol, size );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto&& lambda = [ this, symbol ]( const char* buf, size_t size ) {
|
||||
SendLongString( symbol, buf, size, QueueType::SymbolCode );
|
||||
};
|
||||
|
||||
// 'symbol' may have come from a module that has since unloaded, perform a safe copy before sending
|
||||
if( !WithSafeCopy( (const char*)symbol, size, lambda ) ) AckSymbolCodeNotAvailable();
|
||||
auto&& lambda = [ this, symbol ]( const char* buf, size_t size ) {
|
||||
SendLongString( symbol, buf, size, QueueType::SymbolCode );
|
||||
};
|
||||
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( GetExternalTargetPid() != 0 )
|
||||
{
|
||||
auto buf = (char*)tracy_malloc_fast( size );
|
||||
if( ReadExternalTargetMemory( symbol, size, buf ) == size )
|
||||
{
|
||||
lambda( buf, size );
|
||||
}
|
||||
else
|
||||
{
|
||||
AckSymbolCodeNotAvailable();
|
||||
}
|
||||
tracy_free_fast( buf );
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 'symbol' may have come from a module that has since unloaded, perform a safe copy before sending
|
||||
if( !WithSafeCopy( (const char*)symbol, size, lambda ) ) AckSymbolCodeNotAvailable();
|
||||
}
|
||||
|
||||
void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id )
|
||||
|
||||
@@ -80,6 +80,11 @@ TRACY_API bool IsProfilerStarted();
|
||||
|
||||
TRACY_API bool BeginSamplingProfiling();
|
||||
TRACY_API void EndSamplingProfiling();
|
||||
TRACY_API bool IsSystemTracingFailed();
|
||||
|
||||
TRACY_API void SetReservedListenSocket( int fd );
|
||||
|
||||
TRACY_API bool IsDataPortListening();
|
||||
|
||||
class GpuCtx;
|
||||
class Profiler;
|
||||
@@ -909,6 +914,12 @@ public:
|
||||
return m_isConnected.load( std::memory_order_acquire );
|
||||
}
|
||||
|
||||
// Returns 0 until the listen socket is bound
|
||||
tracy_force_inline uint32_t GetPort() const
|
||||
{
|
||||
return m_dataPort.load( std::memory_order_acquire );
|
||||
}
|
||||
|
||||
tracy_force_inline void SetProgramName( const char* name )
|
||||
{
|
||||
m_programNameLock.lock();
|
||||
@@ -1170,6 +1181,7 @@ private:
|
||||
UdpBroadcast* m_broadcast;
|
||||
bool m_noExit;
|
||||
uint32_t m_userPort;
|
||||
std::atomic<uint32_t> m_dataPort;
|
||||
std::atomic<uint32_t> m_zoneId;
|
||||
std::atomic<uint32_t> m_sectionId;
|
||||
int64_t m_samplingPeriod;
|
||||
|
||||
@@ -404,6 +404,7 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch
|
||||
# include "TracyCpuid.hpp"
|
||||
# endif
|
||||
|
||||
# include "TracyCallstack.hpp"
|
||||
# include "TracyProfiler.hpp"
|
||||
# include "TracyRingBuffer.hpp"
|
||||
# include "TracyThread.hpp"
|
||||
@@ -419,12 +420,7 @@ static bool s_ctxSwitchCallchain = false;
|
||||
|
||||
static RingBuffer* s_ring = nullptr;
|
||||
|
||||
extern uint32_t ___tracy_magic_pid_override;
|
||||
|
||||
// (pid, cpu) pair for a per-task perf event open. In self-profiling mode we
|
||||
// iterate one entry per CPU with pid = our tgid. In monitor mode we iterate
|
||||
// one entry per existing thread of the target, with cpu = -1, so inherit=1
|
||||
// can cover all descendants without multiplying ring buffers by CPU count.
|
||||
struct PerfIterTarget
|
||||
{
|
||||
pid_t pid;
|
||||
@@ -461,6 +457,12 @@ static int EnumerateTaskTids( pid_t pid, uint32_t** out )
|
||||
tids[count++] = (uint32_t)tid;
|
||||
}
|
||||
closedir( dir );
|
||||
if( count == 0 )
|
||||
{
|
||||
tracy_free( tids );
|
||||
*out = nullptr;
|
||||
return 0;
|
||||
}
|
||||
*out = tids;
|
||||
return (int)count;
|
||||
}
|
||||
@@ -476,13 +478,16 @@ static bool CurrentProcOwnsThread( uint32_t tid )
|
||||
if( hv == -tid ) return false;
|
||||
|
||||
char path[256];
|
||||
if( ___tracy_magic_pid_override != 0 )
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
const auto externalPid = GetExternalTargetPid();
|
||||
if( externalPid != 0 )
|
||||
{
|
||||
sprintf( path, "/proc/%d/task/%d", (int)___tracy_magic_pid_override, tid );
|
||||
sprintf( path, "/proc/%" PRIu32 "/task/%" PRIu32, externalPid, tid );
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
sprintf( path, "/proc/self/task/%d", tid );
|
||||
sprintf( path, "/proc/self/task/%" PRIu32, tid );
|
||||
}
|
||||
struct stat st;
|
||||
if( stat( path, &st ) == 0 )
|
||||
@@ -516,6 +521,36 @@ enum TraceEventId
|
||||
EventWaking,
|
||||
};
|
||||
|
||||
static void ProbePreciseIp( perf_event_attr& pe, pid_t pid );
|
||||
|
||||
static bool OpenSampleEvent( const PerfIterTarget& tgt, const perf_event_attr& inPe, int eventId )
|
||||
{
|
||||
static bool noKernelAccessLogged = false;
|
||||
perf_event_attr pe = inPe;
|
||||
int fd = perf_event_open( &pe, tgt.pid, tgt.cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd == -1 )
|
||||
{
|
||||
pe.exclude_kernel = 1;
|
||||
pe.exclude_callchain_kernel = 1;
|
||||
ProbePreciseIp( pe, tgt.pid );
|
||||
fd = perf_event_open( &pe, tgt.pid, tgt.cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 && !noKernelAccessLogged )
|
||||
{
|
||||
noKernelAccessLogged = true;
|
||||
TracyDebug( " No access to kernel samples; user-space only (perf_event_paranoid / capabilities)" );
|
||||
}
|
||||
}
|
||||
if( fd == -1 )
|
||||
{
|
||||
TracyDebug( " Failed to setup!" );
|
||||
return false;
|
||||
}
|
||||
new( s_ring + s_numBuffers ) RingBuffer( 64 * 1024, fd, eventId );
|
||||
if( !s_ring[s_numBuffers].IsValid() ) return false;
|
||||
s_numBuffers++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ProbePreciseIp( perf_event_attr& pe, unsigned long long config0, unsigned long long config1, pid_t pid )
|
||||
{
|
||||
pe.config = config1;
|
||||
@@ -780,16 +815,19 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
}
|
||||
}
|
||||
samplingPeriod = SamplingFrequencyToPeriodNs( samplingFrequency );
|
||||
uint32_t currentPid = ___tracy_magic_pid_override != 0 ? ___tracy_magic_pid_override : (uint32_t)getpid();
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
const auto externalPid = GetExternalTargetPid();
|
||||
#else
|
||||
const uint32_t externalPid = 0;
|
||||
#endif
|
||||
uint32_t currentPid = externalPid != 0 ? externalPid : (uint32_t)getpid();
|
||||
|
||||
s_numCpus = (int)std::thread::hardware_concurrency();
|
||||
|
||||
// Build the per-task iteration list. In monitor mode this is all existing
|
||||
// threads of the target (one event per thread, any CPU); in self-profiling
|
||||
// it is per-CPU bound to our own tgid.
|
||||
PerfIterTarget* iter = nullptr;
|
||||
int numIter = 0;
|
||||
if( ___tracy_magic_pid_override != 0 )
|
||||
PerfIterTarget* iter;
|
||||
int numIter;
|
||||
#ifdef TRACY_HAS_EXTERNAL_TARGET
|
||||
if( externalPid != 0 )
|
||||
{
|
||||
uint32_t* tids = nullptr;
|
||||
const int numTids = EnumerateTaskTids( (pid_t)currentPid, &tids );
|
||||
@@ -798,13 +836,28 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
TracyDebug( "Failed to enumerate threads of pid %u; target may have exited.", currentPid );
|
||||
return false;
|
||||
}
|
||||
iter = (PerfIterTarget*)tracy_malloc( sizeof( PerfIterTarget ) * numTids );
|
||||
for( int i=0; i<numTids; i++ ) iter[i] = { (pid_t)tids[i], -1 };
|
||||
numIter = numTids;
|
||||
if( numTids == 1 )
|
||||
{
|
||||
iter = (PerfIterTarget*)tracy_malloc( sizeof( PerfIterTarget ) * s_numCpus );
|
||||
for( int i=0; i<s_numCpus; i++ ) iter[i] = { (pid_t)tids[0], i };
|
||||
numIter = s_numCpus;
|
||||
TracyDebug( "Monitor mode: per-CPU events on pid %u (launch)", currentPid );
|
||||
}
|
||||
else
|
||||
{
|
||||
iter = (PerfIterTarget*)tracy_malloc( sizeof( PerfIterTarget ) * numTids * s_numCpus );
|
||||
int k = 0;
|
||||
for( int i=0; i<numTids; i++ )
|
||||
{
|
||||
for( int c=0; c<s_numCpus; c++ ) iter[k++] = { (pid_t)tids[i], c };
|
||||
}
|
||||
numIter = numTids * s_numCpus;
|
||||
TracyDebug( "Monitor mode: per-thread per-CPU events for %i threads of pid %u (attach)", numTids, currentPid );
|
||||
}
|
||||
tracy_free( tids );
|
||||
TracyDebug( "Monitor mode: tracing %i existing threads of pid %u", numIter, currentPid );
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
iter = (PerfIterTarget*)tracy_malloc( sizeof( PerfIterTarget ) * s_numCpus );
|
||||
for( int i=0; i<s_numCpus; i++ ) iter[i] = { (pid_t)currentPid, i };
|
||||
@@ -829,7 +882,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
pe.size = sizeof( perf_event_attr );
|
||||
pe.config = PERF_COUNT_SW_CPU_CLOCK;
|
||||
pe.sample_freq = samplingFrequency;
|
||||
pe.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CALLCHAIN;
|
||||
pe.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CALLCHAIN;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION( 4, 8, 0 )
|
||||
if( perfAbi >= PerfAbi48AndNewer ) pe.sample_max_stack = 127;
|
||||
#endif
|
||||
@@ -848,25 +901,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
ProbePreciseIp( pe, currentPid );
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd == -1 )
|
||||
{
|
||||
pe.exclude_kernel = 1;
|
||||
ProbePreciseIp( pe, currentPid );
|
||||
fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd == -1 )
|
||||
{
|
||||
TracyDebug( " Failed to setup!");
|
||||
break;
|
||||
}
|
||||
TracyDebug( " No access to kernel samples" );
|
||||
}
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventCallstack );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventCallstack ) ) TracyDebug( " Target %i ok (EventCallstack)", i );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,31 +929,13 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
ProbePreciseIp( pe, PERF_COUNT_HW_CPU_CYCLES, PERF_COUNT_HW_INSTRUCTIONS, currentPid );
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
const int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 )
|
||||
{
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventCpuCycles );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventCpuCycles ) ) TracyDebug( " Target %i ok (EventCpuCycles)", i );
|
||||
}
|
||||
|
||||
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
const int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 )
|
||||
{
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventInstructionsRetired );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventInstructionsRetired ) ) TracyDebug( " Target %i ok (EventInstructionsRetired)", i );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,31 +951,13 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
}
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
const int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 )
|
||||
{
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventCacheReference );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventCacheReference ) ) TracyDebug( " Target %i ok (EventCacheReference)", i );
|
||||
}
|
||||
|
||||
pe.config = PERF_COUNT_HW_CACHE_MISSES;
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
const int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 )
|
||||
{
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventCacheMiss );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventCacheMiss ) ) TracyDebug( " Target %i ok (EventCacheMiss)", i );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,31 +968,13 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
ProbePreciseIp( pe, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, PERF_COUNT_HW_BRANCH_MISSES, currentPid );
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
const int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 )
|
||||
{
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventBranchRetired );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventBranchRetired ) ) TracyDebug( " Target %i ok (EventBranchRetired)", i );
|
||||
}
|
||||
|
||||
pe.config = PERF_COUNT_HW_BRANCH_MISSES;
|
||||
for( int i=0; i<numIter; i++ )
|
||||
{
|
||||
const int fd = perf_event_open( &pe, iter[i].pid, iter[i].cpu, -1, PERF_FLAG_FD_CLOEXEC );
|
||||
if( fd != -1 )
|
||||
{
|
||||
new( s_ring+s_numBuffers ) RingBuffer( 64*1024, fd, EventBranchMiss );
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Target %i ok", i );
|
||||
}
|
||||
}
|
||||
if( OpenSampleEvent( iter[i], pe, EventBranchMiss ) ) TracyDebug( " Target %i ok (EventBranchMiss)", i );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,7 +1006,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Core %i ok", i );
|
||||
TracyDebug( " Core %i ok (EventVsync)", i );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1067,7 +1048,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Core %i ok", i );
|
||||
TracyDebug( " Core %i ok (EventContextSwitch)", i );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1101,7 +1082,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
if( s_ring[s_numBuffers].IsValid() )
|
||||
{
|
||||
s_numBuffers++;
|
||||
TracyDebug( " Core %i ok", i );
|
||||
TracyDebug( " Core %i ok (EventWaking)", i );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1112,7 +1093,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
|
||||
|
||||
tracy_free( iter );
|
||||
|
||||
if( s_numBuffers == 0 )
|
||||
if( s_numBuffers == 0 || ( externalPid != 0 && s_ctxBufferIdx == 0 ) )
|
||||
{
|
||||
tracy_free( s_ring );
|
||||
s_ring = nullptr;
|
||||
@@ -1225,38 +1206,42 @@ void SysTraceWorker( void* ptr )
|
||||
{
|
||||
auto offset = pos + sizeof( perf_event_header );
|
||||
|
||||
// Layout:
|
||||
// u32 pid, tid
|
||||
// u64 time
|
||||
// u64 cnt
|
||||
// u64 ip[cnt]
|
||||
// field order matches PERF_SAMPLE_IP | TID | TIME | CALLCHAIN (then buf.cnt ips)
|
||||
|
||||
#pragma pack( push, 1 )
|
||||
struct
|
||||
{
|
||||
uint64_t ip;
|
||||
uint32_t pid;
|
||||
uint32_t tid;
|
||||
uint64_t t0;
|
||||
uint64_t cnt;
|
||||
} buf;
|
||||
#pragma pack( pop )
|
||||
|
||||
offset += sizeof( uint32_t );
|
||||
ring.Read( &buf, offset, sizeof( buf ) );
|
||||
offset += sizeof( buf );
|
||||
|
||||
uint64_t* trace;
|
||||
if( buf.cnt > 0 )
|
||||
{
|
||||
#if defined TRACY_HW_TIMER && defined TRACY_HAS_RDTSC
|
||||
buf.t0 = ring.ConvertTimeToTsc( buf.t0 );
|
||||
#endif
|
||||
auto trace = GetCallstackBlock( buf.cnt, ring, offset );
|
||||
|
||||
TracyLfqPrepare( QueueType::CallstackSample );
|
||||
MemWrite( &item->callstackSampleFat.time, int64_t( buf.t0 ) );
|
||||
MemWrite( &item->callstackSampleFat.thread, buf.tid );
|
||||
MemWrite( &item->callstackSampleFat.ptr, uint64_t( trace ) );
|
||||
TracyLfqCommit;
|
||||
trace = GetCallstackBlock( buf.cnt, ring, offset );
|
||||
}
|
||||
else
|
||||
{
|
||||
trace = (uint64_t*)tracy_malloc_fast( 2 * sizeof( uint64_t ) );
|
||||
trace[0] = 1;
|
||||
trace[1] = buf.ip;
|
||||
}
|
||||
|
||||
#if defined TRACY_HW_TIMER && defined TRACY_HAS_RDTSC
|
||||
buf.t0 = ring.ConvertTimeToTsc( buf.t0 );
|
||||
#endif
|
||||
TracyLfqPrepare( QueueType::CallstackSample );
|
||||
MemWrite( &item->callstackSampleFat.time, int64_t( buf.t0 ) );
|
||||
MemWrite( &item->callstackSampleFat.thread, buf.tid );
|
||||
MemWrite( &item->callstackSampleFat.ptr, uint64_t( trace ) );
|
||||
TracyLfqCommit;
|
||||
}
|
||||
pos += hdr.size;
|
||||
}
|
||||
@@ -1580,10 +1565,17 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch
|
||||
f = fopen( fn, "rb" );
|
||||
if( f )
|
||||
{
|
||||
char buf[256];
|
||||
char buf[256] = {};
|
||||
const auto sz = fread( buf, 1, 256, f );
|
||||
if( sz > 0 && buf[sz-1] == '\n' ) buf[sz-1] = '\0';
|
||||
threadName = CopyString( buf );
|
||||
if( sz > 0 )
|
||||
{
|
||||
threadName = CopyString( buf );
|
||||
}
|
||||
else
|
||||
{
|
||||
threadName = CopyString( "???", 3 );
|
||||
}
|
||||
fclose( f );
|
||||
}
|
||||
else
|
||||
@@ -1595,15 +1587,22 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch
|
||||
f = fopen( fn, "rb" );
|
||||
if( f )
|
||||
{
|
||||
char* tmp = (char*)tracy_malloc_fast( 8*1024 );
|
||||
char* tmp = (char*)tracy_malloc_fast( 8*1024 + 1 );
|
||||
const auto fsz = (ptrdiff_t)fread( tmp, 1, 8*1024, f );
|
||||
fclose( f );
|
||||
if( fsz <= 0 )
|
||||
{
|
||||
tracy_free_fast( tmp );
|
||||
name = CopyStringFast( "???", 3 );
|
||||
return;
|
||||
}
|
||||
tmp[fsz] = '\0';
|
||||
|
||||
int pid = -1;
|
||||
auto line = tmp;
|
||||
for(;;)
|
||||
{
|
||||
if( memcmp( "Tgid:\t", line, 6 ) == 0 )
|
||||
if( line - tmp + 6 <= fsz && memcmp( "Tgid:\t", line, 6 ) == 0 )
|
||||
{
|
||||
pid = atoi( line + 6 );
|
||||
break;
|
||||
@@ -1627,10 +1626,10 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch
|
||||
f = fopen( fn, "rb" );
|
||||
if( f )
|
||||
{
|
||||
char buf[256];
|
||||
char buf[256] = {};
|
||||
const auto sz = fread( buf, 1, 256, f );
|
||||
if( sz > 0 && buf[sz-1] == '\n' ) buf[sz-1] = '\0';
|
||||
name = CopyStringFast( buf );
|
||||
name = sz > 0 ? CopyStringFast( buf ) : CopyStringFast( "???", 3 );
|
||||
fclose( f );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -427,6 +427,21 @@ ListenSocket::~ListenSocket()
|
||||
if( m_sock != -1 ) Close();
|
||||
}
|
||||
|
||||
void ListenSocket::Adopt( int fd )
|
||||
{
|
||||
TRACY_ASSERT( m_sock == -1 );
|
||||
m_sock = fd;
|
||||
}
|
||||
|
||||
uint16_t ListenSocket::LocalPort() const
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof( addr );
|
||||
if( getsockname( m_sock, (sockaddr*)&addr, &len ) != 0 ) return 0;
|
||||
if( addr.ss_family == AF_INET6 ) return ntohs( ((struct sockaddr_in6*)&addr)->sin6_port );
|
||||
return ntohs( ((struct sockaddr_in*)&addr)->sin_port );
|
||||
}
|
||||
|
||||
static int addrinfo_and_socket_for_family( uint16_t port, int ai_family, struct addrinfo** res )
|
||||
{
|
||||
struct addrinfo hints;
|
||||
|
||||
@@ -97,6 +97,8 @@ public:
|
||||
bool Listen( uint16_t port, int backlog );
|
||||
Socket* Accept();
|
||||
void Close();
|
||||
void Adopt( int fd );
|
||||
uint16_t LocalPort() const;
|
||||
|
||||
ListenSocket( const ListenSocket& ) = delete;
|
||||
ListenSocket( ListenSocket&& ) = delete;
|
||||
|
||||
@@ -94,7 +94,10 @@ extern struct backtrace_state *backtrace_create_state (
|
||||
data to be loaded directly from the file with base_address=0.
|
||||
The caller is responsible for converting runtime virtual addresses
|
||||
to ELF virtual addresses before passing them to backtrace_pcinfo
|
||||
or backtrace_syminfo. */
|
||||
or backtrace_syminfo.
|
||||
The filename is copied into state-owned memory (the file is opened
|
||||
lazily on first use), so the caller's buffer need not outlive this
|
||||
call. */
|
||||
|
||||
extern struct backtrace_state *backtrace_create_state_for_file (
|
||||
const char *filename, int threaded,
|
||||
|
||||
@@ -81,11 +81,20 @@ backtrace_create_state_for_file (const char *filename, int threaded,
|
||||
backtrace_error_callback error_callback,
|
||||
void *data)
|
||||
{
|
||||
struct backtrace_state *state;
|
||||
/* The state opens the file lazily on first use (fileline_initialize),
|
||||
so it must own the filename: the caller's buffer may be freed as
|
||||
soon as this returns. */
|
||||
const size_t len = strlen (filename) + 1;
|
||||
char *copy = (char*)backtrace_alloc (NULL, len, error_callback, data);
|
||||
if (copy == NULL)
|
||||
return NULL;
|
||||
memcpy (copy, filename, len);
|
||||
|
||||
state = backtrace_create_state (filename, threaded, error_callback, data);
|
||||
if (state != NULL)
|
||||
state->external_file = 1;
|
||||
struct backtrace_state *state =
|
||||
backtrace_create_state (copy, threaded, error_callback, data);
|
||||
if (state == NULL)
|
||||
return NULL;
|
||||
state->external_file = 1;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
#define TracyParameterRegister(x,y)
|
||||
#define TracyParameterSetup(x,y,z,w)
|
||||
#define TracyIsConnected false
|
||||
#define TracyPort 0
|
||||
#define TracyIsStarted false
|
||||
#define TracySetProgramName(x)
|
||||
|
||||
@@ -258,6 +259,7 @@
|
||||
#define TracyParameterRegister( cb, data ) tracy::Profiler::ParameterRegister( cb, data )
|
||||
#define TracyParameterSetup( idx, name, type, val ) tracy::Profiler::ParameterSetup( idx, name, type, val )
|
||||
#define TracyIsConnected tracy::GetProfiler().IsConnected()
|
||||
#define TracyPort tracy::GetProfiler().GetPort()
|
||||
#define TracySetProgramName( name ) tracy::GetProfiler().SetProgramName( name )
|
||||
|
||||
#define TracySectionEnter( fmt, ... ) tracy::Profiler::SectionEnter( 0, fmt, ##__VA_ARGS__ )
|
||||
|
||||
@@ -174,7 +174,7 @@ public:
|
||||
|
||||
WriteInitialItem( physdev, tcpu, tgpu );
|
||||
|
||||
m_res = (int64_t*)tracy_malloc( sizeof( int64_t ) * m_queryCount );
|
||||
AllocateQueryResultBuffer();
|
||||
}
|
||||
|
||||
#if defined VK_EXT_host_query_reset
|
||||
@@ -223,9 +223,7 @@ public:
|
||||
|
||||
WriteInitialItem( physdev, tcpu, tgpu );
|
||||
|
||||
// We need the buffer to be twice as large for availability values
|
||||
size_t resSize = sizeof( int64_t ) * m_queryCount * 2;
|
||||
m_res = (int64_t*)tracy_malloc( resSize );
|
||||
AllocateQueryResultBuffer();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -414,6 +412,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
tracy_force_inline void AllocateQueryResultBuffer()
|
||||
{
|
||||
// We need the buffer to be twice as large for availability values
|
||||
m_res = (int64_t*)tracy_malloc( sizeof( int64_t ) * m_queryCount * 2 );
|
||||
}
|
||||
|
||||
tracy_force_inline void FindAvailableTimeDomains( VkPhysicalDevice physicalDevice, PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT _vkGetPhysicalDeviceCalibrateableTimeDomainsEXT )
|
||||
{
|
||||
uint32_t num;
|
||||
|
||||
@@ -33,15 +33,19 @@ TaskDispatch::~TaskDispatch()
|
||||
|
||||
void TaskDispatch::Queue( const std::function<void(void)>& f )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_queueLock );
|
||||
m_queue.emplace_back( f );
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_queueLock );
|
||||
m_queue.emplace_back( f );
|
||||
}
|
||||
m_cvWork.notify_one();
|
||||
}
|
||||
|
||||
void TaskDispatch::Queue( std::function<void(void)>&& f )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_queueLock );
|
||||
m_queue.emplace_back( std::move( f ) );
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_queueLock );
|
||||
m_queue.emplace_back( std::move( f ) );
|
||||
}
|
||||
m_cvWork.notify_one();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user