From 037b8ea0a949379dd4a81b2aa9cb87186ff00843 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Fri, 29 Sep 2017 00:21:50 +0200 Subject: [PATCH] Add README. --- README.md | 58 ++++++++ doc/cost.png | Bin 0 -> 2357 bytes doc/design.svg | 370 +++++++++++++++++++++++++++++++++++++++++++++++ doc/profiler.png | Bin 0 -> 10463 bytes 4 files changed, 428 insertions(+) create mode 100644 README.md create mode 100644 doc/cost.png create mode 100755 doc/design.svg create mode 100644 doc/profiler.png diff --git a/README.md b/README.md new file mode 100644 index 0000000000..41819d1c7d --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Tracy Profiler + +Tracy is a frame profiler that can be used for remote or embedded telemetry of your application. + +![](doc/profiler.png) + +Tracy requires compiler support for C++14 and Thread Local Storage. There are no other requirements. The following platforms are confirmed to be working: + +- Windows (x64) +- Linux (x64, ARM64) +- Android (ARM) + +Other platforms should also work fine. + +### High-level overview + +![](doc/design.svg) + +Tracy is split into client and server side. The client side collects events using a high-efficiency queue and awaits for an incoming connection. The server part connects to client and receives collected data from the client, which is then reconstructed into a viewable timeline. The transfer is performed using a TCP connection. + +## Usage instructions + +#### Initial client setup + +Add source files from `tracy/client` and `tracy/common` to your project. That's all. Tracy is now integrated into your application. + +#### Marking zones + +To begin data collection, tracy requires that you manually instrument your application (automatic tracing of every entered function is not feasible due to the amount of data that would generate). All the user-facing interface is contained in the `tracy/client/Tracy.hpp` header file. + +To slice the program's execution recording into frame-sized chunks, put the `FrameMark` macro after you have completed rendering the frame. Ideally that would be right after the swap buffers command. Note that this step is optional, as some applications (for example: a compression utility) do not have the concept of a frame. + +To record a zone's execution time add the `ZoneScoped` macro at the beginning of the scope you want to measure. This will automatically record function name, source file name and location. Optionally you may use the `ZoneScopedC( 0xBBGGRR )` macro to set a custom color for the zone. Note that the color value will be constant in the recording (don't try to parametrize it). After you have marked the zone, you may further parametrize it. + +Use the `ZoneName( const char* name )` macro to set a custom name for the zone, which will be displayed instead of the function's name in the timeline view. The text string that you have provided **must** be accessible indefinitely at the given address. Tracy does not guarantee at which point in time it will be sent to the server and there is no notification when it happens. + +Use the `ZoneText( const char* text, size_t size )` macro to add a custom text string that will be displayed along the zone information (for example, name of the file you are opening). Note that every time `ZoneText` is invoked, a memory allocation is performed to store an internal copy of the data. The string you have provided is not used by tracy. + +#### Running the server + +The easiest way to get going is to build the standalone server, available in the `standalone` directory. You can connect to localhost or remote clients and view the collected data right away. + +Alternatively, you may want to embed the server in your application, the same which is running the client part of tracy. Doing so requires that you also include the `server` and `imgui` directories. Include the `tracy/server/TracyView.hpp` header file, create an instance of the `tracy::View` class and call its `Draw()` method every frame. Unfortunately, there's also the hard part - you need to integrate the imgui library into the innards of your program. How to do so is outside the scope of this document. + +## Good practices + +- Remember to set thread names for proper identification of threads. You may use the functions exposed in the `tracy/common/TracySystem.hpp` header to do so. Note that the max thread name length in pthreads is limited to 15 characters. Proper thread naming support is available in MSVC only if you are using Windows SDK 10.0.15063 or newer (a tracy-specific workaround may be added in the future). +- Enable the MSVC String Pooling option (`/GF`) or the gcc counterpart, `-fmerge-constants`. This will reduce number of queries the server needs to perform to the client. Note that these options are enabled in optimized builds by default. + +## Practical considerations + +Tracy's time measurement precision is not infinite. It's only as good as the system-provided timers are. +- On the embedded ARM-based systems you can expect to have something around 1 µs time resolution. +- On x86 (currently only implemented on Windows) the time resolution depends on the hardware implementation of the RDTSCP instruction and typically is in the low nanoseconds. This may vary from one micro-architecture to another and requires a fairly modern (Sandy Bridge) processor for reliable results. + +While the data collection is very lightweight, it is not completely free. Each recorded zone event has a cost, which tracy tries to calculate and display on the timeline view, as a red zone. Note that this is an *approximation* of the real cost, which ignores many important factors. For example, you can't determine the impact of cache effects. The CPU frequency may be reduced in some situations, which will increase the recorded time, but the displayed profiler cost will not compensate for that. + +![](doc/cost.png) diff --git a/doc/cost.png b/doc/cost.png new file mode 100644 index 0000000000000000000000000000000000000000..cd83e98628d12dc0ee8d59aee24ba243ac185ab0 GIT binary patch literal 2357 zcmZvedps2V9>>?(Sljlrw0c&t(dM$d(ROj#Bx~JjTja9KdPu|O7R!=5wI=t7=p6Ob zBDq$sSt7|0qMOsxsZ`W-7dDC>E5$Q({&>#ob>{b)`Fy|M-^_Qu^L_p1J(KC=xO)X| z4Gx7ut>EmjaY3Qb5Mq~Mm62oZ^1v2E)Wmx{4x>=ZS1s;JsIyWM5>$=kIM}MbQqx|I zU&mS&HHw6Ef_Q>R>oAeXFEkQyQ7G#OKc7gygSs(6k%79qISx(-P5e|*D5V-Fd!8+E zj|<6U9eHsxBa!d34N>WHk$Ev znlaPD3%SN@iGARH_Vw*PXa^kh>kbU+I%GsW6xq*9SoCJyfqN0fyJ<%dUf zM9|0)?mdSMsgbT1qg*Rv4UJ-BugAt!#TgpKCtQqAyqIWcl(?_xh@sKZqt}i~s*_wr zpn*|xN=piloFYp}kx5fzvNQvuG~uy~y}8Gu&SaUAvxIqB!pm90p6u+V6FIURr?A}P zH78HXPUbb{*(K+@iVHHU&Yo?Tip-YENs-EAg?w=lUtD4lR>}%3<%N}5T9jFama#+2 zgn8v`QMr|S9INFXs`Fp9BSeDw+I4T`TnhL{;ff&?Oe}x_n3BJUc0d8#&3t@nVAa<3-d4EdZAD% zUpO{wUhFe@p=!c&8&XoTtLmP6X)e+EzW*q9a`P~j-92{G$S?N_-e*Fq3lBm+{Ixyv z^|{TCh93)F%miKTD|llu&_4TRc-T;K*w~%9T=$+fS{Y4YkXFae>|8s>#o}{tLq|tZ z>LdaQ?6k)u@tJ1pLAnRo2`C!czq?q`g*Dgof{rHyyF8}+$jKkF)yw@+N)+>^ihfRM^*Y~A7_;zGLrsO|+Y6P`>nN%!LUj7mF6Qux{FjB_b(`RmUa1*{`O3)+ z{0v`v4(axbnY#jz13qteeG9>2jK79zDAs=SN?iuh_r=a5MluJKEB?Kz7xy1bbJ-7+ za2XcwV6Qi>7~%Kgc!vS{w*Ut6Fyc=-TFuh~K(*y&^z#R-7VyiDp zRD{c-u+=qxR}VCIy05-;F=T239Qm=8M6dD&PW+F00fkGxQ1in1&!dZGJs-F@ua&JS zSPNcEMNygv1^;yQo_|+-u4h(GPkTw+G!%aSQEnYgJh05-v2w?peC~*5=)3LpdZCH0 z3;3p?e>%ex7S3O;N{V~+R;_2=zgDqB<>gn|1!yLLI=MSqZKKWRowneFG^U2a!d#_@ zDa-D65W%dH3G01B>VoX~>B7e?kr z_yL$-v`-NPeKv3+*yif80#ncrHhmqKbO7|I^^v#-P9S$UucT3uwsj0Dff@~j{ZBn3 z*A#dA;iR!9-?kK`XMCP}W}eyTuOm3HkgbL82zcWt?e0yzv7Z?Ix}u~`k`~-VSRc;F zI={MWl;b78KF$4HoGaNbi4`(KqJ{3P^ntcfpK=-OA}l^p(I813cK#+h;rO*2Te{rN=w&4MaJ> z`Mb7EBi(g`2RizuT{B>&11!|Y9E;j?*|GAzA$m9OIqN11WMv(6SYy6RaoiI2sA_DZA*XdY+H%~nXPb4UVQ``hX%e5%@(zgHwUCcU~v0gkK zHSX1Y81|Tpz=kAA=d~#+`X!GTg{FK)CCJk8Nd;vne06PN_`e5|H0T4)T{QvxmpNYjA&CRa!xp`mA-&J>G@qdP6>u6JJ1swYe D3DXih literal 0 HcmV?d00001 diff --git a/doc/design.svg b/doc/design.svg new file mode 100755 index 0000000000..d9ac570b5e --- /dev/null +++ b/doc/design.svg @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Thread 1 + + + + Thread 2 + + + + Thread 3 + + + + Tracyclient + + + + + + + + Tracyserver + + + + + + + + + DB + + + + + Display + + + + diff --git a/doc/profiler.png b/doc/profiler.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6fe135f2f4342891c81fb3f74ba8af18815662 GIT binary patch literal 10463 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 literal 0 HcmV?d00001