mirror of
https://github.com/wolfpld/tracy.git
synced 2026-06-16 12:18:59 +00:00
Compare commits
17 Commits
slomp/gl-c
...
slomp/stra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c541a2e1b | ||
|
|
9ab39d8af3 | ||
|
|
bfab6d03f4 | ||
|
|
66e4f5cef7 | ||
|
|
7637971e9e | ||
|
|
4e3cffc4ba | ||
|
|
3956616fc2 | ||
|
|
d34c45fa5a | ||
|
|
8fe5a511c9 | ||
|
|
afdd2e2f81 | ||
|
|
3c1b1b2f80 | ||
|
|
992134f85e | ||
|
|
37bc986584 | ||
|
|
feb4e7c989 | ||
|
|
4a8fe6f56e | ||
|
|
a960a25285 | ||
|
|
9588912aa9 |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
|
||||
cmake --build profiler/build
|
||||
else
|
||||
cmake --build profiler/build --parallel
|
||||
cmake --build profiler/build --parallel 2
|
||||
fi
|
||||
- name: Update utility
|
||||
run: |
|
||||
|
||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Build profiler
|
||||
run: |
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build profiler/build --parallel --config Release
|
||||
cmake --build profiler/build --parallel 2 --config Release
|
||||
- name: Build update
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
|
||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Build profiler
|
||||
run: |
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build profiler/build --parallel --config Release
|
||||
cmake --build profiler/build --parallel 2 --config Release
|
||||
- name: Build update
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
|
||||
@@ -11,7 +11,7 @@ The user manual
|
||||
|
||||
**Bartosz Taudul** [\<wolf@nereid.pl\>](mailto:wolf@nereid.pl)
|
||||
|
||||
2026-06-06 <https://github.com/wolfpld/tracy>
|
||||
2026-06-09 <https://github.com/wolfpld/tracy>
|
||||
|
||||
# Quick overview {#quick-overview .unnumbered}
|
||||
|
||||
@@ -1495,6 +1495,12 @@ You also need to periodically collect the GPU events using the `TracyGpuCollect`
|
||||
|
||||
[^49]: Because Apple is unable to implement standards properly.
|
||||
|
||||
##### Calibrated context
|
||||
|
||||
By default, the OpenGL context is uncalibrated: the CPU and GPU clocks are aligned only once, when the context is created, so over long captures the two time domains may drift apart (section [5.4](#options) describes correcting this drift manually). Defining `TRACY_OPENGL_AUTO_CALIBRATION` before including `TracyOpenGL.hpp` enables periodic recalibration instead: roughly once per second Tracy samples the GPU and CPU clocks together and emits a calibration event, allowing the profiler to track and remove the drift automatically.
|
||||
|
||||
This is opt-in because OpenGL exposes no atomic CPU+GPU timestamp query (unlike Vulkan's `VK_EXT_calibrated_timestamps` or Direct3D 12, whose contexts are always calibrated). Recalibration therefore reads the GPU clock with `glGetInteger64v(GL_TIMESTAMP)`, which forces a CPU/GPU synchronization (a pipeline stall) each time it runs. Enable it only when the improved long-capture alignment is worth the periodic stall.
|
||||
|
||||
### Vulkan
|
||||
|
||||
Similarly, for Vulkan support you should include the `public/tracy/TracyVulkan.hpp` header file. Tracing Vulkan devices and queues is a bit more involved, and the Vulkan initialization macro `TracyVkContext(physdev, device, queue, cmdbuf)` returns an instance of `TracyVkCtx` object, which tracks an associated Vulkan queue. Cleanup is performed using the `TracyVkDestroy(ctx)` macro. You may create multiple Vulkan contexts. To set a custom name for the context, use the `TracyVkContextName(ctx, name, size)` macro.
|
||||
@@ -1794,6 +1800,10 @@ By default, tracy client resolves callstack symbols in a background thread at ru
|
||||
|
||||
The generated tracy capture will have callstack frames symbols showing `[unresolved]`. The `update` tool can be used to load that capture, perform symbol resolution offline (by passing `-r`) and writing out a new capture with symbols resolved. By default `update` will use the original shared libraries paths that were recorded in the capture (which assumes running in the same machine or a machine with identical filesystem setup as the one used to run the tracy instrumented application). You can do path substitution with the `-p` option to perform any number of path substitions in order to use symbols located elsewhere.
|
||||
|
||||
By default symbol resolution is performed with the platform's native facility: the DbgHelp library on Windows, and the `addr2line` tool found in `PATH` elsewhere. You can override this with the `-a` option, passing the path to a custom `addr2line`-compatible tool (for instance an `addr2line` from a cross-compilation toolchain, or `llvm-addr2line`). The `-a` option works on all platforms, including Windows, and takes precedence over the platform default.
|
||||
|
||||
Extra arguments can be passed verbatim to the resolution tool with the `-A` option. Tracy records callstack frame offsets relative to the image base, but `addr2line`-compatible tools expect a full virtual address for images that have a non-zero preferred image base (such as PE on Windows or Mach-O on Apple). For these, pass `-A "--relative-address"` so that `llvm-addr2line` or `llvm-symbolizer` adds the image base back. ELF images need no such adjustment.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Important**
|
||||
>
|
||||
@@ -1988,6 +1998,39 @@ After you release the lock use the `TracyCLockAfterUnlock` macro:
|
||||
|
||||
You can optionally mark the location of where the lock is held by using the `TracyCLockMark` macro, this should be done after acquiring the lock.
|
||||
|
||||
Similarly, you can use the following macros to mark a shared lock using the C API:
|
||||
|
||||
- `TracyCSharedLockAnnounce(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockTerminate(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockBeforeLock(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockAfterLock(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockAfterUnlock(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockAfterTryLock(lock_ctx, acquired)`
|
||||
|
||||
- `TracyCSharedLockBeforeSharedLock(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockAfterSharedLock(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockAfterSharedUnlock(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockAfterTrySharedLock(lock_ctx, acquired)`
|
||||
|
||||
- `TracyCSharedLockMark(lock_ctx)`
|
||||
|
||||
- `TracyCSharedLockCustomName(lock_ctx, name, size)`
|
||||
|
||||
A shared lock context has to be defined next to the shared lock that it will be marking:
|
||||
|
||||
TracyCSharedLockCtx tracy_shared_lock_ctx;
|
||||
HANDLE shared_lock;
|
||||
|
||||
The same rules apply to shared locks as to regular locks, but you need to use the shared lock macros instead. Lock implementations in classes `Lockable` and `SharedLockable` show how to properly perform context handling.
|
||||
|
||||
### Memory profiling {#cmemoryprofiling}
|
||||
|
||||
Use the following macros in your implementations of `malloc` and `free`:
|
||||
@@ -3582,7 +3625,7 @@ You can freely adjust each time range on the timeline by clicking the left mouse
|
||||
|
||||
Tracy allows adding custom notes to the trace. For example, you may want to mark a region to ignore because the application was out-of-focus or a region where a new user was connecting to the game, which resulted in a frame drop that needs to be investigated.
|
||||
|
||||
Methods of specifying the annotation region are described in section [5.3](#timeranges). When a new annotation is added, a settings window is displayed (section [5.21](#annotationsettings)), allowing you to enter a description.
|
||||
Methods of specifying the annotation region are described in section [5.3](#timeranges). When a new annotation is added, it is assigned a semi-unique random name to make it distinguishable. The settings window is also opened (section [5.21](#annotationsettings)), allowing you to enter your own description of the annotation.
|
||||
|
||||
Annotations are displayed on the timeline, as presented in figure [21](#annotation). Clicking on the circle next to the text description will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section [5.22](#annotationlist), which is accessible through the * Tools* button on the control menu.
|
||||
|
||||
@@ -4125,7 +4168,9 @@ The information about the selected memory allocation is displayed in this window
|
||||
|
||||
## Trace information window {#traceinfo}
|
||||
|
||||
This window contains information about the current trace: captured program name, time of the capture, profiler version which performed the capture, and a custom trace description, which you can fill in.
|
||||
This window contains information about the current trace: captured program name, time of the capture, profiler version which performed the capture.
|
||||
|
||||
There's an text entry field for an optional custom description of the trace for you to fill in. This description will appear on the profiler window title bar, or when comparing two traces (section [5.8](#compare)), enabling you to quickly recognize what the trace contains. For some people it's fine to just have *any* semi-unique description to be able to identify a specific trace. For such purposes there's an * Generate name* button, which will set the trace description to an abstract meaningless identifier.
|
||||
|
||||
If the * Public sidecar* option is selected, the file containing trace-specific user settings (see section [9.2](#tracespecific)) will be saved on disk next to the trace file.
|
||||
|
||||
@@ -4159,6 +4204,7 @@ If an application should crash during profiling (section [2.5](#crashhandling))
|
||||
|
||||
-----
|
||||
|
||||
- Dice icon
|
||||
- User Gear icon
|
||||
|
||||
## Zone information window {#zoneinfo}
|
||||
@@ -4562,7 +4608,12 @@ The profiled program is highlighted using green color. Furthermore, the yellow h
|
||||
|
||||
## Annotation settings window {#annotationsettings}
|
||||
|
||||
In this window, you may modify how a timeline annotation (section [5.3.1](#annotatingtrace)) is presented by setting its text description or selecting region highlight color. If the note is no longer needed, you may also remove it here.
|
||||
In this window, you may modify how a timeline annotation (section [5.3.1](#annotatingtrace)) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the * Generate name* button. If the note is no longer needed, you may also remove it here.
|
||||
|
||||
|
||||
-----
|
||||
|
||||
- Dice icon
|
||||
|
||||
## Annotation list window {#annotationlist}
|
||||
|
||||
|
||||
@@ -4071,7 +4071,7 @@ You can freely adjust each time range on the timeline by clicking the \LMB{}~lef
|
||||
|
||||
Tracy allows adding custom notes to the trace. For example, you may want to mark a region to ignore because the application was out-of-focus or a region where a new user was connecting to the game, which resulted in a frame drop that needs to be investigated.
|
||||
|
||||
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, a settings window is displayed (section~\ref{annotationsettings}), allowing you to enter a description.
|
||||
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, it is assigned a semi-unique random name to make it distinguishable. The settings window is also opened (section~\ref{annotationsettings}), allowing you to enter your own description of the annotation.
|
||||
|
||||
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the circle next to the text description will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
|
||||
@@ -4582,7 +4582,9 @@ The information about the selected memory allocation is displayed in this window
|
||||
\subsection{Trace information window}
|
||||
\label{traceinfo}
|
||||
|
||||
This window contains information about the current trace: captured program name, time of the capture, profiler version which performed the capture, and a custom trace description, which you can fill in.
|
||||
This window contains information about the current trace: captured program name, time of the capture, profiler version which performed the capture.
|
||||
|
||||
There's an text entry field for an optional custom description of the trace for you to fill in. This description will appear on the profiler window title bar, or when comparing two traces (section~\ref{compare}), enabling you to quickly recognize what the trace contains. For some people it's fine to just have \emph{any} semi-unique description to be able to identify a specific trace. For such purposes there's an \emph{\faDice{}~Generate name} button, which will set the trace description to an abstract meaningless identifier.
|
||||
|
||||
If the \emph{\faUserGear{}~Public sidecar} option is selected, the file containing trace-specific user settings (see section~\ref{tracespecific}) will be saved on disk next to the trace file.
|
||||
|
||||
@@ -4922,7 +4924,7 @@ The profiled program is highlighted using green color. Furthermore, the yellow h
|
||||
\subsection{Annotation settings window}
|
||||
\label{annotationsettings}
|
||||
|
||||
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. If the note is no longer needed, you may also remove it here.
|
||||
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also remove it here.
|
||||
|
||||
\subsection{Annotation list window}
|
||||
\label{annotationlist}
|
||||
|
||||
@@ -70,6 +70,7 @@ set(SERVER_FILES
|
||||
TracyMarkdown.cpp
|
||||
TracyMicroArchitecture.cpp
|
||||
TracyMouse.cpp
|
||||
TracyNameGen.cpp
|
||||
TracyProtoHistory.cpp
|
||||
TracySourceContents.cpp
|
||||
TracySourceTokenizer.cpp
|
||||
|
||||
@@ -290,7 +290,7 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
|
||||
|
||||
[[maybe_unused]] static tracy_force_inline void TooltipIfHovered( const char* text )
|
||||
{
|
||||
if( !ImGui::IsItemHovered() ) return;
|
||||
if( !ImGui::IsItemHovered( ImGuiHoveredFlags_AllowWhenDisabled ) ) return;
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextUnformatted( text );
|
||||
ImGui::EndTooltip();
|
||||
|
||||
221
profiler/src/profiler/TracyNameGen.cpp
Normal file
221
profiler/src/profiler/TracyNameGen.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "TracyNameGen.hpp"
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
struct NameBank
|
||||
{
|
||||
const char* const* adjectives;
|
||||
const char* const* nouns;
|
||||
size_t numAdjectives;
|
||||
size_t numNouns;
|
||||
};
|
||||
|
||||
constexpr const char* AnalysisAdjectives[] = {
|
||||
"Granular", "Forensic", "Acute", "Lucid", "Precise",
|
||||
"Deep", "Exact", "Critical", "Analytical", "Transparent",
|
||||
"Subtle", "Sharp", "Rigid", "Focused", "Absolute",
|
||||
"Meticulous", "Spectral", "Diagnostic", "Pervasive", "Introspective",
|
||||
"Systematic", "Optical", "Minute", "Piercing", "Detailed",
|
||||
"Scrutinized", "Clear", "Keen", "Rigorous", "Vast",
|
||||
"Incisive", "Exhaustive", "Lateral", "Prismatic", "Observant"
|
||||
};
|
||||
constexpr const char* AnalysisNouns[] = {
|
||||
"Probe", "Trace", "Lens", "Scope", "Metric",
|
||||
"Insight", "Scan", "Audit", "Point", "Vector",
|
||||
"Signal", "Marker", "Frame", "Detail", "View",
|
||||
"Spectrum", "Snapshot", "Blueprint", "Aperture", "Index",
|
||||
"Radar", "Prism", "Gauge", "Focal", "Pattern",
|
||||
"Echo", "Signature", "Horizon", "Mirror", "Scale",
|
||||
"Telemetry", "Graph", "Stratum", "Artifact", "Aspect"
|
||||
};
|
||||
|
||||
constexpr const char* PerformanceAdjectives[] = {
|
||||
"Swift", "Lean", "Kinetic", "Agile", "Hyper",
|
||||
"Rapid", "Fluid", "Peak", "Instant", "Nimble",
|
||||
"Optimal", "Sonic", "Linear", "Warp", "Turbo",
|
||||
"Frictionless", "Seamless", "Electric", "Blazing", "Aerodynamic",
|
||||
"Quantum", "Prompt", "Direct", "Streamlined", "Volatile",
|
||||
"Highgain", "Rapidfire", "Torrential", "Sleek", "Velocity",
|
||||
"Dynamic", "Active", "Persistent", "Lightweight", "Snappy"
|
||||
};
|
||||
constexpr const char* PerformanceNouns[] = {
|
||||
"Pulse", "Flow", "Cycle", "Burst", "Stream",
|
||||
"Tick", "Glide", "Shift", "Velocity", "Spike",
|
||||
"Pace", "Rhythm", "Drive", "Path", "Edge",
|
||||
"Sprint", "Torrent", "Current", "Surge", "Momentum",
|
||||
"Flux", "Wave", "Accelerator", "Spark", "Jet",
|
||||
"Thrust", "Orbit", "Apex", "Bolt", "Phase",
|
||||
"Rush", "Impact", "Frequency", "Lapse", "Kick"
|
||||
};
|
||||
|
||||
constexpr const char* CoreAdjectives[] = {
|
||||
"Binary", "Raw", "Atomic", "Static", "Core",
|
||||
"Virtual", "Base", "Solid", "Dense", "Opaque",
|
||||
"Primitive", "Native", "Hard", "Stable", "Immutable",
|
||||
"Monolithic", "Bare", "Rigid", "Concrete", "Fundamental",
|
||||
"Discrete", "Fixed", "Heavy", "Latent", "Symmetric",
|
||||
"Implicit", "Explicit", "Cold", "Basic", "Granite",
|
||||
"Stark", "Brute", "Firm", "Stout", "Coarse"
|
||||
};
|
||||
constexpr const char* CoreNouns[] = {
|
||||
"Stack", "Heap", "Node", "Buffer", "Segment",
|
||||
"Thread", "Kernel", "Block", "Page", "Shell",
|
||||
"Layer", "Bit", "Logic", "Port", "Root",
|
||||
"Register", "Pointer", "Address", "Cache", "Opcode",
|
||||
"Slab", "Pipeline", "Bus", "Socket", "Sector",
|
||||
"Vault", "Anchor", "Pillar", "Base", "Primitive",
|
||||
"Offset", "Handle", "Struct", "Memory", "Word"
|
||||
};
|
||||
|
||||
constexpr const char* ModernAdjectives[] = {
|
||||
"Synthetic", "Neural", "Async", "Elastic", "Cloud",
|
||||
"Distributed", "Reactive", "Orbital", "Poly", "Infinite",
|
||||
"Parallel", "Modular", "Virtualized", "Scalable", "Agnostic",
|
||||
"Adaptive", "Hybrid", "Autonomous", "Global", "Synergic",
|
||||
"Omnipresent", "Evolving", "Abstract", "Unified", "Concurrent",
|
||||
"Remote", "Digital", "Cluster", "Ephemeral", "Stateful",
|
||||
"Stateless", "Serverless", "Decoupled", "Fluent", "Native"
|
||||
};
|
||||
constexpr const char* ModernNouns[] = {
|
||||
"Nexus", "Grid", "Matrix", "Vertex", "Sync",
|
||||
"Axiom", "Sphere", "Hub", "Mesh", "Bridge",
|
||||
"Link", "Unit", "Fabric", "Cluster", "Portal",
|
||||
"Ecosystem", "Catalyst", "Interface", "Domain", "Gateway",
|
||||
"Lattice", "Cloud", "Instance", "Schema", "Registry",
|
||||
"Tenant", "Namespace", "Pod", "Stream", "Endpoint",
|
||||
"Payload", "Relay", "Orchestrator", "Broker", "Agent"
|
||||
};
|
||||
|
||||
constexpr const char* FailureAdjectives[] = {
|
||||
"Clumsy", "Wobbly", "Confused", "Chaotic", "Sneaky",
|
||||
"Lazy", "Dizzy", "Broken", "Leaky", "Fragile",
|
||||
"Shaky", "Erratic", "Sleepy", "Lost", "Random",
|
||||
"Glitchy", "Unstable", "Paradoxical", "Cluttery", "Hiccupy",
|
||||
"Wonky", "Flaky", "Stubborn", "Moody", "Nervous",
|
||||
"Fumbling", "Drifting", "Tangled", "Blurred", "Absent",
|
||||
"Haphazard", "Spasmodic", "Clunky", "Jittery", "Bewildered"
|
||||
};
|
||||
constexpr const char* FailureNouns[] = {
|
||||
"Crash", "Bug", "Leak", "Hang", "Timeout",
|
||||
"Panic", "Loop", "Spill", "Hiccup", "Glitch",
|
||||
"Wobble", "Tumble", "Void", "Abyss", "Maze",
|
||||
"Knot", "Static", "Noise", "Drift", "Stumble",
|
||||
"Gap", "Fragment", "Shard", "Spark", "Bubble",
|
||||
"Slip", "Trip", "Fall", "Ghost", "Shadow",
|
||||
"Blur", "Overflow", "Sinkhole", "Echo", "Mirage"
|
||||
};
|
||||
|
||||
constexpr const char* MythicAdjectives[] = {
|
||||
"Mythic", "Arcane", "Ancient", "Eternal", "Sacred",
|
||||
"Divine", "Forgotten", "Elder", "Primordial", "Venerable",
|
||||
"Runic", "Prophetic", "Colossal", "Imperial", "Regal",
|
||||
"Sovereign", "Mystic", "Occult", "Hidden", "Cryptic",
|
||||
"Ethereal", "Celestial", "Gnostic", "Hermetic", "Alchemical",
|
||||
"Astral", "Golden", "Iron", "Bronze", "Obsidian",
|
||||
"Silver", "Timeless", "Boundless", "Omnipotent", "Everlasting"
|
||||
};
|
||||
constexpr const char* MythicNouns[] = {
|
||||
"Aegis", "Helios", "Oracle", "Titan", "Rune",
|
||||
"Lex", "Codex", "Obelisk", "Monolith", "Temple",
|
||||
"Altar", "Scepter", "Crown", "Sigil", "Glyph",
|
||||
"Tome", "Relic", "Artifact", "Sanctum", "Citadel",
|
||||
"Bastion", "Spire", "Pillar", "Throne", "Vault",
|
||||
"Key", "Gate", "Bridge", "Seal", "Pact",
|
||||
"Covenant", "Legacy", "Epoch", "Era", "Myth"
|
||||
};
|
||||
|
||||
constexpr const char* CosmosAdjectives[] = {
|
||||
"Relativistic", "Baryonic", "Intergalactic", "Event-Horizon", "Singular",
|
||||
"Celestial", "Nebular", "Void-Born", "Astral", "Luminous",
|
||||
"Spectral", "Ionized", "Gravitational", "Ecliptic", "Zenithal",
|
||||
"Stellar", "Cosmological", "Parallactic", "Zero-Point", "Dark-Matter",
|
||||
"Radiant", "Orbital", "Supernova", "Hyper-Spatial", "Aetheric",
|
||||
"Cold-Void", "Infinite", "Dimensional", "Crystalline", "Tidal",
|
||||
"Planetary", "Solar", "Lunar", "Galactic", "Oblique"
|
||||
};
|
||||
constexpr const char* CosmosNouns[] = {
|
||||
"Pulsar", "Quasar", "Singularity", "Void", "Nebula",
|
||||
"Horizon", "Apex", "Zenith", "Equinox", "Corona",
|
||||
"Aperture", "Axis", "Parallax", "Cluster", "Constellation",
|
||||
"Vacuum", "Symmetry", "Continuum", "Flux", "Vortex",
|
||||
"Nova", "Eclipse", "Solenoid", "Sphere", "Vector",
|
||||
"Siderostat", "Sextant", "Obliquity", "Precession", "Azimuth",
|
||||
"Wavelength", "Frequency", "Radiance", "Entropy", "Magnitude"
|
||||
};
|
||||
|
||||
constexpr const char* GameAdjectives[] = {
|
||||
"Frame-Locked", "Pixel-Perfect", "Arcade", "Retro", "Hardcore",
|
||||
"Unlocked", "Godlike", "Buffed", "Nerfed", "Overclocked",
|
||||
"Clutch", "Lagless", "Sweaty", "Tryhard", "Broken",
|
||||
"Turbo", "Min-Max", "Rage-Quit", "No-Scope", "Frame-Perfect",
|
||||
"Savescum", "Co-Op", "Modded", "Patched", "Hotfixed",
|
||||
"Debugged", "Optimized", "Smoothed", "Playtest", "Sandbox",
|
||||
"Scripted", "Speedrun", "Cheat-Code", "Invincible", "Flawless"
|
||||
};
|
||||
constexpr const char* GameNouns[] = {
|
||||
"Frame", "Tick", "Sprite", "Polygon", "Shader",
|
||||
"Texture", "Voxel", "Render", "Hitbox", "Hurtbox",
|
||||
"Collision", "Input", "Viewport", "Level", "Checkpoint",
|
||||
"Boss", "Loot", "Quest", "Spawn", "Respawn",
|
||||
"Grind", "Scroll", "Tilemap", "Backdrop", "Rig",
|
||||
"Build", "Frag", "Gib", "Drawcall", "Pass",
|
||||
"Batch", "Delta", "Pool", "Arena", "Worker"
|
||||
};
|
||||
|
||||
constexpr std::array NameBanks = {
|
||||
NameBank { AnalysisAdjectives, AnalysisNouns, sizeof(AnalysisAdjectives) / sizeof(AnalysisAdjectives[0]), sizeof(AnalysisNouns) / sizeof(AnalysisNouns[0]) },
|
||||
NameBank { PerformanceAdjectives, PerformanceNouns, sizeof(PerformanceAdjectives) / sizeof(PerformanceAdjectives[0]), sizeof(PerformanceNouns) / sizeof(PerformanceNouns[0]) },
|
||||
NameBank { CoreAdjectives, CoreNouns, sizeof(CoreAdjectives) / sizeof(CoreAdjectives[0]), sizeof(CoreNouns) / sizeof(CoreNouns[0]) },
|
||||
NameBank { ModernAdjectives, ModernNouns, sizeof(ModernAdjectives) / sizeof(ModernAdjectives[0]), sizeof(ModernNouns) / sizeof(ModernNouns[0]) },
|
||||
NameBank { FailureAdjectives, FailureNouns, sizeof(FailureAdjectives) / sizeof(FailureAdjectives[0]), sizeof(FailureNouns) / sizeof(FailureNouns[0]) },
|
||||
NameBank { MythicAdjectives, MythicNouns, sizeof(MythicAdjectives) / sizeof(MythicAdjectives[0]), sizeof(MythicNouns) / sizeof(MythicNouns[0]) },
|
||||
NameBank { CosmosAdjectives, CosmosNouns, sizeof(CosmosAdjectives) / sizeof(CosmosAdjectives[0]), sizeof(CosmosNouns) / sizeof(CosmosNouns[0]) },
|
||||
NameBank { GameAdjectives, GameNouns, sizeof(GameAdjectives) / sizeof(GameAdjectives[0]), sizeof(GameNouns) / sizeof(GameNouns[0]) },
|
||||
};
|
||||
|
||||
constexpr std::array NameStructure = { "an", "aan", "nn" };
|
||||
|
||||
|
||||
std::string GenerateAbstractName()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::default_random_engine gen( rd() );
|
||||
std::uniform_int_distribution<uint32_t> dist( 0, UINT32_MAX );
|
||||
|
||||
const auto baseBank = NameBanks[dist( gen ) % NameBanks.size()];
|
||||
const char* structure = NameStructure[dist( gen ) % NameStructure.size()];
|
||||
|
||||
std::vector<std::string> parts;
|
||||
while( *structure )
|
||||
{
|
||||
const auto type = *structure++;
|
||||
assert( type == 'a' || type == 'n' );
|
||||
const auto bank = dist( gen ) % 6 == 0 ? NameBanks[dist( gen ) % NameBanks.size()] : baseBank;
|
||||
for(;;)
|
||||
{
|
||||
auto part = std::string( type == 'a' ? bank.adjectives[dist( gen ) % bank.numAdjectives] : bank.nouns[dist( gen ) % bank.numNouns] );
|
||||
if( std::ranges::find( parts, part ) == parts.end() )
|
||||
{
|
||||
parts.emplace_back( std::move( part ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::string ret = parts[0];
|
||||
for( size_t i=1; i<parts.size(); i++ )
|
||||
{
|
||||
ret += " " + parts[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
13
profiler/src/profiler/TracyNameGen.hpp
Normal file
13
profiler/src/profiler/TracyNameGen.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef __TRACYNAMEGEN_HPP__
|
||||
#define __TRACYNAMEGEN_HPP__
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
std::string GenerateAbstractName();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "TracyImGui.hpp"
|
||||
#include "TracyNameGen.hpp"
|
||||
#include "TracyPrint.hpp"
|
||||
#include "TracyView.hpp"
|
||||
#include "tracy_pdqsort.h"
|
||||
@@ -10,6 +13,7 @@ namespace tracy
|
||||
void View::AddAnnotation( int64_t start, int64_t end )
|
||||
{
|
||||
auto ann = std::make_shared<Annotation>();
|
||||
ann->text = GenerateAbstractName();
|
||||
ann->range.active = true;
|
||||
ann->range.min = start;
|
||||
ann->range.max = end;
|
||||
@@ -52,7 +56,22 @@ void View::DrawSelectedAnnotation()
|
||||
char buf[1024];
|
||||
buf[descsz] = '\0';
|
||||
memcpy( buf, desc, descsz );
|
||||
if( ImGui::InputTextWithHint( "##anndesc", "Describe annotation", buf, 256 ) )
|
||||
|
||||
const char* buttonText = ICON_FA_DICE;
|
||||
auto buttonSize = ImGui::CalcTextSize( buttonText );
|
||||
buttonSize.x += ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x - buttonSize.x );
|
||||
bool changed = ImGui::InputTextWithHint( "##anndesc", "Describe annotation", buf, 256 );
|
||||
ImGui::SameLine();
|
||||
if( ImGui::Button( buttonText ) )
|
||||
{
|
||||
changed = true;
|
||||
const auto name = GenerateAbstractName();
|
||||
const auto len = std::min( sizeof( buf ) - 1, name.size() );
|
||||
memcpy( buf, name.c_str(), len );
|
||||
buf[len] = '\0';
|
||||
}
|
||||
if( changed )
|
||||
{
|
||||
m_selectedAnnotation->text.assign( buf );
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "TracyImGui.hpp"
|
||||
#include "TracyNameGen.hpp"
|
||||
#include "TracyPrint.hpp"
|
||||
#include "TracyView.hpp"
|
||||
#include "tracy_pdqsort.h"
|
||||
@@ -55,8 +56,22 @@ void View::DrawInfo()
|
||||
char buf[256];
|
||||
buf[descsz] = '\0';
|
||||
memcpy( buf, desc.c_str(), descsz );
|
||||
ImGui::SetNextItemWidth( -1 );
|
||||
if( ImGui::InputTextWithHint( "##traceDesc", "Enter description of the trace", buf, 256 ) )
|
||||
|
||||
const char* buttonText = ICON_FA_DICE;
|
||||
auto buttonSize = ImGui::CalcTextSize( buttonText );
|
||||
buttonSize.x += ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x - buttonSize.x );
|
||||
bool changed = ImGui::InputTextWithHint( "##traceDesc", "Enter description of the trace", buf, 256 );
|
||||
ImGui::SameLine();
|
||||
if( ImGui::Button( buttonText ) )
|
||||
{
|
||||
changed = true;
|
||||
const auto name = GenerateAbstractName();
|
||||
const auto len = std::min( sizeof( buf ) - 1, name.size() );
|
||||
memcpy( buf, name.c_str(), len );
|
||||
buf[len] = '\0';
|
||||
}
|
||||
if( changed )
|
||||
{
|
||||
m_userData.SetDescription( buf );
|
||||
if( m_stcb ) UpdateTitle();
|
||||
|
||||
@@ -5257,7 +5257,7 @@ TRACY_API int32_t ___tracy_before_lock_shared_shared_lockable_ctx( struct __trac
|
||||
return static_cast<int32_t>(true);
|
||||
}
|
||||
|
||||
TRACY_API void ___tracy_after_locked_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata )
|
||||
TRACY_API void ___tracy_after_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata )
|
||||
{
|
||||
auto item = tracy::Profiler::QueueSerial();
|
||||
tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockSharedObtain );
|
||||
|
||||
@@ -171,8 +171,8 @@ struct ConcurrentQueueDefaultTraits
|
||||
#if defined(malloc) || defined(free)
|
||||
// Gah, this is 2015, stop defining macros that break standard code already!
|
||||
// Work around malloc/free being special macros:
|
||||
static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); }
|
||||
static inline void WORKAROUND_free(void* ptr) { return free(ptr); }
|
||||
static inline void* WORKAROUND_malloc(size_t size) { return tracy::tracy_malloc(size); }
|
||||
static inline void WORKAROUND_free(void* ptr) { return tracy::tracy_free(ptr); }
|
||||
static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); }
|
||||
static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); }
|
||||
#else
|
||||
|
||||
@@ -391,7 +391,7 @@ TRACY_API void ___tracy_after_lock_shared_lockable_ctx( struct __tracy_shared_lo
|
||||
TRACY_API void ___tracy_after_unlock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata );
|
||||
TRACY_API void ___tracy_after_try_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, int32_t acquired );
|
||||
TRACY_API int32_t ___tracy_before_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata );
|
||||
TRACY_API void ___tracy_after_locked_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata );
|
||||
TRACY_API void ___tracy_after_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata );
|
||||
TRACY_API void ___tracy_after_unlock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata );
|
||||
TRACY_API void ___tracy_after_try_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, int32_t acquired );
|
||||
TRACY_API void ___tracy_mark_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, const struct ___tracy_source_location_data* srcloc );
|
||||
@@ -414,7 +414,7 @@ TRACY_API void ___tracy_custom_name_shared_lockable_ctx( struct __tracy_shared_l
|
||||
#define TracyCSharedLockAfterUnlock( lock ) ___tracy_after_unlock_shared_lockable_ctx( lock );
|
||||
#define TracyCSharedLockAfterTryLock( lock, acquired ) ___tracy_after_try_lock_shared_lockable_ctx( lock, acquired );
|
||||
#define TracyCSharedLockBeforeSharedLock( lock ) ___tracy_before_lock_shared_shared_lockable_ctx( lock );
|
||||
#define TracyCSharedLockAfterSharedLock( lock ) ___tracy_after_locked_shared_shared_lockable_ctx( lock );
|
||||
#define TracyCSharedLockAfterSharedLock( lock ) ___tracy_after_lock_shared_shared_lockable_ctx( lock );
|
||||
#define TracyCSharedLockAfterSharedUnlock( lock ) ___tracy_after_unlock_shared_shared_lockable_ctx( lock );
|
||||
#define TracyCSharedLockAfterTrySharedLock( lock, acquired ) ___tracy_after_try_lock_shared_shared_lockable_ctx( lock, acquired );
|
||||
#define TracyCSharedLockMark( lock ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { NULL, __func__, TracyFile, (uint32_t)TracyLine, 0 }; ___tracy_mark_shared_lockable_ctx( lock, &TracyConcat(__tracy_source_location,TracyLine) );
|
||||
|
||||
26
stracy/CMakeLists.txt
Normal file
26
stracy/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(NO_STATISTICS ON)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
project(
|
||||
tracy-import-strace
|
||||
LANGUAGES C CXX
|
||||
VERSION ${TRACY_VERSION_STRING}
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
add_executable(tracy-import-strace
|
||||
src/stracy.cpp
|
||||
)
|
||||
add_git_ref(tracy-import-strace)
|
||||
target_link_libraries(tracy-import-strace PRIVATE TracyServer)
|
||||
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT tracy-import-strace)
|
||||
378
stracy/src/stracy.cpp
Normal file
378
stracy/src/stracy.cpp
Normal file
@@ -0,0 +1,378 @@
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../../server/TracyFileWrite.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
static void Usage()
|
||||
{
|
||||
printf( "tracy-import-strace %d.%d.%d / %s\n\n",
|
||||
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: tracy-import-strace input.strace output.tracy\n" );
|
||||
printf( " tracy-import-strace - output.tracy (read from stdin)\n\n" );
|
||||
printf( "Recommended strace invocation:\n" );
|
||||
printf( " strace -ttt -T -f -o trace.log <program> [args...]\n" );
|
||||
printf( " strace -ttt -T -f -p <pid> -o trace.log\n\n" );
|
||||
printf( "Mapped events:\n" );
|
||||
printf( " syscall entry/return -> timeline zones\n" );
|
||||
printf( " signals -> messages\n" );
|
||||
printf( " process exit/kill -> messages\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// Parse "SSSSSSSSSS.UUUUUU" → nanoseconds without floating-point precision loss.
|
||||
// Advances *p past the consumed characters. Returns UINT64_MAX on parse error.
|
||||
static uint64_t ParseTimestamp( const char*& p )
|
||||
{
|
||||
if( !isdigit( (unsigned char)*p ) ) return UINT64_MAX;
|
||||
|
||||
uint64_t sec = 0;
|
||||
while( isdigit( (unsigned char)*p ) )
|
||||
sec = sec * 10 + ( *p++ - '0' );
|
||||
|
||||
if( *p != '.' ) return UINT64_MAX;
|
||||
p++;
|
||||
|
||||
uint64_t usec = 0;
|
||||
int digits = 0;
|
||||
while( isdigit( (unsigned char)*p ) && digits < 6 )
|
||||
{
|
||||
usec = usec * 10 + ( *p++ - '0' );
|
||||
digits++;
|
||||
}
|
||||
for( ; digits < 6; digits++ ) usec *= 10; // pad to microseconds if fewer digits
|
||||
while( isdigit( (unsigned char)*p ) ) p++; // discard extra precision
|
||||
|
||||
return sec * 1000000000ULL + usec * 1000ULL;
|
||||
}
|
||||
|
||||
// Consume "[pid N]" prefix and return N, or return default_tid if not present.
|
||||
// Advances *p past the consumed characters (including trailing space).
|
||||
static uint64_t ConsumePidPrefix( const char*& p, uint64_t default_tid )
|
||||
{
|
||||
if( p[0] != '[' || strncmp( p, "[pid", 4 ) != 0 ) return default_tid;
|
||||
|
||||
const char* q = p + 4;
|
||||
while( *q == ' ' ) q++;
|
||||
if( !isdigit( (unsigned char)*q ) ) return default_tid;
|
||||
|
||||
uint64_t tid = 0;
|
||||
while( isdigit( (unsigned char)*q ) )
|
||||
tid = tid * 10 + ( *q++ - '0' );
|
||||
|
||||
while( *q == ' ' ) q++;
|
||||
if( *q != ']' ) return default_tid;
|
||||
|
||||
p = q + 1;
|
||||
return tid;
|
||||
}
|
||||
|
||||
// Parse "<N.NNNNNN>" trailing duration → nanoseconds. Returns 0 if not present.
|
||||
static uint64_t ParseTrailingDuration( const char* line )
|
||||
{
|
||||
const char* p = line + strlen( line );
|
||||
while( p > line && strchr( " \r\n", p[-1] ) ) p--;
|
||||
if( p == line || p[-1] != '>' ) return 0;
|
||||
p--;
|
||||
|
||||
const char* end = p;
|
||||
while( p > line && p[-1] != '<' ) p--;
|
||||
if( p == line || p[-1] != '<' ) return 0;
|
||||
|
||||
char tmp[32];
|
||||
size_t len = end - p;
|
||||
if( len == 0 || len >= sizeof( tmp ) ) return 0;
|
||||
memcpy( tmp, p, len );
|
||||
tmp[len] = '\0';
|
||||
|
||||
double dur_sec = 0.0;
|
||||
if( sscanf( tmp, "%lf", &dur_sec ) != 1 ) return 0;
|
||||
return (uint64_t)( dur_sec * 1e9 );
|
||||
}
|
||||
|
||||
// Return the syscall name — the identifier before the first '('. Empty on failure.
|
||||
static std::string ParseSyscallName( const char* p )
|
||||
{
|
||||
const char* start = p;
|
||||
while( *p && *p != '(' && *p != ' ' && *p != '\n' ) p++;
|
||||
if( p == start || *p != '(' ) return {};
|
||||
return std::string( start, p );
|
||||
}
|
||||
|
||||
// Extract syscall arguments for a COMPLETE call line.
|
||||
// Finds the last " = " (the retval separator), then the ')' before it.
|
||||
// This correctly handles " = " appearing inside string arguments.
|
||||
static std::string ExtractArgsComplete( const char* line_start )
|
||||
{
|
||||
const char* open = strchr( line_start, '(' );
|
||||
if( !open ) return {};
|
||||
const char* args = open + 1;
|
||||
|
||||
// Walk the whole line to find the last " = "
|
||||
const char* last_eq = nullptr;
|
||||
for( const char* p = args; *p; p++ )
|
||||
if( p[0] == ' ' && p[1] == '=' && p[2] == ' ' )
|
||||
last_eq = p;
|
||||
|
||||
if( !last_eq ) return {};
|
||||
|
||||
// Find the closing ')' that immediately precedes " = "
|
||||
const char* close = last_eq;
|
||||
while( close > args && *close != ')' ) close--;
|
||||
if( close <= args ) return {};
|
||||
|
||||
return std::string( args, close );
|
||||
}
|
||||
|
||||
// Extract syscall arguments for an UNFINISHED call line, up to the delimiter.
|
||||
static std::string ExtractArgsUnfinished( const char* line_start, const char* delim )
|
||||
{
|
||||
const char* open = strchr( line_start, '(' );
|
||||
if( !open ) return {};
|
||||
const char* args = open + 1;
|
||||
|
||||
const char* end = strstr( args, delim );
|
||||
if( !end || end <= args ) return {};
|
||||
while( end > args && end[-1] == ' ' ) end--; // trim trailing space
|
||||
return std::string( args, end );
|
||||
}
|
||||
|
||||
// Extract a quoted string from strace arg output, e.g. the "name" in
|
||||
// prctl(PR_SET_NAME, "name") = 0. Returns empty string if not found.
|
||||
static std::string ExtractQuotedString( const char* p )
|
||||
{
|
||||
const char* open = strchr( p, '"' );
|
||||
if( !open ) return {};
|
||||
open++;
|
||||
const char* close = strchr( open, '"' );
|
||||
if( !close ) return {};
|
||||
return std::string( open, close );
|
||||
}
|
||||
|
||||
// Parse the integer return value from a complete strace line " = N <dur>".
|
||||
// Returns -1 on failure.
|
||||
static int64_t ParseRetval( const char* line )
|
||||
{
|
||||
const char* eq = strstr( line, " = " );
|
||||
if( !eq ) return -1;
|
||||
const char* p = eq + 3;
|
||||
bool neg = ( *p == '-' );
|
||||
if( neg ) p++;
|
||||
if( !isdigit( (unsigned char)*p ) ) return -1;
|
||||
int64_t v = 0;
|
||||
while( isdigit( (unsigned char)*p ) ) v = v * 10 + (*p++ - '0');
|
||||
return neg ? -v : v;
|
||||
}
|
||||
|
||||
struct PendingEntry
|
||||
{
|
||||
uint64_t ts_begin;
|
||||
std::string syscall_name;
|
||||
std::string args_text;
|
||||
};
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
if( argc != 3 ) Usage();
|
||||
|
||||
const char* input_path = argv[1];
|
||||
const char* output_path = argv[2];
|
||||
|
||||
FILE* fin = ( strcmp( input_path, "-" ) == 0 ) ? stdin : fopen( input_path, "r" );
|
||||
if( !fin )
|
||||
{
|
||||
fprintf( stderr, "Cannot open input: %s\n", input_path );
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<tracy::Worker::ImportEventTimeline> timeline;
|
||||
std::vector<tracy::Worker::ImportEventMessages> messages;
|
||||
std::vector<tracy::Worker::ImportEventPlots> plots; // required by Worker API; unused
|
||||
std::unordered_map<uint64_t, std::string> threadNames;
|
||||
std::unordered_map<uint64_t, PendingEntry> pending; // tid → in-flight syscall
|
||||
|
||||
char buf[65536];
|
||||
// TID used when strace output has no [pid N] prefix (single-threaded traces or
|
||||
// the very first calls of a process before any fork).
|
||||
constexpr uint64_t kDefaultTid = 0;
|
||||
|
||||
while( fgets( buf, sizeof( buf ), fin ) )
|
||||
{
|
||||
const char* p = buf;
|
||||
|
||||
// Only process lines whose first character is a digit.
|
||||
// Silently skips strace warnings, "Process N attached/detached", etc.
|
||||
if( !isdigit( (unsigned char)*p ) ) continue;
|
||||
|
||||
// Detect which line format strace used:
|
||||
// Format A (-o to file/pipe): "PID TIMESTAMP syscall..."
|
||||
// Format B (to stderr/tty): "TIMESTAMP [pid N] syscall..."
|
||||
// Distinguish by whether the first numeric token contains a '.' (timestamp)
|
||||
// or is followed by a space without a '.' (bare PID).
|
||||
uint64_t tid = kDefaultTid;
|
||||
{
|
||||
const char* q = p;
|
||||
while( isdigit( (unsigned char)*q ) ) q++;
|
||||
if( *q == ' ' )
|
||||
{
|
||||
// Format A: leading PID token
|
||||
while( isdigit( (unsigned char)*p ) ) tid = tid * 10 + (*p++ - '0');
|
||||
while( *p == ' ' ) p++;
|
||||
}
|
||||
// else: Format B — timestamp comes first, [pid N] (if any) follows it
|
||||
}
|
||||
|
||||
uint64_t ts = ParseTimestamp( p );
|
||||
if( ts == UINT64_MAX ) continue;
|
||||
|
||||
while( *p == ' ' ) p++;
|
||||
|
||||
// Format B may carry "[pid N]" after the timestamp; Format A already has tid.
|
||||
if( tid == kDefaultTid )
|
||||
tid = ConsumePidPrefix( p, kDefaultTid );
|
||||
|
||||
while( *p == ' ' ) p++;
|
||||
|
||||
// Register the thread name the first time we see this TID.
|
||||
if( threadNames.find( tid ) == threadNames.end() )
|
||||
{
|
||||
char name[32];
|
||||
if( tid == kDefaultTid )
|
||||
snprintf( name, sizeof( name ), "main" );
|
||||
else
|
||||
snprintf( name, sizeof( name ), "%" PRIu64, tid );
|
||||
threadNames[tid] = name;
|
||||
}
|
||||
|
||||
// --- Resumed syscall -------------------------------------------
|
||||
// "<... SYSCALL resumed>) = RETVAL <DUR>"
|
||||
if( strncmp( p, "<...", 4 ) == 0 )
|
||||
{
|
||||
const char* name_start = p + 4;
|
||||
while( *name_start == ' ' ) name_start++;
|
||||
const char* resumed = strstr( name_start, " resumed" );
|
||||
if( !resumed ) continue;
|
||||
|
||||
std::string syscall_name( name_start, resumed );
|
||||
|
||||
auto it = pending.find( tid );
|
||||
uint64_t ts_begin = ( it != pending.end() ) ? it->second.ts_begin : ts;
|
||||
std::string args_text = ( it != pending.end() ) ? it->second.args_text : "";
|
||||
if( it != pending.end() ) pending.erase( it );
|
||||
|
||||
// ts = timestamp of the return line; use as zone end.
|
||||
timeline.push_back( { tid, ts_begin, syscall_name, std::move( args_text ), false, "", 0 } );
|
||||
timeline.push_back( { tid, ts, "", "", true, "", 0 } );
|
||||
}
|
||||
// --- Process exit / kill ----------------------------------------
|
||||
// "+++ exited with N +++" | "+++ killed by SIGNAL +++"
|
||||
else if( strncmp( p, "+++", 3 ) == 0 )
|
||||
{
|
||||
std::string raw( p );
|
||||
while( !raw.empty() && strchr( "\r\n", raw.back() ) ) raw.pop_back();
|
||||
messages.push_back( { tid, ts, std::to_string( tid ) + ": " + raw } );
|
||||
}
|
||||
// --- Signal ------------------------------------------------------
|
||||
// "--- SIGNAME {...} ---"
|
||||
else if( strncmp( p, "---", 3 ) == 0 )
|
||||
{
|
||||
std::string raw( p );
|
||||
while( !raw.empty() && strchr( "\r\n", raw.back() ) ) raw.pop_back();
|
||||
messages.push_back( { tid, ts, std::to_string( tid ) + ": " + raw } );
|
||||
}
|
||||
// --- Regular syscall (complete or unfinished) --------------------
|
||||
else
|
||||
{
|
||||
std::string syscall_name = ParseSyscallName( p );
|
||||
if( syscall_name.empty() ) continue;
|
||||
|
||||
if( strstr( p, "<unfinished ...>" ) )
|
||||
{
|
||||
// Syscall blocked — park in the pending table.
|
||||
std::string args = ExtractArgsUnfinished( p, " <unfinished" );
|
||||
pending[tid] = PendingEntry{ ts, std::move( syscall_name ), std::move( args ) };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complete call with return value (and optional duration from -T).
|
||||
uint64_t dur_ns = ParseTrailingDuration( buf );
|
||||
std::string args = ExtractArgsComplete( p );
|
||||
|
||||
// prctl(PR_SET_NAME, "name") — update the thread's display name.
|
||||
if( syscall_name == "prctl" && strstr( p, "PR_SET_NAME" ) )
|
||||
{
|
||||
std::string name = ExtractQuotedString( strstr( p, "PR_SET_NAME" ) );
|
||||
if( !name.empty() )
|
||||
threadNames[tid] = std::move( name );
|
||||
}
|
||||
// clone/clone3 with CLONE_THREAD — pre-name the child thread so it
|
||||
// has a label before it calls prctl itself.
|
||||
else if( ( syscall_name == "clone" || syscall_name == "clone3" ) &&
|
||||
strstr( p, "CLONE_THREAD" ) )
|
||||
{
|
||||
int64_t child_tid = ParseRetval( buf );
|
||||
if( child_tid > 0 && threadNames.find( (uint64_t)child_tid ) == threadNames.end() )
|
||||
{
|
||||
char name[32];
|
||||
snprintf( name, sizeof( name ), "%" PRId64, child_tid );
|
||||
threadNames[(uint64_t)child_tid] = name;
|
||||
}
|
||||
}
|
||||
|
||||
timeline.push_back( { tid, ts, syscall_name, std::move( args ), false, "", 0 } );
|
||||
timeline.push_back( { tid, ts + dur_ns, "", "", true, "", 0 } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( fin != stdin ) fclose( fin );
|
||||
|
||||
// Sort by timestamp. strace output is mostly ordered, but interleaved threads
|
||||
// and resumed events can place end-of-zone before begin-of-zone after sorting.
|
||||
std::stable_sort( timeline.begin(), timeline.end(),
|
||||
[]( const auto& a, const auto& b ) { return a.timestamp < b.timestamp; } );
|
||||
std::stable_sort( messages.begin(), messages.end(),
|
||||
[]( const auto& a, const auto& b ) { return a.timestamp < b.timestamp; } );
|
||||
|
||||
// Shift all timestamps so the trace starts at t = 0.
|
||||
uint64_t mts = UINT64_MAX;
|
||||
for( const auto& e : timeline ) mts = std::min( mts, e.timestamp );
|
||||
for( const auto& e : messages ) mts = std::min( mts, e.timestamp );
|
||||
if( mts == UINT64_MAX ) mts = 0;
|
||||
|
||||
for( auto& e : timeline ) e.timestamp -= mts;
|
||||
for( auto& e : messages ) e.timestamp -= mts;
|
||||
|
||||
const size_t zone_count = timeline.size() / 2;
|
||||
fprintf( stderr, "Parsed %zu zones, %zu messages, %zu threads\n",
|
||||
zone_count, messages.size(), threadNames.size() );
|
||||
|
||||
// Tracy Worker expects basenames, not full paths.
|
||||
auto basename = []( const char* path ) -> const char* {
|
||||
const char* s = path;
|
||||
for( const char* q = path; *q; q++ )
|
||||
if( *q == '/' || *q == '\\' ) s = q + 1;
|
||||
return s;
|
||||
};
|
||||
|
||||
tracy::Worker worker( basename( output_path ), basename( input_path ),
|
||||
timeline, messages, plots, threadNames );
|
||||
|
||||
auto w = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( output_path, tracy::FileCompression::Fast ) );
|
||||
if( !w )
|
||||
{
|
||||
fprintf( stderr, "Cannot open output file: %s\n", output_path );
|
||||
return 1;
|
||||
}
|
||||
worker.Write( *w, false );
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user