mirror of
https://github.com/wolfpld/tracy.git
synced 2026-06-08 08:33:48 +00:00
Compare commits
920 Commits
v0.13.1
...
e5aa8eba51
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5aa8eba51 | ||
|
|
7437c41514 | ||
|
|
f441a5070b | ||
|
|
00b6abd67b | ||
|
|
e4e3d75eb8 | ||
|
|
fc5318dcad | ||
|
|
661c664b75 | ||
|
|
6dbebca666 | ||
|
|
73d78ad517 | ||
|
|
e5371d7987 | ||
|
|
9806f35714 | ||
|
|
d40289d594 | ||
|
|
86fbe529ed | ||
|
|
9b169ef3f9 | ||
|
|
64797dc735 | ||
|
|
76797799c0 | ||
|
|
19549693a0 | ||
|
|
10d64d69b5 | ||
|
|
d89c956394 | ||
|
|
79467b4b31 | ||
|
|
ae275f239d | ||
|
|
77fb86155f | ||
|
|
e627fcce98 | ||
|
|
e80893ac20 | ||
|
|
912f8c048c | ||
|
|
d16f627cbc | ||
|
|
7cb98245ce | ||
|
|
3974cc8026 | ||
|
|
55d5436fb9 | ||
|
|
2b11785b05 | ||
|
|
715815374d | ||
|
|
4f64b974c6 | ||
|
|
7c58db4c0a | ||
|
|
f9f772e507 | ||
|
|
af2a369dff | ||
|
|
bebf20846f | ||
|
|
5f6bc2238a | ||
|
|
4564a626b2 | ||
|
|
e95a757e6c | ||
|
|
2ebf18be7f | ||
|
|
fc97af4c68 | ||
|
|
8eada19734 | ||
|
|
a56b08e539 | ||
|
|
9c9dca8ea5 | ||
|
|
268cab7f89 | ||
|
|
aeda64d36b | ||
|
|
f5526d01a2 | ||
|
|
20e6835da6 | ||
|
|
6b92ec1a23 | ||
|
|
45e90549aa | ||
|
|
1690ac0d9d | ||
|
|
a8fab8c977 | ||
|
|
a0b50ee68e | ||
|
|
896a59d00b | ||
|
|
d3db50c201 | ||
|
|
21434e9877 | ||
|
|
185482c0d9 | ||
|
|
a27dae3e88 | ||
|
|
7d139a7bf1 | ||
|
|
02e279bd38 | ||
|
|
4e593d91f5 | ||
|
|
75e721cf0c | ||
|
|
c4321bc83c | ||
|
|
224ff6d0e8 | ||
|
|
cc76d0f60e | ||
|
|
d4d1f78263 | ||
|
|
c570288145 | ||
|
|
98b61e0096 | ||
|
|
5c75032cad | ||
|
|
1381b427db | ||
|
|
21b9f50cea | ||
|
|
cbad012980 | ||
|
|
d4298f7794 | ||
|
|
f113bbb212 | ||
|
|
0b4128f76c | ||
|
|
bab8c8eb54 | ||
|
|
cbe5347593 | ||
|
|
189e8a1a89 | ||
|
|
a5316d525c | ||
|
|
c3e9ff17da | ||
|
|
0cf438a78e | ||
|
|
e37b12aacb | ||
|
|
dd53f721ac | ||
|
|
058d5ca7c3 | ||
|
|
eef525243d | ||
|
|
1f2bbe918f | ||
|
|
0089fab94c | ||
|
|
0778ef85c6 | ||
|
|
5e6d872940 | ||
|
|
b7ed5bd9ef | ||
|
|
de1f84d52b | ||
|
|
9f88bc6d04 | ||
|
|
bd0ba00513 | ||
|
|
0e52e387bd | ||
|
|
e93ddd2aa7 | ||
|
|
33905b2f15 | ||
|
|
d06755652f | ||
|
|
dbdbd710d8 | ||
|
|
15ee99ae41 | ||
|
|
17f6be4ad4 | ||
|
|
30fd92de0f | ||
|
|
0b27b9ec1a | ||
|
|
cc4b7dcea9 | ||
|
|
4a9e3ea095 | ||
|
|
55de5bc5ca | ||
|
|
2e87eecc67 | ||
|
|
74eea83051 | ||
|
|
c9fa58f2bb | ||
|
|
b7fdc8c0eb | ||
|
|
f5581d7dcb | ||
|
|
b682a77f82 | ||
|
|
800334a953 | ||
|
|
9da66d4c6b | ||
|
|
04c1a84159 | ||
|
|
b736e4590e | ||
|
|
6b28296ef3 | ||
|
|
c11fd010d8 | ||
|
|
121e10c837 | ||
|
|
e57c0869df | ||
|
|
4cf754f3fe | ||
|
|
22d1c2d3c3 | ||
|
|
884415264b | ||
|
|
ea8cbc849f | ||
|
|
20d7135c24 | ||
|
|
aaf1304308 | ||
|
|
37750e27ab | ||
|
|
ab62c00be5 | ||
|
|
757b582ae7 | ||
|
|
b81ef061f8 | ||
|
|
702b16977b | ||
|
|
3f1e572a23 | ||
|
|
00d4e9ba21 | ||
|
|
ee37bb40f0 | ||
|
|
215199239f | ||
|
|
f93d17a96f | ||
|
|
84570487bf | ||
|
|
03227f7ae1 | ||
|
|
05c2638467 | ||
|
|
fd68959223 | ||
|
|
5c0263c2f0 | ||
|
|
5fd0dc569b | ||
|
|
ec1c2d4267 | ||
|
|
b7e405ebbd | ||
|
|
e8a0676c0b | ||
|
|
32b46a3b90 | ||
|
|
cffd0007c5 | ||
|
|
564e0e1e71 | ||
|
|
6eb9bf81ad | ||
|
|
f99a02505a | ||
|
|
f63c25fb79 | ||
|
|
a0af290db5 | ||
|
|
65db47ed0d | ||
|
|
d06c4ce816 | ||
|
|
c91215bd99 | ||
|
|
dfac4cb8a4 | ||
|
|
34a1bb6107 | ||
|
|
6957f968a4 | ||
|
|
896cdc9462 | ||
|
|
efc33a09d9 | ||
|
|
61875e1bd0 | ||
|
|
e003885946 | ||
|
|
d40806caff | ||
|
|
e7c71c991c | ||
|
|
69855a1416 | ||
|
|
60247b68d3 | ||
|
|
b48ef9a2ab | ||
|
|
e59601cf1c | ||
|
|
43472f1226 | ||
|
|
c1177a0cde | ||
|
|
91c0b1e42b | ||
|
|
14a1a3227e | ||
|
|
8582715fa5 | ||
|
|
d51634ad24 | ||
|
|
4bbfe5edbd | ||
|
|
55c5348fad | ||
|
|
5b4dcd4655 | ||
|
|
a380046e17 | ||
|
|
6494285283 | ||
|
|
ec92f2fac3 | ||
|
|
07b2600c08 | ||
|
|
562a120087 | ||
|
|
33fe84532e | ||
|
|
1f1738c221 | ||
|
|
eb411b1138 | ||
|
|
cf5325032e | ||
|
|
f74a452b89 | ||
|
|
365c99e601 | ||
|
|
b8f68e3fae | ||
|
|
5d4ba366ba | ||
|
|
a7937d2710 | ||
|
|
efd7ec262d | ||
|
|
f0f579172b | ||
|
|
4c6157d249 | ||
|
|
6ae6fb741e | ||
|
|
4ab7ef301e | ||
|
|
41f1172774 | ||
|
|
b18d81cbbe | ||
|
|
03e60c902f | ||
|
|
0b88cbaa3c | ||
|
|
5b180fdd5f | ||
|
|
03f737a923 | ||
|
|
4e0259148f | ||
|
|
a3cadb2fce | ||
|
|
6789e7d6f9 | ||
|
|
c3e3ea98ad | ||
|
|
c11c36f9f7 | ||
|
|
150ec1534c | ||
|
|
0dfd7fb20b | ||
|
|
b90e44a5f1 | ||
|
|
744bd21423 | ||
|
|
4a58c42e2d | ||
|
|
af9802e3f2 | ||
|
|
ad9c6f2f18 | ||
|
|
eb3df3b411 | ||
|
|
7e9f66c987 | ||
|
|
f4b89278ef | ||
|
|
e0a8499376 | ||
|
|
6ded0d1d7b | ||
|
|
77152da627 | ||
|
|
2a51d4acf1 | ||
|
|
ea651eacce | ||
|
|
3edcf37c58 | ||
|
|
b0f00d20c8 | ||
|
|
453f2c1871 | ||
|
|
e40497e02e | ||
|
|
e124ae985d | ||
|
|
efa1b1ef48 | ||
|
|
ec6c2618ca | ||
|
|
fe4c920794 | ||
|
|
afcefc7855 | ||
|
|
22ec55f1d5 | ||
|
|
c026c98bbb | ||
|
|
205983e87f | ||
|
|
c9a9bbdab2 | ||
|
|
ae56d79c7e | ||
|
|
860724d794 | ||
|
|
40199a42b3 | ||
|
|
7c6e656fda | ||
|
|
9611069a4f | ||
|
|
825960553a | ||
|
|
325fe71d30 | ||
|
|
beec259520 | ||
|
|
1dcad83790 | ||
|
|
8dd606009c | ||
|
|
17b6656c85 | ||
|
|
edb5241bee | ||
|
|
5cb283073e | ||
|
|
a6c72f1cc7 | ||
|
|
95416a4b9c | ||
|
|
f7ab78893c | ||
|
|
7de27ebffa | ||
|
|
70535435f2 | ||
|
|
61a6fb6780 | ||
|
|
d5bce07a90 | ||
|
|
027a37409b | ||
|
|
1bfd3beab9 | ||
|
|
b864ff47e5 | ||
|
|
4d34163ade | ||
|
|
8ce05917db | ||
|
|
d59c1e7bd2 | ||
|
|
6fb7f6d095 | ||
|
|
db8bfc9ba9 | ||
|
|
305382453d | ||
|
|
ccaef5ba0b | ||
|
|
4d094c108d | ||
|
|
b1768b98a5 | ||
|
|
d6e77b3f40 | ||
|
|
58eaa330af | ||
|
|
8c2a970222 | ||
|
|
173c58bb6e | ||
|
|
6a214ed419 | ||
|
|
df20e8ad84 | ||
|
|
761d50e8f5 | ||
|
|
2997a78872 | ||
|
|
6b4ed90323 | ||
|
|
ace8f18adb | ||
|
|
8692f2a650 | ||
|
|
ee3120010a | ||
|
|
3d6129f8b0 | ||
|
|
dbb17e50e3 | ||
|
|
8b9c25f19c | ||
|
|
33b5404033 | ||
|
|
1e5926b9c0 | ||
|
|
b452e84e53 | ||
|
|
bb4c6bc5a9 | ||
|
|
474b3dbdfb | ||
|
|
80e6087ff3 | ||
|
|
c4d18c8291 | ||
|
|
bf1222c4b1 | ||
|
|
0ec1b29a66 | ||
|
|
035a2ba30d | ||
|
|
2242811004 | ||
|
|
099af6dd74 | ||
|
|
2a7e2215e0 | ||
|
|
8fc8b42221 | ||
|
|
f538ac49c6 | ||
|
|
c35acce915 | ||
|
|
77be1c8616 | ||
|
|
0fdc70a75f | ||
|
|
2b4e5250ed | ||
|
|
760eeb40e8 | ||
|
|
303680257f | ||
|
|
952da01f33 | ||
|
|
601c55bad2 | ||
|
|
ea7fb726ac | ||
|
|
4f9de46d3d | ||
|
|
60935f2a25 | ||
|
|
7e70154e4e | ||
|
|
d94c484adc | ||
|
|
0a27c61313 | ||
|
|
b34966fe62 | ||
|
|
85789a8a13 | ||
|
|
333e4bcb05 | ||
|
|
b80fa52ba2 | ||
|
|
b638a3adff | ||
|
|
9b2f1266ea | ||
|
|
bde92ffed2 | ||
|
|
3bf371c4fb | ||
|
|
c39d1d6441 | ||
|
|
1d8022c301 | ||
|
|
0afe331d1e | ||
|
|
19e48a843d | ||
|
|
9649302da8 | ||
|
|
f06989e17c | ||
|
|
e1357d7a1b | ||
|
|
4f62a115fa | ||
|
|
cd1a2c7c6e | ||
|
|
37628ed305 | ||
|
|
e5cdb8b361 | ||
|
|
65cb45ea97 | ||
|
|
4f44b954ab | ||
|
|
d8aaa91379 | ||
|
|
43b8a98091 | ||
|
|
db9e82691c | ||
|
|
e7a503c47f | ||
|
|
7e0855d56b | ||
|
|
b0b3c8a335 | ||
|
|
1c2c1b5c17 | ||
|
|
b50876e77c | ||
|
|
c877d0bcb4 | ||
|
|
5a168f7ae4 | ||
|
|
5bbafeabd7 | ||
|
|
04728994a5 | ||
|
|
600227a30b | ||
|
|
6d4a080da5 | ||
|
|
7fac1ef8c9 | ||
|
|
7e043e21e7 | ||
|
|
40b328dc95 | ||
|
|
0a64f16c2d | ||
|
|
9965aff723 | ||
|
|
6276f1b12a | ||
|
|
335403d860 | ||
|
|
4e6f7c2152 | ||
|
|
8877a2527d | ||
|
|
71fb624ec4 | ||
|
|
4394d79c39 | ||
|
|
3900890ef4 | ||
|
|
a11e417e43 | ||
|
|
78bac8dcbc | ||
|
|
9a7233ced5 | ||
|
|
460352d0d5 | ||
|
|
89bd8b2ba5 | ||
|
|
cf2c07d905 | ||
|
|
529c688ff2 | ||
|
|
af665ef3dc | ||
|
|
6c5e0cdf1d | ||
|
|
80f504019f | ||
|
|
6ccad8a94d | ||
|
|
f0df6112dc | ||
|
|
170b0b4edf | ||
|
|
39cd16a92b | ||
|
|
eaf66e7af9 | ||
|
|
ab75aeeac9 | ||
|
|
4fe2949033 | ||
|
|
ac20896f57 | ||
|
|
afddce2538 | ||
|
|
14aa5dafe5 | ||
|
|
4d62b3a573 | ||
|
|
d4241d987d | ||
|
|
04d30fb487 | ||
|
|
d653e984b5 | ||
|
|
afe51ec5f3 | ||
|
|
29d21fe1f7 | ||
|
|
43643ba0b6 | ||
|
|
efb1973210 | ||
|
|
0bd56feb2d | ||
|
|
83a31730ce | ||
|
|
5f7a36cf44 | ||
|
|
46bccd9a92 | ||
|
|
7448c0fbe1 | ||
|
|
2755166543 | ||
|
|
cbfa625fb9 | ||
|
|
23930e998b | ||
|
|
9b708c433f | ||
|
|
2c6adfb416 | ||
|
|
bde6e06cd7 | ||
|
|
4022494934 | ||
|
|
68ee8704d2 | ||
|
|
8d9a8494b8 | ||
|
|
c1ab158f6c | ||
|
|
ca076b4a60 | ||
|
|
a4f245550b | ||
|
|
1711d024dd | ||
|
|
217bdcf5a9 | ||
|
|
4aac9e677d | ||
|
|
aba343e429 | ||
|
|
25f09bee2c | ||
|
|
cb9ef7814e | ||
|
|
8f208d732a | ||
|
|
8816dd0557 | ||
|
|
0a5647b20f | ||
|
|
d48794024d | ||
|
|
95b6fdeed3 | ||
|
|
e550e15ce6 | ||
|
|
4e670fcaf5 | ||
|
|
70fc86536c | ||
|
|
1c3691a57b | ||
|
|
ebf3f02264 | ||
|
|
0a0566abc5 | ||
|
|
58809f95ff | ||
|
|
8f6249c12f | ||
|
|
da878d29d1 | ||
|
|
b22c8e86ba | ||
|
|
ec7805fd92 | ||
|
|
a64b9a2029 | ||
|
|
0269a196a4 | ||
|
|
010d25be06 | ||
|
|
0de97789dd | ||
|
|
f71c74aaf7 | ||
|
|
82560c8c0e | ||
|
|
12dc23f67e | ||
|
|
c807367099 | ||
|
|
9e36e5d1ad | ||
|
|
b6222ae28b | ||
|
|
703df05529 | ||
|
|
c8ebc6f21e | ||
|
|
0b6e27934d | ||
|
|
d9a1cc06c1 | ||
|
|
f74dd21573 | ||
|
|
4ccaea9f08 | ||
|
|
d36ca27041 | ||
|
|
2e5b076175 | ||
|
|
7bca9dcd90 | ||
|
|
00a069d608 | ||
|
|
adf3a5b998 | ||
|
|
09d6a674c4 | ||
|
|
ab61777896 | ||
|
|
c564b163d3 | ||
|
|
43c0f67353 | ||
|
|
44b1de2d71 | ||
|
|
803b10fd6b | ||
|
|
3ac364ebcb | ||
|
|
842420439d | ||
|
|
ee976f8146 | ||
|
|
bb1d4ad580 | ||
|
|
d5eb85e4c4 | ||
|
|
08f82dc0be | ||
|
|
0fb2f8d75d | ||
|
|
a861c6671c | ||
|
|
b18b7461e4 | ||
|
|
d8139f2058 | ||
|
|
af5f1c4d52 | ||
|
|
5877db5411 | ||
|
|
eb81bdc3f8 | ||
|
|
b6b1eed1d5 | ||
|
|
e89eadf91a | ||
|
|
785eaeac86 | ||
|
|
313d5b681e | ||
|
|
16f7cd3c68 | ||
|
|
a0b3a71212 | ||
|
|
850cfd90f7 | ||
|
|
f8c6f4a7ba | ||
|
|
eca9609f52 | ||
|
|
739123d005 | ||
|
|
53439ea5e2 | ||
|
|
da4b95bf59 | ||
|
|
79f7d99b02 | ||
|
|
8c5724d125 | ||
|
|
cf33cbd33f | ||
|
|
c53d94f538 | ||
|
|
b3aaac43ca | ||
|
|
7974a74580 | ||
|
|
2f1fc19f88 | ||
|
|
52607f70fe | ||
|
|
d79b7d4176 | ||
|
|
6010770879 | ||
|
|
c8a93adb01 | ||
|
|
1dee03e4f2 | ||
|
|
0312a77390 | ||
|
|
fddbb5d990 | ||
|
|
8bd49317ca | ||
|
|
1b34592d80 | ||
|
|
6f91104542 | ||
|
|
e300d56f68 | ||
|
|
8351daea73 | ||
|
|
b5a322d122 | ||
|
|
77a53e2b5b | ||
|
|
ef41e12ba8 | ||
|
|
6c6999bf01 | ||
|
|
aa657d5438 | ||
|
|
e1143fd985 | ||
|
|
fb9912db5c | ||
|
|
a59ff2dcbb | ||
|
|
e792752104 | ||
|
|
3c283187c3 | ||
|
|
1967f2c79b | ||
|
|
f5df9f9c24 | ||
|
|
5040412813 | ||
|
|
9326227bcd | ||
|
|
b52af15504 | ||
|
|
e1381d1beb | ||
|
|
c4f42b1bc9 | ||
|
|
d0222ef7d9 | ||
|
|
87781fd75c | ||
|
|
16af373a7e | ||
|
|
e9314add15 | ||
|
|
145efd63c9 | ||
|
|
0905e203e3 | ||
|
|
4fbbfb410d | ||
|
|
36aaa19ae9 | ||
|
|
8b1e413db5 | ||
|
|
54f778c204 | ||
|
|
3876d33a15 | ||
|
|
94e5ff2cfb | ||
|
|
79d165190a | ||
|
|
83b7d3a267 | ||
|
|
a51efcc974 | ||
|
|
1476afb992 | ||
|
|
80b8e8284e | ||
|
|
8f9392cae7 | ||
|
|
01bd20c30a | ||
|
|
b0dd2b291d | ||
|
|
9f432d5fa2 | ||
|
|
2f90aac0ac | ||
|
|
869ad0a02d | ||
|
|
928de7ae6c | ||
|
|
feb07e476a | ||
|
|
460944ef42 | ||
|
|
f1bc3ef4c7 | ||
|
|
cc21988537 | ||
|
|
db0082de3f | ||
|
|
bbc937c309 | ||
|
|
71c5494414 | ||
|
|
2157e0a1b6 | ||
|
|
cf09a04e92 | ||
|
|
aba6efd24a | ||
|
|
4460e64f3a | ||
|
|
c696cb7f63 | ||
|
|
332bbf2310 | ||
|
|
ab2c32e52f | ||
|
|
eaa376be05 | ||
|
|
53fa2ba67b | ||
|
|
80c849a2aa | ||
|
|
add9a77396 | ||
|
|
e6aae0c18e | ||
|
|
03f01b9c49 | ||
|
|
2341dee04a | ||
|
|
f5545f864d | ||
|
|
2e82525fb4 | ||
|
|
0dc6be27f3 | ||
|
|
49590756a0 | ||
|
|
9442517f30 | ||
|
|
93036b6877 | ||
|
|
42b0512517 | ||
|
|
83707a9c2f | ||
|
|
0f170f51c6 | ||
|
|
960911f263 | ||
|
|
e41ae15f27 | ||
|
|
aa30c611a8 | ||
|
|
20932eae88 | ||
|
|
54c63c11d4 | ||
|
|
d6620568e5 | ||
|
|
ad659b8f8e | ||
|
|
8420a1d7d6 | ||
|
|
730dce67e6 | ||
|
|
a9070da72e | ||
|
|
0abf0083be | ||
|
|
2bc3b87a2d | ||
|
|
95661c24df | ||
|
|
27a4091975 | ||
|
|
853144214b | ||
|
|
574e0d17ec | ||
|
|
cea95bdede | ||
|
|
12da626fd1 | ||
|
|
6de054002d | ||
|
|
9079baf0b3 | ||
|
|
a754bbe3a4 | ||
|
|
2ecb45d8c0 | ||
|
|
ebd9df6ab9 | ||
|
|
99b502f9a4 | ||
|
|
9879a31fc5 | ||
|
|
75320a3353 | ||
|
|
c1556a0a73 | ||
|
|
02a229b12f | ||
|
|
7d27fe6ff9 | ||
|
|
f61fa7b0e4 | ||
|
|
0d3b5b8a04 | ||
|
|
2d9bed27f2 | ||
|
|
d47344187d | ||
|
|
12a964d3be | ||
|
|
ea8eca6a45 | ||
|
|
b890719fdd | ||
|
|
e9073800d5 | ||
|
|
d089c3e1b0 | ||
|
|
b778b67676 | ||
|
|
2b2fafbcfc | ||
|
|
21086a25d6 | ||
|
|
08969fde4b | ||
|
|
b27561c21e | ||
|
|
79cb60025d | ||
|
|
04f24cb222 | ||
|
|
a23e910d5f | ||
|
|
c93c9bce6f | ||
|
|
0b02b097a9 | ||
|
|
9d78cca5ff | ||
|
|
a31ab53d26 | ||
|
|
66cc77bd5f | ||
|
|
5a41f422fe | ||
|
|
5c5af7c1e5 | ||
|
|
ca5ef812a3 | ||
|
|
a9ea7ec1c9 | ||
|
|
8f0c83dbf9 | ||
|
|
2b201e6f59 | ||
|
|
c86549a3bb | ||
|
|
aba3ae2869 | ||
|
|
d1e831f69d | ||
|
|
1d2b6cca24 | ||
|
|
d71a38632c | ||
|
|
d4d9339d95 | ||
|
|
0ad8767903 | ||
|
|
1c383d8589 | ||
|
|
55ce01c69e | ||
|
|
e1fa883fbb | ||
|
|
be78dc4bfb | ||
|
|
bc122d5c3d | ||
|
|
b60ab86fa5 | ||
|
|
32fae40c4e | ||
|
|
324ecdcb99 | ||
|
|
c886c8fa00 | ||
|
|
925732914a | ||
|
|
d1ae380cc2 | ||
|
|
9d33668c9d | ||
|
|
903b6b198f | ||
|
|
30ea4ed341 | ||
|
|
5b7a6bf447 | ||
|
|
4fe8d16a20 | ||
|
|
7cc7e484b9 | ||
|
|
cfcc8af5c3 | ||
|
|
3ce2905582 | ||
|
|
67b75ac7fb | ||
|
|
99b70f7a6e | ||
|
|
de002ed071 | ||
|
|
50eae09cda | ||
|
|
b9a4bf4f7b | ||
|
|
d6e62b3c1f | ||
|
|
e017f504c6 | ||
|
|
81e8b1a4d8 | ||
|
|
b7c54253ff | ||
|
|
eb18dafb22 | ||
|
|
3ddb96b9af | ||
|
|
0c6b551afa | ||
|
|
5a1ff2496c | ||
|
|
7ef92c3547 | ||
|
|
73572e6d7f | ||
|
|
ebc2c9e28a | ||
|
|
ea9dbe5643 | ||
|
|
237058420f | ||
|
|
b0704ba35f | ||
|
|
9b98bce193 | ||
|
|
41dd81ef7e | ||
|
|
55f4f5f704 | ||
|
|
a371325346 | ||
|
|
d1148377ae | ||
|
|
cf001a38df | ||
|
|
841af4795c | ||
|
|
daef2f4c6b | ||
|
|
0a58d88be2 | ||
|
|
27ecaed76e | ||
|
|
beb4cd4ea6 | ||
|
|
d4a56c9a37 | ||
|
|
a4324d595d | ||
|
|
63c6e48562 | ||
|
|
1f4b2dd45f | ||
|
|
7a2aaa6e8e | ||
|
|
b2dcd55d95 | ||
|
|
9c096a31f0 | ||
|
|
cdec805cc9 | ||
|
|
7c54c824ab | ||
|
|
a7ed5e1fb3 | ||
|
|
73694c7a24 | ||
|
|
bc6cf23f08 | ||
|
|
b624ada00a | ||
|
|
5e078ed0dd | ||
|
|
26b1782b89 | ||
|
|
cfb5f5595a | ||
|
|
b8cb7a1883 | ||
|
|
e5646b1f6a | ||
|
|
07147111b2 | ||
|
|
d6597b0bdd | ||
|
|
805d4bf6fd | ||
|
|
faade0f871 | ||
|
|
0a438193ba | ||
|
|
ba677b725b | ||
|
|
00ce663136 | ||
|
|
2bc09a42cb | ||
|
|
a34d7fb01a | ||
|
|
c4207dec75 | ||
|
|
ef77600bae | ||
|
|
839b38d2ec | ||
|
|
44696c47c6 | ||
|
|
dacb3dbae6 | ||
|
|
eeacbac8bc | ||
|
|
1cad2903a1 | ||
|
|
fc63779b18 | ||
|
|
3817db0dc2 | ||
|
|
fa8a57af86 | ||
|
|
01d1850b0f | ||
|
|
c153f598b6 | ||
|
|
23e752a03b | ||
|
|
828d32521d | ||
|
|
227c7133c3 | ||
|
|
b19d9622b3 | ||
|
|
5b568d7bce | ||
|
|
a991b4fd50 | ||
|
|
24ea5118e0 | ||
|
|
38b73254e9 | ||
|
|
3547009d1c | ||
|
|
ce74512b92 | ||
|
|
bc33767aab | ||
|
|
040b4d0e33 | ||
|
|
43c0fe9b61 | ||
|
|
1fa1a4f5e7 | ||
|
|
9acc186ceb | ||
|
|
9b135b53b4 | ||
|
|
5b79a9a825 | ||
|
|
76e0ab135b | ||
|
|
ed8fc7690f | ||
|
|
0b9dcc0fbe | ||
|
|
b53e10b25e | ||
|
|
3008cb8ad7 | ||
|
|
1918667bbd | ||
|
|
6f06a25669 | ||
|
|
3ae9db27de | ||
|
|
2ef21b93c5 | ||
|
|
2bf0a3c7f9 | ||
|
|
9b5cbf835d | ||
|
|
761cb1041b | ||
|
|
caba47a66b | ||
|
|
bf61589f3d | ||
|
|
b26fefd325 | ||
|
|
a04b0e515a | ||
|
|
ec2ac9f227 | ||
|
|
7268cd8c32 | ||
|
|
c0acafea63 | ||
|
|
aeadeace0f | ||
|
|
d4c88dc7c4 | ||
|
|
560f8f935d | ||
|
|
723bdc71dc | ||
|
|
d9200351ef | ||
|
|
d1a4746076 | ||
|
|
517366bec9 | ||
|
|
c1ffbe8e0d | ||
|
|
825ab7f411 | ||
|
|
e4ff8d34be | ||
|
|
a5e5e8a435 | ||
|
|
1413bb4b4d | ||
|
|
e37d58c60c | ||
|
|
2903fcabe4 | ||
|
|
26c5999a6e | ||
|
|
9774fdd017 | ||
|
|
14f0ed1cba | ||
|
|
f00694fae0 | ||
|
|
3c82b63046 | ||
|
|
82d47db47d | ||
|
|
203f6cc508 | ||
|
|
218265ad37 | ||
|
|
189a4fc203 | ||
|
|
711771bc27 | ||
|
|
1851743c9d | ||
|
|
405778acf3 | ||
|
|
4d7670bac5 | ||
|
|
51cee7f07d | ||
|
|
14bdad425f | ||
|
|
29f304554d | ||
|
|
b79f3232b6 | ||
|
|
3f84749b05 | ||
|
|
d146714185 | ||
|
|
e5d251a0be | ||
|
|
9d5b380ea9 | ||
|
|
6f5fb3044a | ||
|
|
b6f03ef75d | ||
|
|
b7a51d265c | ||
|
|
6323f1642d | ||
|
|
58dbde7b2f | ||
|
|
c2431d5c17 | ||
|
|
63a8b4cfb4 | ||
|
|
34faaeb314 | ||
|
|
523d2fb8bf | ||
|
|
c01d5d5dd2 | ||
|
|
09aa695048 | ||
|
|
3d9b228177 | ||
|
|
96961f696b | ||
|
|
52179c4428 | ||
|
|
8520c79749 | ||
|
|
50e6497390 | ||
|
|
b17002a9c0 | ||
|
|
d9e977ed42 | ||
|
|
217906b63b | ||
|
|
b4ff3b1c83 | ||
|
|
ad09ae8790 | ||
|
|
5bc2e4b95c | ||
|
|
9016911a04 | ||
|
|
4661233904 | ||
|
|
f6ba6cbd4e | ||
|
|
20b9d002dc | ||
|
|
94c1614416 | ||
|
|
e1b426efe6 | ||
|
|
299b641173 | ||
|
|
6933780aed | ||
|
|
da85325686 | ||
|
|
6a26472278 | ||
|
|
47806d00c8 | ||
|
|
fc35fa6a1d | ||
|
|
e889fa17f3 | ||
|
|
c996c67390 | ||
|
|
ac5dabbb94 | ||
|
|
cd9585d713 | ||
|
|
237e90fe64 | ||
|
|
7a43036e6e | ||
|
|
26cf9591e7 | ||
|
|
167779392c | ||
|
|
fe567e5b42 | ||
|
|
edffd4af81 | ||
|
|
f4f3c63755 | ||
|
|
0a983cb24c | ||
|
|
dedc50e848 | ||
|
|
c9443db053 | ||
|
|
e7288604ea | ||
|
|
79ee17d2f6 | ||
|
|
9183aef645 | ||
|
|
0246cec0a7 | ||
|
|
d385328a16 | ||
|
|
1708a88445 | ||
|
|
e2a86c1756 | ||
|
|
12fa5aeb5e | ||
|
|
6f9504c50a | ||
|
|
2a9ab49d13 | ||
|
|
f649943e93 | ||
|
|
f43b86e3b3 | ||
|
|
5ef64841cc | ||
|
|
2df7c53c26 | ||
|
|
4cceab5ad8 | ||
|
|
88c926d723 | ||
|
|
1a4cdef4b6 | ||
|
|
f17bd3f444 | ||
|
|
13261b9bdc | ||
|
|
b2a7bb64ff | ||
|
|
3e99b0e7c2 | ||
|
|
a0016be7ff | ||
|
|
35a05aea62 | ||
|
|
615db1a78c | ||
|
|
1e61dc88de | ||
|
|
f650b69591 | ||
|
|
f981330f66 | ||
|
|
9c57b229ea | ||
|
|
a602127edd | ||
|
|
66b23bb19a | ||
|
|
1ad248892d | ||
|
|
b63f605ec1 | ||
|
|
2d3fa2141e | ||
|
|
f55bd056a9 | ||
|
|
6cd7751479 | ||
|
|
9ced780715 | ||
|
|
0eb9f4acb6 | ||
|
|
921d426488 | ||
|
|
90bc94f237 | ||
|
|
dffa18378b | ||
|
|
8b2019830a | ||
|
|
2c19b82d72 | ||
|
|
d59e6cdfad | ||
|
|
4a6c75d8f8 | ||
|
|
3ac8242d72 | ||
|
|
4745db1553 | ||
|
|
3258599899 | ||
|
|
db745611c4 | ||
|
|
1977beafe6 | ||
|
|
16558f2915 | ||
|
|
7976e6ab0b | ||
|
|
2403438bd9 | ||
|
|
bb3b5b6a4f | ||
|
|
d2ffc9b9c6 | ||
|
|
a0092a793a | ||
|
|
c0ac36918a | ||
|
|
22d2b7407f | ||
|
|
0e3b3c57fc | ||
|
|
9c66d27777 | ||
|
|
a7c6b7f02a | ||
|
|
f1efafe825 | ||
|
|
4fe1e00696 | ||
|
|
49ab593247 | ||
|
|
cc7790af04 | ||
|
|
aaa695611b | ||
|
|
1be738ba10 | ||
|
|
86a6b9b671 | ||
|
|
d79b6d040e | ||
|
|
04c32562a0 | ||
|
|
d363ddde2a | ||
|
|
7b5de9e2b9 | ||
|
|
16724b39c5 | ||
|
|
9bb2be27cf | ||
|
|
f0b8658346 | ||
|
|
f6f9ec49d8 | ||
|
|
bca011966d | ||
|
|
e5ca27966e | ||
|
|
d1f63dbca8 | ||
|
|
95658d532e | ||
|
|
f6965c62a9 | ||
|
|
0270177a85 | ||
|
|
16e2f9f3bb | ||
|
|
0adb0dbc57 |
@@ -20,6 +20,7 @@ Checks:
|
||||
-google-readability-namespace-comments,
|
||||
-misc-confusable-identifiers,
|
||||
-misc-no-recursion,
|
||||
-misc-use-anonymous-namespace,
|
||||
-misc-use-internal-linkage,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-deprecated-headers,
|
||||
|
||||
35
.github/actions/test-tracy/action.yml
vendored
Normal file
35
.github/actions/test-tracy/action.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: 'Test Tracy'
|
||||
description: 'Build the Tracy test application with various cmake flag combinations'
|
||||
|
||||
inputs:
|
||||
extra_cmake_flags:
|
||||
description: 'Additional cmake flags appended to each configure command (e.g. cross-compilation flags)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Test application
|
||||
shell: bash
|
||||
run: |
|
||||
# test compilation with different flags
|
||||
# we clean the build folder to reset cached variables between runs
|
||||
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release ${{ inputs.extra_cmake_flags }}
|
||||
cmake --build tests/tracy/build --parallel
|
||||
cmake -E rm -rf tests/tracy/build
|
||||
|
||||
# same with TRACY_ON_DEMAND
|
||||
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON ${{ inputs.extra_cmake_flags }}
|
||||
cmake --build tests/tracy/build --parallel
|
||||
cmake -E rm -rf tests/tracy/build
|
||||
|
||||
# same with TRACY_DELAYED_INIT and TRACY_MANUAL_LIFETIME
|
||||
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
|
||||
cmake --build tests/tracy/build --parallel
|
||||
cmake -E rm -rf tests/tracy/build
|
||||
|
||||
# same with TRACY_DEMANGLE
|
||||
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON ${{ inputs.extra_cmake_flags }}
|
||||
cmake --build tests/tracy/build --parallel
|
||||
cmake -E rm -rf tests/tracy/build
|
||||
15
.github/workflows/emscripten.yml
vendored
15
.github/workflows/emscripten.yml
vendored
@@ -5,24 +5,31 @@ on:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-emscripten:
|
||||
runs-on: ubuntu-latest
|
||||
container: archlinux:base-devel
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git unzip python ninja zstd
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git unzip python ninja zstd nodejs
|
||||
- name: Setup emscripten
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
uses: emscripten-core/setup-emsdk@v16
|
||||
with:
|
||||
version: 4.0.10
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- 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
|
||||
@@ -48,7 +55,7 @@ jobs:
|
||||
path: bin
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
needs: build-emscripten
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
|
||||
4
.github/workflows/latex.yml
vendored
4
.github/workflows/latex.yml
vendored
@@ -5,9 +5,11 @@ on:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-manual:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
65
.github/workflows/linux.yml
vendored
65
.github/workflows/linux.yml
vendored
@@ -5,64 +5,68 @@ on:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
container: archlinux:base-devel
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Profiler GUI
|
||||
run: |
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build profiler/build --parallel
|
||||
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
|
||||
cmake --build profiler/build
|
||||
else
|
||||
cmake --build profiler/build --parallel
|
||||
fi
|
||||
- name: Update utility
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build update/build --parallel
|
||||
- name: Capture utility
|
||||
run: |
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build capture/build --parallel
|
||||
- name: Csvexport utility
|
||||
run: |
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build csvexport/build --parallel
|
||||
- name: Import utilities
|
||||
run: |
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build import/build --parallel
|
||||
- name: Library
|
||||
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib build && meson compile -C build && meson install -C build
|
||||
- name: Test application
|
||||
- name: Merge utility
|
||||
run: |
|
||||
# test compilation with different flags
|
||||
# we clean the build folder to reset cached variables between runs
|
||||
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build test/build --parallel
|
||||
rm -rf test/build
|
||||
|
||||
# same with TRACY_ON_DEMAND
|
||||
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON .
|
||||
cmake --build test/build --parallel
|
||||
rm -rf test/build
|
||||
|
||||
# same with TRACY_DELAYED_INIT TRACY_MANUAL_LIFETIME
|
||||
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON .
|
||||
cmake --build test/build --parallel
|
||||
rm -rf test/build
|
||||
|
||||
# same with TRACY_DEMANGLE
|
||||
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON .
|
||||
cmake --build test/build --parallel
|
||||
rm -rf test/build
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel
|
||||
- name: Library (cmake)
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
env:
|
||||
CMAKE_INSTALL_PREFIX: ${{ github.workspace }}/bin
|
||||
- name: Library (meson)
|
||||
run: |
|
||||
meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build-meson
|
||||
meson compile -C build-meson
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Find Artifacts
|
||||
id: find_artifacts
|
||||
run: |
|
||||
@@ -73,6 +77,7 @@ jobs:
|
||||
cp csvexport/build/tracy-csvexport bin
|
||||
cp import/build/tracy-import-chrome bin
|
||||
cp import/build/tracy-import-fuchsia bin
|
||||
cp merge/build/tracy-merge bin
|
||||
strip bin/tracy-*
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
69
.github/workflows/macos.yml
vendored
Normal file
69
.github/workflows/macos.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: macos
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config glfw meson
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- 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
|
||||
- name: Build update
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build update/build --parallel --config Release
|
||||
- name: Build capture
|
||||
run: |
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build capture/build --parallel --config Release
|
||||
- name: Build csvexport
|
||||
run: |
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build csvexport/build --parallel --config Release
|
||||
- name: Build import
|
||||
run: |
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build import/build --parallel --config Release
|
||||
- name: Build merge
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel --config Release
|
||||
- name: Build library
|
||||
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build && meson compile -C build && meson install -C build
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir -p bin
|
||||
cp profiler/build/tracy-profiler bin
|
||||
cp update/build/tracy-update bin
|
||||
cp capture/build/tracy-capture bin
|
||||
cp csvexport/build/tracy-csvexport bin
|
||||
cp import/build/tracy-import-chrome bin
|
||||
cp import/build/tracy-import-fuchsia bin
|
||||
cp merge/build/tracy-merge bin
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: bin
|
||||
61
.github/workflows/mingw.yml
vendored
Normal file
61
.github/workflows/mingw.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: build-mingw
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build-mingw:
|
||||
runs-on: ubuntu-latest
|
||||
container: archlinux:base-devel
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pacman -Syu --noconfirm
|
||||
pacman -S --noconfirm --needed mingw-w64-gcc cmake git nodejs meson
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Build TracyClient
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON \
|
||||
-DCMAKE_SYSTEM_NAME=Windows \
|
||||
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
|
||||
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
|
||||
cmake --build build
|
||||
- name: Library (meson)
|
||||
run: |
|
||||
cat > mingw-cross.txt << 'EOF'
|
||||
[binaries]
|
||||
c = '/usr/bin/x86_64-w64-mingw32-gcc'
|
||||
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
|
||||
ar = '/usr/bin/x86_64-w64-mingw32-ar'
|
||||
strip = '/usr/bin/x86_64-w64-mingw32-strip'
|
||||
|
||||
[host_machine]
|
||||
system = 'windows'
|
||||
cpu_family = 'x86_64'
|
||||
cpu = 'x86_64'
|
||||
endian = 'little'
|
||||
EOF
|
||||
meson setup build-meson --cross-file mingw-cross.txt -Ddefault_library=static -Dtracy_enable=true
|
||||
meson compile -C build-meson
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
with:
|
||||
extra_cmake_flags: >-
|
||||
-DCMAKE_SYSTEM_NAME=Windows
|
||||
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc
|
||||
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
|
||||
29
.github/workflows/release.yml
vendored
Normal file
29
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
release-build-windows:
|
||||
uses: ./.github/workflows/windows.yml
|
||||
|
||||
release-build-manual:
|
||||
uses: ./.github/workflows/latex.yml
|
||||
|
||||
attach-to-release:
|
||||
needs: [release-build-windows, release-build-manual]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Create versioned zip
|
||||
run: |
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
cd windows
|
||||
zip -r ../windows-$VERSION_NO_V.zip .
|
||||
- uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
windows-*.zip
|
||||
manual/tracy.pdf
|
||||
@@ -1,74 +1,61 @@
|
||||
name: build
|
||||
name: windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-latest, macos-15 ]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- if: startsWith(matrix.os, 'windows')
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- if: startsWith(matrix.os, 'windows')
|
||||
uses: actions/setup-python@v2
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- uses: microsoft/setup-msbuild@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- if: startsWith(matrix.os, 'windows')
|
||||
run: pip install meson ninja
|
||||
- if: startsWith(matrix.os, 'macos')
|
||||
name: Install macos dependencies
|
||||
run: brew install pkg-config glfw meson
|
||||
- run: pip install meson ninja
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- name: Profiler GUI
|
||||
- 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
|
||||
- name: Update utility
|
||||
- name: Build update
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build update/build --parallel --config Release
|
||||
- name: Capture utility
|
||||
- name: Build capture
|
||||
run: |
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build capture/build --parallel --config Release
|
||||
- name: Csvexport utility
|
||||
- name: Build csvexport
|
||||
run: |
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build csvexport/build --parallel --config Release
|
||||
- name: Import utilities
|
||||
- name: Build import
|
||||
run: |
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build import/build --parallel --config Release
|
||||
- if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
name: Library
|
||||
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib build && meson compile -C build && meson install -C build
|
||||
- if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
name: Find Artifacts
|
||||
id: find_artifacts
|
||||
- name: Build merge
|
||||
run: |
|
||||
mkdir -p bin
|
||||
cp profiler/build/tracy-profiler bin
|
||||
cp update/build/tracy-update bin
|
||||
cp capture/build/tracy-capture bin
|
||||
cp csvexport/build/tracy-csvexport bin
|
||||
cp import/build/tracy-import-chrome bin
|
||||
cp import/build/tracy-import-fuchsia bin
|
||||
- if: startsWith(matrix.os, 'windows')
|
||||
name: Find Artifacts
|
||||
id: find_artifacts_windows
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel --config Release
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir bin
|
||||
copy profiler\build\Release\tracy-profiler.exe bin
|
||||
@@ -77,7 +64,8 @@ jobs:
|
||||
copy csvexport\build\Release\tracy-csvexport.exe bin
|
||||
copy import\build\Release\tracy-import-chrome.exe bin
|
||||
copy import\build\Release\tracy-import-fuchsia.exe bin
|
||||
copy merge\build\Release\tracy-merge.exe bin
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}
|
||||
name: windows
|
||||
path: bin
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"cmake.configureOnOpen": true,
|
||||
"cmake.sourceDirectory": [
|
||||
"${workspaceFolder}/profiler",
|
||||
"${workspaceFolder}/capture",
|
||||
"${workspaceFolder}/csvexport",
|
||||
"${workspaceFolder}/import",
|
||||
"${workspaceFolder}/merge",
|
||||
"${workspaceFolder}/update",
|
||||
"${workspaceFolder}/test",
|
||||
"${workspaceFolder}",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# Run version helper script
|
||||
include(cmake/version.cmake)
|
||||
@@ -78,7 +78,8 @@ endif()
|
||||
|
||||
# Public dependency on some libraries required when using Mingw
|
||||
if(WIN32 AND ${CMAKE_CXX_COMPILER_ID} MATCHES "GNU|Clang")
|
||||
target_link_libraries(TracyClient PUBLIC ws2_32 dbghelp)
|
||||
target_link_libraries(TracyClient PUBLIC ws2_32 dbghelp secur32)
|
||||
target_compile_definitions(TracyClient PUBLIC WINVER=0x0A00 _WIN32_WINNT=0x0A00)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
@@ -105,51 +106,47 @@ if(TRACY_Fortran)
|
||||
add_library(Tracy::TracyClient_Fortran ALIAS TracyClientF90)
|
||||
endif()
|
||||
|
||||
macro(set_option option help value)
|
||||
option(${option} ${help} ${value})
|
||||
if(${option})
|
||||
message(STATUS "${option}: ON")
|
||||
target_compile_definitions(TracyClient PUBLIC ${option})
|
||||
else()
|
||||
message(STATUS "${option}: OFF")
|
||||
endif()
|
||||
endmacro()
|
||||
include(cmake/options.cmake)
|
||||
|
||||
set_option(TRACY_ENABLE "Enable profiling" ON)
|
||||
set_option(TRACY_ON_DEMAND "On-demand profiling" OFF)
|
||||
set_option(TRACY_CALLSTACK "Enforce callstack collection for tracy regions" OFF)
|
||||
set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF)
|
||||
set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF)
|
||||
set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF)
|
||||
set_option(TRACY_NO_BROADCAST "Disable client discovery by broadcast to local network" OFF)
|
||||
set_option(TRACY_ONLY_IPV4 "Tracy will only accept connections on IPv4 addresses (disable IPv6)" OFF)
|
||||
set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF)
|
||||
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF)
|
||||
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF)
|
||||
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF)
|
||||
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF)
|
||||
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF)
|
||||
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF)
|
||||
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF)
|
||||
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF)
|
||||
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF)
|
||||
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF)
|
||||
set_option(TRACY_FIBERS "Enable fibers support" OFF)
|
||||
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF)
|
||||
set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF)
|
||||
set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF)
|
||||
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF)
|
||||
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF)
|
||||
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF)
|
||||
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF)
|
||||
set_option(TRACY_ENABLE "Enable profiling" OFF TracyClient)
|
||||
set_option(TRACY_ON_DEMAND "On-demand profiling" OFF TracyClient)
|
||||
set_option_value(TRACY_CALLSTACK "Override the callstack collection depth for tracy zones" "" TracyClient)
|
||||
set_option_value_as_string(TRACY_PLATFORM_HEADER "Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform" "" TracyClient)
|
||||
set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF TracyClient)
|
||||
set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF TracyClient)
|
||||
set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF TracyClient)
|
||||
set_option(TRACY_NO_BROADCAST "Disable client discovery by broadcast to local network" OFF TracyClient)
|
||||
set_option(TRACY_ONLY_IPV4 "Tracy will only accept connections on IPv4 addresses (disable IPv6)" OFF TracyClient)
|
||||
set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF TracyClient)
|
||||
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF TracyClient)
|
||||
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF TracyClient)
|
||||
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF TracyClient)
|
||||
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF TracyClient)
|
||||
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF TracyClient)
|
||||
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF TracyClient)
|
||||
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF TracyClient)
|
||||
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF TracyClient)
|
||||
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF TracyClient)
|
||||
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF TracyClient)
|
||||
set_option(TRACY_FIBERS "Enable fibers support" OFF TracyClient)
|
||||
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF TracyClient)
|
||||
set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF TracyClient)
|
||||
set_option(TRACY_DISALLOW_HW_TIMER "Disallow hardware timer (may be useful on VMs). Requires TRACY_TIMER_FALLBACK=ON" OFF TracyClient)
|
||||
set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF TracyClient)
|
||||
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF TracyClient)
|
||||
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF TracyClient)
|
||||
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF TracyClient)
|
||||
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF TracyClient)
|
||||
|
||||
# advanced
|
||||
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF)
|
||||
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF TracyClient)
|
||||
mark_as_advanced(TRACY_VERBOSE)
|
||||
set_option(TRACY_DEMANGLE "[advanced] Don't use default demangling function - You'll need to provide your own" OFF)
|
||||
set_option(TRACY_NO_INTERNAL_MESSAGE "[advanced] Prevent the profiler from logging messages" OFF TracyClient)
|
||||
mark_as_advanced(TRACY_NO_INTERNAL_MESSAGE)
|
||||
set_option(TRACY_DEMANGLE "[advanced] Don't use default demangling function - You'll need to provide your own" OFF TracyClient)
|
||||
mark_as_advanced(TRACY_DEMANGLE)
|
||||
if(rocprofiler-sdk_FOUND)
|
||||
set_option(TRACY_ROCPROF_CALIBRATION "[advanced] Use continuous calibration of the Rocprof GPU time." OFF)
|
||||
set_option(TRACY_ROCPROF_CALIBRATION "[advanced] Use continuous calibration of the Rocprof GPU time." OFF TracyClient)
|
||||
mark_as_advanced(TRACY_ROCPROF_CALIBRATION)
|
||||
endif()
|
||||
|
||||
@@ -157,6 +154,9 @@ endif()
|
||||
if(TRACY_MANUAL_LIFETIME AND NOT TRACY_DELAYED_INIT)
|
||||
message(FATAL_ERROR "TRACY_MANUAL_LIFETIME can not be activated with disabled TRACY_DELAYED_INIT")
|
||||
endif()
|
||||
if(TRACY_DISALLOW_HW_TIMER AND NOT TRACY_TIMER_FALLBACK)
|
||||
message(FATAL_ERROR "TRACY_DISALLOW_HW_TIMER can not be activated with disabled TRACY_TIMER_FALLBACK")
|
||||
endif()
|
||||
|
||||
if(NOT TRACY_STATIC)
|
||||
target_compile_definitions(TracyClient PRIVATE TRACY_EXPORTS)
|
||||
@@ -196,6 +196,7 @@ set(client_includes
|
||||
${TRACY_PUBLIC_DIR}/client/TracyDxt1.hpp
|
||||
${TRACY_PUBLIC_DIR}/client/TracyFastVector.hpp
|
||||
${TRACY_PUBLIC_DIR}/client/TracyLock.hpp
|
||||
${TRACY_PUBLIC_DIR}/client/TracyMangle.hpp
|
||||
${TRACY_PUBLIC_DIR}/client/TracyProfiler.hpp
|
||||
${TRACY_PUBLIC_DIR}/client/TracyRingBuffer.hpp
|
||||
${TRACY_PUBLIC_DIR}/client/TracyScoped.hpp
|
||||
@@ -219,6 +220,7 @@ set(common_includes
|
||||
${TRACY_PUBLIC_DIR}/common/TracySocket.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracySystem.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyTaggedUserlandAddress.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyYield.hpp)
|
||||
|
||||
@@ -281,3 +283,7 @@ if(TRACY_CLIENT_PYTHON)
|
||||
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
set(CMAKE_COLOR_DIAGNOSTICS ON)
|
||||
endif()
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,7 +1,7 @@
|
||||
Tracy Profiler (https://github.com/wolfpld/tracy) is licensed under the
|
||||
3-clause BSD license.
|
||||
|
||||
Copyright (c) 2017-2025, Bartosz Taudul <wolf@nereid.pl>
|
||||
Copyright (c) 2017-2026, Bartosz Taudul <wolf@nereid.pl>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
172
NEWS
172
NEWS
@@ -2,6 +2,178 @@ Note: There is no guarantee that version mismatched client and server will
|
||||
be able to talk with each other. Network protocol breakages won't be listed
|
||||
here.
|
||||
|
||||
vx.xx.x (2026-xx-xx)
|
||||
--------------------
|
||||
|
||||
- Added tracy-capture-daemon for automated multi-client trace capture.
|
||||
- Added tracy-merge utility for combining multiple trace files into one.
|
||||
- Added support for Windows on ARM64 with MSVC.
|
||||
- External frames are now omitted in the single-line call stack list visible
|
||||
in messages list, or in memory allocation info window.
|
||||
- External frames are now hidden by default in various contexts where they
|
||||
were previously enabled:
|
||||
- Flame graph window.
|
||||
- Call stack window.
|
||||
- Statistics window (sampling mode).
|
||||
- External frames are now dimmed out in call stacks in various parts of UI.
|
||||
- Single-line call stacks now have ellipsis at the end, if there are frames
|
||||
remaining.
|
||||
- System tracing on Windows has been refactored to be more robust.
|
||||
- Tracing on Arm macOS will now have more precise timer readings.
|
||||
- Extended CUDA support to track some previously missing memory operations.
|
||||
- Added support for setting message's source and severity, through the
|
||||
TracyLogString macro.
|
||||
- The "zone trace" list in zone information window has been removed. It was
|
||||
never convenient to use properly. It was replaced by "parent zones" list,
|
||||
which is basically a less convoluted equivalent.
|
||||
- Added inline call stack list to the zone information window.
|
||||
- The "call stack" button for opening the call stack in a separate window
|
||||
is still there.
|
||||
- Call stacks are now also displayed in zone tooltips (single-line).
|
||||
- Implemented heuristic reconstruction of zone call stacks for zones that
|
||||
were captured without call stacks.
|
||||
- Requires sampling to be enabled, and at least one sample in the zone
|
||||
extent.
|
||||
- Since this is a heuristic, the result can be wrong.
|
||||
- The reconstructed call stack can be displayed in the zone tooltip and in
|
||||
the inline call stack view in zone information window.
|
||||
- Reconstructed call stacks are indicated with the "magic wand" icon.
|
||||
- Various LLM integration improvements.
|
||||
- The protocol has been updated to use model templates. As a result, tools
|
||||
are now specified in a common way and the reasoning is performed in a
|
||||
separate content stream.
|
||||
- Several new tools were added, which in concert enable the assistant to
|
||||
answer very general questions, such as "how to optimize this program?".
|
||||
- Smaller models are now viable to use. Models as small as 4B parameters do
|
||||
now work really well. You can run such models on virtually all hardware.
|
||||
- Added horizontal scroll bars to code segments.
|
||||
- LLM thinking regions are now hidden by default.
|
||||
- The assistant may notify the user about its current findings, then
|
||||
resume thinking, after which it may give a more complete answer. In
|
||||
such cases, the initial part of the reply will be faded out.
|
||||
- Sampled execution costs are now included in assembly attachments.
|
||||
- Source code retrieval now has an optional line context parameter.
|
||||
- Added ability to search the code for keywords.
|
||||
- Calls in assembly attachments are now annotated with function names.
|
||||
- Wikipedia search will now return 10 results, not only the top one.
|
||||
- Brave search engine is now available as an alternative web search option.
|
||||
- Added emoji font.
|
||||
- Maximum tool reply size has been tweaked to better work with larger
|
||||
contexts.
|
||||
- Tool reply size limit is now configurable in LLM settings.
|
||||
- Tool reply eviction logic for context management has been adjusted to
|
||||
better work with larger contexts. Additional logic was added to prefer
|
||||
eviction of old responses.
|
||||
- Certain LLM actions want to run in a fast mode, with reasoning disabled.
|
||||
In most scenarios the default chat model will have to do here. If you
|
||||
have the memory to spare, you can optionally load two models at once,
|
||||
setting the "fast" model to a smaller and much quicker one.
|
||||
- Chat topic description is now provided, based on the first user question.
|
||||
- Each assistant reply is now labeled with used model and reply time.
|
||||
- Follow-up questions can be automatically suggested.
|
||||
- Expanded LLM attachments.
|
||||
- You can now attach complete symbol assembly.
|
||||
- Entry call stacks can be now attached (previously it was only regular
|
||||
call stacks).
|
||||
- Crash call stack attachments are now annotated with crash info.
|
||||
- Source code can be attached (also with execution costs in symbol view).
|
||||
- Zone histogram data can be attached for analysis.
|
||||
- Markdown renderer improvements.
|
||||
- Tables are now properly rendered.
|
||||
- Tasklist rendering has been implemented.
|
||||
- Strikethrough is now supported.
|
||||
- Clickable links are now underlined.
|
||||
- Tweaked high-resolution scroll handling on Wayland.
|
||||
- Touchpad gestures on the timeline now either scroll or zoom, but not both
|
||||
simultaneously.
|
||||
- Full user name is now stored in trace info.
|
||||
- External functions can be filtered out in the sampling statistics view.
|
||||
- Tweaked external paths heuristics.
|
||||
- Check for both 64-bit and 32-bit versions of Program Files directory.
|
||||
- Hidden unix files and directories are now also considered external. For
|
||||
example: $(HOME)/.cache/cpm/somelib/file.h.
|
||||
- Call stack window can now provide LLM summaries.
|
||||
- These summaries can be performed automatically. Enable in LLM settings.
|
||||
- The capture utility is now displaying query backlog, just like the profiler
|
||||
GUI.
|
||||
- Lua source locations that are script code will now have newlines removed.
|
||||
This is a capture-time change, so previously captured broken Lua source
|
||||
locations won't be fixed.
|
||||
- Call stack window will now display notification if viewing a crash call
|
||||
stack.
|
||||
- Removal of Tracy crash handler stack from the reported crash call stack
|
||||
should now work again on Linux.
|
||||
- In disassembly line view, source file names are now displayed instead of
|
||||
"unknown", in case the source line number is not known.
|
||||
- Trace host info is now properly formatted.
|
||||
- It is now possible to sort the order of threads on the timeline ("visible
|
||||
threads" in trace settings).
|
||||
- Added clipboard support to emscripten backend.
|
||||
- Added TRACY_DISALLOW_HW_TIMER define for virtualized environments and WSL2,
|
||||
which may not have reliable access to hardware timer registers. Falls back
|
||||
to standard library timer with reduced resolution.
|
||||
- Fixed DPI scaling on macOS.
|
||||
- Thread names are no longer truncated to 15 characters on Apple.
|
||||
- Executable path is now inspected when looking for PDB files on Windows.
|
||||
- Added ___tracy_get_time() C API as an equivalent for Profiler::GetTime().
|
||||
- D3D12 instrumentation improvements.
|
||||
- CUDA instrumentation improvements.
|
||||
- Properly set API visibility attributes on MSVC + clang.
|
||||
- Fixed regression in data sorting algorithm that could cause broken (going
|
||||
back in time) plots. A retroactive fix is included for previously broken
|
||||
traces.
|
||||
- All tools provided by the project now report the version number and git sha
|
||||
revision on the command line help output.
|
||||
- Microarchitectural data has been updated to include the latest uops.info
|
||||
measurements.
|
||||
- Added validation check for SymSrv.dll on Windows.
|
||||
- Various CMake options are now available to control optional build settings:
|
||||
- NO_LTO disables link-time optimization.
|
||||
- NO_MOLD_LINKER disables use of the mold linker.
|
||||
- NO_CCACHE disables use of ccache (compiler cache).
|
||||
- The Tracy library now has TRACY_ENABLED unset by default in the CMake and
|
||||
Meson default configurations. This now matches what the documentation was
|
||||
always saying. Some build setups may need updating.
|
||||
- Adjust to max sampling rate on Linux.
|
||||
- Further improvements to tracefs mount path discovery robustness.
|
||||
- Macro mismatch detection between Tracy configuration and client code.
|
||||
- Tracy client build settings change the ABI of the library.
|
||||
- Mismatched versions will break at linking.
|
||||
- As a reminder, Tracy *always* required using the same set of compilation
|
||||
options for the entire program.
|
||||
- Message windows will now properly show full message in a tooltip for
|
||||
multi-line messages.
|
||||
- Greatly improved the in-profiler user manual.
|
||||
- There is now chapter tree and the manual contents are displayed section
|
||||
by section.
|
||||
- Links to chapters are now properly working.
|
||||
- The "bclogo" blocks are now correctly processed.
|
||||
- The font awesome icons now show as in the rest of the UI.
|
||||
- Call stack window will now show the thread viewed call stack originates
|
||||
from (if possible).
|
||||
- "Visible threads" checkboxes in messages, flame graph and wait stacks
|
||||
windows are now displayed in multiple columns, and the maximum number of
|
||||
visible rows is limited, with fallback to scrollable view.
|
||||
- Improved child call distribution list in the symbol view window.
|
||||
- The visible area can be now resized horizontally.
|
||||
- The list is now displayed as a table with resizable columns, etc.
|
||||
- Child calls time percentage is now shown as a percentage of calls (as
|
||||
it was before), and also as a percentage of total symbol time.
|
||||
- Child calls time is now also displayed as a percentage.
|
||||
- Prototype implementation of system tracing on Apple devices.
|
||||
- Local (inline) call stack printouts were added to tooltips in statistics
|
||||
window, in sampling mode.
|
||||
- Ironed out some code corners to make integration of closed gaming console
|
||||
platforms easier. Added support for custom platform headers.
|
||||
- Bottom and top sample trees (in wait stacks, or in entry call stacks)
|
||||
now display aggregation counts if "group by function name" is enabled.
|
||||
- HW sample view in symbol view are now disabled by default.
|
||||
- The profiler can no longer be built with the statistics disabled.
|
||||
- Fixed NVCC builds.
|
||||
- Fixed possible lockups in Vulkan timer calibration loop.
|
||||
- The flame graph view now supports zooming in and panning with the mouse.
|
||||
|
||||
|
||||
v0.13.1 (2025-12-11)
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
|
||||
option(NO_STATISTICS "Disable calculation of statistics" ON)
|
||||
set(NO_STATISTICS ON)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
@@ -16,13 +15,22 @@ project(
|
||||
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)
|
||||
|
||||
set(PROGRAM_FILES
|
||||
src/capture.cpp
|
||||
src/CaptureOutput.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
||||
add_git_ref(${PROJECT_NAME})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
add_executable(tracy-capture-daemon src/capturedaemon.cpp src/CaptureOutput.cpp ${COMMON_FILES} ${SERVER_FILES})
|
||||
add_git_ref(tracy-capture-daemon)
|
||||
target_link_libraries(tracy-capture-daemon PRIVATE TracyServer TracyGetOpt)
|
||||
|
||||
install(TARGETS tracy-capture-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
202
capture/src/CaptureOutput.cpp
Normal file
202
capture/src/CaptureOutput.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <inttypes.h>
|
||||
#include <thread>
|
||||
|
||||
#include "CaptureOutput.hpp"
|
||||
#include "../../public/common/TracyProtocol.hpp"
|
||||
#include "../../public/common/TracyStackFrames.hpp"
|
||||
#include "../../server/TracyMemory.hpp"
|
||||
#include "../../server/TracyPrint.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
|
||||
static bool s_isTerminal = false;
|
||||
|
||||
void InitTerminalDetection()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
s_isTerminal = _isatty( fileno( stdout ) );
|
||||
#else
|
||||
s_isTerminal = isatty( fileno( stdout ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsTerminal()
|
||||
{
|
||||
return s_isTerminal;
|
||||
}
|
||||
|
||||
void AnsiPrintf( const char* ansiEscape, const char* format, ... )
|
||||
{
|
||||
if( IsTerminal() )
|
||||
{
|
||||
char buf[256];
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vsnprintf( buf, sizeof buf, format, args );
|
||||
va_end( args );
|
||||
printf( "%s%s" ANSI_RESET, ansiEscape, buf );
|
||||
}
|
||||
else
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vfprintf( stdout, format, args );
|
||||
va_end( args );
|
||||
}
|
||||
}
|
||||
|
||||
int WaitForConnection( tracy::Worker& worker )
|
||||
{
|
||||
while( !worker.HasData() )
|
||||
{
|
||||
const auto handshake = worker.GetHandshakeStatus();
|
||||
if( handshake == tracy::HandshakeProtocolMismatch )
|
||||
{
|
||||
printf( "\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n" );
|
||||
return 1;
|
||||
}
|
||||
if( handshake == tracy::HandshakeNotAvailable )
|
||||
{
|
||||
printf( "\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n" );
|
||||
return 2;
|
||||
}
|
||||
if( handshake == tracy::HandshakeDropped )
|
||||
{
|
||||
printf( "\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n" );
|
||||
return 3;
|
||||
}
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PrintWorkerFailure( tracy::Worker& worker )
|
||||
{
|
||||
const auto& failure = worker.GetFailureType();
|
||||
if( failure == tracy::Worker::Failure::None ) return;
|
||||
|
||||
AnsiPrintf( ANSI_RED ANSI_BOLD, "\nInstrumentation failure: %s", tracy::Worker::GetFailureString( failure ) );
|
||||
auto& fd = worker.GetFailureData();
|
||||
if( !fd.message.empty() )
|
||||
{
|
||||
printf( "\nContext: %s", fd.message.c_str() );
|
||||
}
|
||||
if( fd.callstack != 0 )
|
||||
{
|
||||
AnsiPrintf( ANSI_BOLD, "\nFailure callstack:\n" );
|
||||
auto& cs = worker.GetCallstack( fd.callstack );
|
||||
int fidx = 0;
|
||||
for( auto& entry : cs )
|
||||
{
|
||||
auto frameData = worker.GetCallstackFrame( entry );
|
||||
if( !frameData )
|
||||
{
|
||||
printf( "%3i. %p\n", fidx++, (void*)worker.GetCanonicalPointer( entry ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto fsz = frameData->size;
|
||||
for( uint8_t f = 0; f < fsz; f++ )
|
||||
{
|
||||
const auto& frame = frameData->data[f];
|
||||
auto txt = worker.GetString( frame.name );
|
||||
|
||||
if( fidx == 0 && f != fsz - 1 )
|
||||
{
|
||||
auto test = tracy::s_tracyStackFrames;
|
||||
bool match = false;
|
||||
do
|
||||
{
|
||||
if( strcmp( txt, *test ) == 0 )
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while( *++test );
|
||||
if( match ) continue;
|
||||
}
|
||||
|
||||
if( f == fsz - 1 )
|
||||
{
|
||||
printf( "%3i. ", fidx++ );
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiPrintf( ANSI_BLACK ANSI_BOLD, "inl. " );
|
||||
}
|
||||
AnsiPrintf( ANSI_CYAN, "%s ", txt );
|
||||
txt = worker.GetString( frame.file );
|
||||
if( frame.line == 0 )
|
||||
{
|
||||
AnsiPrintf( ANSI_YELLOW, "(%s)", txt );
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiPrintf( ANSI_YELLOW, "(%s:%" PRIu32 ")", txt, frame.line );
|
||||
}
|
||||
if( frameData->imageName.Active() )
|
||||
{
|
||||
AnsiPrintf( ANSI_MAGENTA, " %s\n", worker.GetString( frameData->imageName ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintCaptureProgress( tracy::Worker& worker, int64_t firstTime, int64_t memoryLimit )
|
||||
{
|
||||
if( !IsTerminal() ) return;
|
||||
|
||||
auto& lock = worker.GetMbpsDataLock();
|
||||
lock.lock();
|
||||
const auto mbps = worker.GetMbpsData().back();
|
||||
const auto compRatio = worker.GetCompRatio();
|
||||
const auto netTotal = worker.GetDataTransferred();
|
||||
const auto queueSize = worker.GetSendQueueSize();
|
||||
lock.unlock();
|
||||
|
||||
const char* unit = "Mbps";
|
||||
float unitsPerMbps = 1.f;
|
||||
if( mbps < 0.1f )
|
||||
{
|
||||
unit = "Kbps";
|
||||
unitsPerMbps = 1000.f;
|
||||
}
|
||||
AnsiPrintf( ANSI_ERASE_LINE ANSI_CYAN ANSI_BOLD, "\r%7.2f %s", mbps * unitsPerMbps, unit );
|
||||
printf( " /" );
|
||||
AnsiPrintf( ANSI_CYAN ANSI_BOLD, "%5.1f%%", compRatio * 100.f );
|
||||
printf( " =" );
|
||||
AnsiPrintf( ANSI_YELLOW ANSI_BOLD, "%7.2f Mbps", mbps / compRatio );
|
||||
printf( " | " );
|
||||
AnsiPrintf( ANSI_YELLOW, "Tx: " );
|
||||
AnsiPrintf( ANSI_GREEN, "%s", tracy::MemSizeToString( netTotal ) );
|
||||
printf( " | " );
|
||||
AnsiPrintf( ANSI_RED ANSI_BOLD, "%s", tracy::MemSizeToString( tracy::memUsage.load( std::memory_order_relaxed ) ) );
|
||||
if( memoryLimit > 0 )
|
||||
{
|
||||
printf( " / " );
|
||||
AnsiPrintf( ANSI_BLUE ANSI_BOLD, "%s", tracy::MemSizeToString( memoryLimit ) );
|
||||
}
|
||||
printf( " | " );
|
||||
AnsiPrintf( ANSI_RED, "%s", tracy::TimeToString( worker.GetLastTime() - firstTime ) );
|
||||
printf( " | " );
|
||||
AnsiPrintf( ANSI_RED ANSI_BOLD, "%s query backlog", tracy::RealToString( queueSize ) );
|
||||
fflush( stdout );
|
||||
}
|
||||
33
capture/src/CaptureOutput.hpp
Normal file
33
capture/src/CaptureOutput.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef __CAPTUREOUTPUT_HPP__
|
||||
#define __CAPTUREOUTPUT_HPP__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ANSI_RESET "\033[0m"
|
||||
#define ANSI_BOLD "\033[1m"
|
||||
#define ANSI_BLACK "\033[30m"
|
||||
#define ANSI_RED "\033[31m"
|
||||
#define ANSI_GREEN "\033[32m"
|
||||
#define ANSI_YELLOW "\033[33m"
|
||||
#define ANSI_BLUE "\033[34m"
|
||||
#define ANSI_MAGENTA "\033[35m"
|
||||
#define ANSI_CYAN "\033[36m"
|
||||
#define ANSI_ERASE_LINE "\033[2K"
|
||||
|
||||
namespace tracy { class Worker; }
|
||||
|
||||
void InitTerminalDetection();
|
||||
bool IsTerminal();
|
||||
|
||||
#ifdef __GNUC__
|
||||
[[gnu::format( __printf__, 2, 3 )]]
|
||||
#endif
|
||||
void AnsiPrintf( const char* ansiEscape, const char* format, ... );
|
||||
|
||||
int WaitForConnection( tracy::Worker& worker );
|
||||
|
||||
void PrintWorkerFailure( tracy::Worker& worker );
|
||||
|
||||
void PrintCaptureProgress( tracy::Worker& worker, int64_t firstTime, int64_t memoryLimit );
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,5 @@
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
@@ -8,21 +7,20 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <inttypes.h>
|
||||
#include <mutex>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../../public/common/TracyProtocol.hpp"
|
||||
#include "../../public/common/TracyStackFrames.hpp"
|
||||
#include "../../server/TracyFileWrite.hpp"
|
||||
#include "../../server/TracyMemory.hpp"
|
||||
#include "../../server/TracyPrint.hpp"
|
||||
#include "../../server/TracySysUtil.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
#include "CaptureOutput.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include "../../getopt/getopt.h"
|
||||
@@ -38,60 +36,12 @@ static std::atomic<bool> s_disconnect { false };
|
||||
|
||||
void SigInt( int )
|
||||
{
|
||||
// Relaxed order is closest to a traditional `volatile` write.
|
||||
// We don't need stronger ordering since this signal handler doesn't do
|
||||
// anything else that would need to be ordered relatively to this.
|
||||
s_disconnect.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static bool s_isStdoutATerminal = false;
|
||||
|
||||
void InitIsStdoutATerminal() {
|
||||
#ifdef _WIN32
|
||||
s_isStdoutATerminal = _isatty( fileno( stdout ) );
|
||||
#else
|
||||
s_isStdoutATerminal = isatty( fileno( stdout ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsStdoutATerminal() { return s_isStdoutATerminal; }
|
||||
|
||||
#define ANSI_RESET "\033[0m"
|
||||
#define ANSI_BOLD "\033[1m"
|
||||
#define ANSI_BLACK "\033[30m"
|
||||
#define ANSI_RED "\033[31m"
|
||||
#define ANSI_GREEN "\033[32m"
|
||||
#define ANSI_YELLOW "\033[33m"
|
||||
#define ANSI_BLUE "\033[34m"
|
||||
#define ANSI_MAGENTA "\033[35m"
|
||||
#define ANSI_CYAN "\033[36m"
|
||||
#define ANSI_ERASE_LINE "\033[2K"
|
||||
|
||||
// Like printf, but if stdout is a terminal, prepends the output with
|
||||
// the given `ansiEscape` and appends ANSI_RESET.
|
||||
void AnsiPrintf( const char* ansiEscape, const char* format, ... ) {
|
||||
if( IsStdoutATerminal() )
|
||||
{
|
||||
// Prepend ansiEscape and append ANSI_RESET.
|
||||
char buf[256];
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vsnprintf( buf, sizeof buf, format, args );
|
||||
va_end( args );
|
||||
printf( "%s%s" ANSI_RESET, ansiEscape, buf );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just a normal printf.
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vfprintf( stdout, format, args );
|
||||
va_end( args );
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void Usage()
|
||||
{
|
||||
printf( "tracy-capture %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: capture -o output.tracy [-a address] [-p port] [-f] [-s seconds] [-m memlimit]\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
@@ -106,7 +56,7 @@ int main( int argc, char** argv )
|
||||
}
|
||||
#endif
|
||||
|
||||
InitIsStdoutATerminal();
|
||||
InitTerminalDetection();
|
||||
|
||||
bool overwrite = false;
|
||||
const char* address = "127.0.0.1";
|
||||
@@ -165,26 +115,8 @@ int main( int argc, char** argv )
|
||||
printf( "Connecting to %s:%i...", address, port );
|
||||
fflush( stdout );
|
||||
tracy::Worker worker( address, port, memoryLimit );
|
||||
while( !worker.HasData() )
|
||||
{
|
||||
const auto handshake = worker.GetHandshakeStatus();
|
||||
if( handshake == tracy::HandshakeProtocolMismatch )
|
||||
{
|
||||
printf( "\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n" );
|
||||
return 1;
|
||||
}
|
||||
if( handshake == tracy::HandshakeNotAvailable )
|
||||
{
|
||||
printf( "\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n" );
|
||||
return 2;
|
||||
}
|
||||
if( handshake == tracy::HandshakeDropped )
|
||||
{
|
||||
printf( "\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n" );
|
||||
return 3;
|
||||
}
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
}
|
||||
int result = WaitForConnection( worker );
|
||||
if( result != 0 ) return result;
|
||||
printf( "\nTimer resolution: %s\n", tracy::TimeToString( worker.GetResolution() ) );
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -197,59 +129,18 @@ int main( int argc, char** argv )
|
||||
#endif
|
||||
|
||||
const auto firstTime = worker.GetFirstTime();
|
||||
auto& lock = worker.GetMbpsDataLock();
|
||||
|
||||
const auto t0 = std::chrono::high_resolution_clock::now();
|
||||
while( worker.IsConnected() )
|
||||
{
|
||||
// Relaxed order is sufficient here because `s_disconnect` is only ever
|
||||
// set by this thread or by the SigInt handler, and that handler does
|
||||
// nothing else than storing `s_disconnect`.
|
||||
if( s_disconnect.load( std::memory_order_relaxed ) )
|
||||
{
|
||||
worker.Disconnect();
|
||||
// Relaxed order is sufficient because only this thread ever reads
|
||||
// this value.
|
||||
s_disconnect.store(false, std::memory_order_relaxed );
|
||||
break;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
const auto mbps = worker.GetMbpsData().back();
|
||||
const auto compRatio = worker.GetCompRatio();
|
||||
const auto netTotal = worker.GetDataTransferred();
|
||||
lock.unlock();
|
||||
|
||||
// Output progress info only if destination is a TTY to avoid bloating
|
||||
// log files (so this is not just about usage of ANSI color codes).
|
||||
if( IsStdoutATerminal() )
|
||||
{
|
||||
const char* unit = "Mbps";
|
||||
float unitsPerMbps = 1.f;
|
||||
if( mbps < 0.1f )
|
||||
{
|
||||
unit = "Kbps";
|
||||
unitsPerMbps = 1000.f;
|
||||
}
|
||||
AnsiPrintf( ANSI_ERASE_LINE ANSI_CYAN ANSI_BOLD, "\r%7.2f %s", mbps * unitsPerMbps, unit );
|
||||
printf( " /");
|
||||
AnsiPrintf( ANSI_CYAN ANSI_BOLD, "%5.1f%%", compRatio * 100.f );
|
||||
printf( " =");
|
||||
AnsiPrintf( ANSI_YELLOW ANSI_BOLD, "%7.2f Mbps", mbps / compRatio );
|
||||
printf( " | ");
|
||||
AnsiPrintf( ANSI_YELLOW, "Tx: ");
|
||||
AnsiPrintf( ANSI_GREEN, "%s", tracy::MemSizeToString( netTotal ) );
|
||||
printf( " | ");
|
||||
AnsiPrintf( ANSI_RED ANSI_BOLD, "%s", tracy::MemSizeToString( tracy::memUsage.load( std::memory_order_relaxed ) ) );
|
||||
if( memoryLimit > 0 )
|
||||
{
|
||||
printf( " / " );
|
||||
AnsiPrintf( ANSI_BLUE ANSI_BOLD, "%s", tracy::MemSizeToString( memoryLimit ) );
|
||||
}
|
||||
printf( " | ");
|
||||
AnsiPrintf( ANSI_RED, "%s", tracy::TimeToString( worker.GetLastTime() - firstTime ) );
|
||||
fflush( stdout );
|
||||
}
|
||||
PrintCaptureProgress( worker, firstTime, memoryLimit );
|
||||
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
if( seconds != -1 )
|
||||
@@ -257,90 +148,13 @@ int main( int argc, char** argv )
|
||||
const auto dur = std::chrono::high_resolution_clock::now() - t0;
|
||||
if( std::chrono::duration_cast<std::chrono::seconds>(dur).count() >= seconds )
|
||||
{
|
||||
// Relaxed order is sufficient because only this thread ever reads
|
||||
// this value.
|
||||
s_disconnect.store(true, std::memory_order_relaxed );
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto t1 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
const auto& failure = worker.GetFailureType();
|
||||
if( failure != tracy::Worker::Failure::None )
|
||||
{
|
||||
AnsiPrintf( ANSI_RED ANSI_BOLD, "\nInstrumentation failure: %s", tracy::Worker::GetFailureString( failure ) );
|
||||
auto& fd = worker.GetFailureData();
|
||||
if( !fd.message.empty() )
|
||||
{
|
||||
printf( "\nContext: %s", fd.message.c_str() );
|
||||
}
|
||||
if( fd.callstack != 0 )
|
||||
{
|
||||
AnsiPrintf( ANSI_BOLD, "\nFailure callstack:\n" );
|
||||
auto& cs = worker.GetCallstack( fd.callstack );
|
||||
int fidx = 0;
|
||||
for( auto& entry : cs )
|
||||
{
|
||||
auto frameData = worker.GetCallstackFrame( entry );
|
||||
if( !frameData )
|
||||
{
|
||||
printf( "%3i. %p\n", fidx++, (void*)worker.GetCanonicalPointer( entry ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto fsz = frameData->size;
|
||||
for( uint8_t f=0; f<fsz; f++ )
|
||||
{
|
||||
const auto& frame = frameData->data[f];
|
||||
auto txt = worker.GetString( frame.name );
|
||||
|
||||
if( fidx == 0 && f != fsz-1 )
|
||||
{
|
||||
auto test = tracy::s_tracyStackFrames;
|
||||
bool match = false;
|
||||
do
|
||||
{
|
||||
if( strcmp( txt, *test ) == 0 )
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while( *++test );
|
||||
if( match ) continue;
|
||||
}
|
||||
|
||||
if( f == fsz-1 )
|
||||
{
|
||||
printf( "%3i. ", fidx++ );
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiPrintf( ANSI_BLACK ANSI_BOLD, "inl. " );
|
||||
}
|
||||
AnsiPrintf( ANSI_CYAN, "%s ", txt );
|
||||
txt = worker.GetString( frame.file );
|
||||
if( frame.line == 0 )
|
||||
{
|
||||
AnsiPrintf( ANSI_YELLOW, "(%s)", txt );
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiPrintf( ANSI_YELLOW, "(%s:%" PRIu32 ")", txt, frame.line );
|
||||
}
|
||||
if( frameData->imageName.Active() )
|
||||
{
|
||||
AnsiPrintf( ANSI_MAGENTA, " %s\n", worker.GetString( frameData->imageName ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PrintWorkerFailure( worker );
|
||||
|
||||
printf( "\nFrames: %" PRIu64 "\nTime span: %s\nZones: %s\nElapsed time: %s\nSaving trace...",
|
||||
worker.GetFrameCount( *worker.GetFramesBase() ), tracy::TimeToString( worker.GetLastTime() - firstTime ), tracy::RealToString( worker.GetZoneCount() ),
|
||||
|
||||
438
capture/src/capturedaemon.cpp
Normal file
438
capture/src/capturedaemon.cpp
Normal file
@@ -0,0 +1,438 @@
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <signal.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "../../getopt/getopt.h"
|
||||
#include "../../public/common/TracySocket.hpp"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "../../server/TracyBroadcast.hpp"
|
||||
#include "../../server/TracyFileWrite.hpp"
|
||||
#include "../../server/TracyMemory.hpp"
|
||||
#include "../../server/TracyPrint.hpp"
|
||||
#include "../../server/TracySysUtil.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
#include "CaptureOutput.hpp"
|
||||
|
||||
static std::atomic<bool> g_shutdown{false};
|
||||
static std::mutex g_clientsMutex;
|
||||
static uint16_t g_listenPort = 8086;
|
||||
static std::string g_filterName;
|
||||
static int g_filterPort = 0;
|
||||
static int64_t g_memoryLimit = -1;
|
||||
|
||||
void SigInt( int )
|
||||
{
|
||||
g_shutdown.store( true, std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
struct ClientStats
|
||||
{
|
||||
std::atomic<float> mbps{0};
|
||||
std::atomic<int64_t> txBytes{0};
|
||||
std::atomic<int64_t> memUsage{0};
|
||||
std::atomic<int64_t> firstTime{-1};
|
||||
};
|
||||
|
||||
struct ClientSession
|
||||
{
|
||||
std::string id;
|
||||
std::string programName;
|
||||
std::string address;
|
||||
uint16_t port;
|
||||
std::string outputFile;
|
||||
std::thread thread;
|
||||
std::atomic<bool> active{true};
|
||||
std::atomic<bool> finished{false};
|
||||
ClientStats stats;
|
||||
std::atomic<uint64_t> fileSize{0};
|
||||
};
|
||||
|
||||
static std::map<std::string, ClientSession*> g_clients;
|
||||
static std::unordered_set<std::string> g_outputFiles;
|
||||
|
||||
[[noreturn]] void Usage()
|
||||
{
|
||||
printf( "tracy-capture-daemon %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: tracy-capture-daemon -o <output_dir> [options]\n\n" );
|
||||
printf( "Options:\n" );
|
||||
printf( " -o, --output <dir> Output directory (required)\n" );
|
||||
printf( " -p, --port <port> UDP listen port (default: 8086)\n" );
|
||||
printf( " -m, --memory <limit> Memory limit per client as %% of system RAM\n" );
|
||||
printf( " --filter-name <pattern> Only capture clients matching program name\n" );
|
||||
printf( " --filter-port <port> Only capture clients with specific data port\n" );
|
||||
printf( " -h, --help Show this help\n" );
|
||||
printf( " -V, --version Show version information\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
std::string SanitizeName( const std::string& name )
|
||||
{
|
||||
std::string result;
|
||||
for( char c : name )
|
||||
{
|
||||
if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= '0' && c <= '9' ) || c == '_' || c == '-' )
|
||||
{
|
||||
result += c;
|
||||
}
|
||||
else if( c == ' ' || c == '\t' )
|
||||
{
|
||||
result += '_';
|
||||
}
|
||||
}
|
||||
if( result.empty() ) result = "unknown";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GenerateOutputFilename( const std::string& outputDir, const std::string& programName, const std::string& address, uint16_t port )
|
||||
{
|
||||
std::string base = SanitizeName( programName ) + "_" + address + "_" + std::to_string( port );
|
||||
std::string candidate = base + ".tracy";
|
||||
std::string path = outputDir + "/" + candidate;
|
||||
|
||||
int idx = 0;
|
||||
while( g_outputFiles.count( path ) || std::filesystem::exists( path ) )
|
||||
{
|
||||
idx++;
|
||||
candidate = base + "_" + std::to_string( idx ) + ".tracy";
|
||||
path = outputDir + "/" + candidate;
|
||||
}
|
||||
|
||||
g_outputFiles.insert( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
bool MatchesFilters( const tracy::BroadcastMessage& msg )
|
||||
{
|
||||
if( !g_filterName.empty() )
|
||||
{
|
||||
if( strstr( msg.programName, g_filterName.c_str() ) == nullptr )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if( g_filterPort > 0 && msg.listenPort != g_filterPort )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CaptureThread( ClientSession* session, const std::string& address, uint16_t port, int64_t memoryLimit, const std::string& outputFile )
|
||||
{
|
||||
printf( "Connecting to %s:%u...\n", address.c_str(), port );
|
||||
fflush( stdout );
|
||||
|
||||
tracy::Worker worker( address.c_str(), port, memoryLimit );
|
||||
|
||||
int result = WaitForConnection( worker );
|
||||
if( result != 0 )
|
||||
{
|
||||
session->active = false;
|
||||
session->finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
printf( "Connected to %s (%s:%u)\n", session->programName.c_str(), address.c_str(), port );
|
||||
|
||||
int64_t firstTime = worker.GetFirstTime();
|
||||
session->stats.firstTime = firstTime;
|
||||
|
||||
while( session->active && worker.IsConnected() )
|
||||
{
|
||||
auto& lock = worker.GetMbpsDataLock();
|
||||
lock.lock();
|
||||
float mbps = worker.GetMbpsData().back();
|
||||
int64_t txTotal = worker.GetDataTransferred();
|
||||
lock.unlock();
|
||||
|
||||
session->stats.mbps = mbps;
|
||||
session->stats.txBytes = txTotal;
|
||||
session->stats.memUsage = tracy::memUsage.load( std::memory_order_relaxed );
|
||||
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
}
|
||||
|
||||
printf( "\nSaving %s...", outputFile.c_str() );
|
||||
fflush( stdout );
|
||||
|
||||
auto file = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( outputFile.c_str(), tracy::FileCompression::Zstd, 3, 4 ) );
|
||||
if( file )
|
||||
{
|
||||
worker.Write( *file, false );
|
||||
file->Finish();
|
||||
auto stats = file->GetCompressionStatistics();
|
||||
session->fileSize = stats.second;
|
||||
AnsiPrintf( ANSI_GREEN ANSI_BOLD, " done!\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiPrintf( ANSI_RED ANSI_BOLD, " failed!\n" );
|
||||
}
|
||||
|
||||
session->finished = true;
|
||||
session->active = false;
|
||||
}
|
||||
|
||||
void RefreshDisplay( const std::string& listenAddr )
|
||||
{
|
||||
if( !IsTerminal() ) return;
|
||||
|
||||
printf( "\033[H\033[J" );
|
||||
|
||||
size_t clientCount = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
clientCount = g_clients.size();
|
||||
}
|
||||
|
||||
printf( "[%zu client%s] Listening on %s:%u... Press Ctrl+C to stop\n\n", clientCount, clientCount == 1 ? "" : "s", listenAddr.c_str(), g_listenPort );
|
||||
|
||||
int idx = 1;
|
||||
float totalMbps = 0;
|
||||
int64_t totalTx = 0;
|
||||
int64_t totalMem = 0;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
for( auto& [id, session] : g_clients )
|
||||
{
|
||||
printf( " [%d] %s @ %s:%u ", idx, session->programName.c_str(), session->address.c_str(), session->port );
|
||||
|
||||
if( session->finished )
|
||||
{
|
||||
printf( "finished (" );
|
||||
printf( "%s", tracy::MemSizeToString( session->fileSize.load() ) );
|
||||
printf( ")" );
|
||||
}
|
||||
else if( session->active )
|
||||
{
|
||||
float mbps = session->stats.mbps.load();
|
||||
int64_t tx = session->stats.txBytes.load();
|
||||
int64_t mem = session->stats.memUsage.load();
|
||||
int64_t firstTime = session->stats.firstTime.load();
|
||||
|
||||
printf( "%.1f Mbps | %s | %s", mbps, tracy::MemSizeToString( tx ), tracy::MemSizeToString( mem ) );
|
||||
|
||||
totalMbps += mbps;
|
||||
totalTx += tx;
|
||||
totalMem += mem;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "connecting..." );
|
||||
}
|
||||
printf( "\n" );
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
printf( "\nTotal: %.1f Mbps | %s | Mem: %s", totalMbps, tracy::MemSizeToString( totalTx ), tracy::MemSizeToString( totalMem ) );
|
||||
fflush( stdout );
|
||||
}
|
||||
|
||||
void PrintSummary()
|
||||
{
|
||||
printf( "\n\n=== Capture Summary ===\n" );
|
||||
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
int idx = 1;
|
||||
int64_t totalSize = 0;
|
||||
|
||||
for( auto& [id, session] : g_clients )
|
||||
{
|
||||
int64_t size = session->fileSize.load();
|
||||
totalSize += size;
|
||||
printf( " [%d] %s @ %s:%u -> %s (%s)\n", idx++, session->programName.c_str(), session->address.c_str(), session->port, session->outputFile.c_str(), tracy::MemSizeToString( size ) );
|
||||
}
|
||||
|
||||
printf( "\nTotal: %zu files, %s\n", g_clients.size(), tracy::MemSizeToString( totalSize ) );
|
||||
}
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if( !AttachConsole( ATTACH_PARENT_PROCESS ) )
|
||||
{
|
||||
AllocConsole();
|
||||
SetConsoleMode( GetStdHandle( STD_OUTPUT_HANDLE ), 0x07 );
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string outputDir;
|
||||
|
||||
static struct option longOptions[] = {
|
||||
{ "output", required_argument, nullptr, 'o' },
|
||||
{ "port", required_argument, nullptr, 'p' },
|
||||
{ "memory", required_argument, nullptr, 'm' },
|
||||
{ "filter-name", required_argument, nullptr, 1 },
|
||||
{ "filter-port", required_argument, nullptr, 2 },
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "version", no_argument, nullptr, 'V' },
|
||||
{ nullptr, 0, nullptr, 0 }
|
||||
};
|
||||
|
||||
int c;
|
||||
while( ( c = getopt_long( argc, argv, "o:p:m:hV", longOptions, nullptr ) ) != -1 )
|
||||
{
|
||||
switch( c )
|
||||
{
|
||||
case 'o':
|
||||
outputDir = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
g_listenPort = atoi( optarg );
|
||||
break;
|
||||
case 'm':
|
||||
g_memoryLimit = std::clamp( atoll( optarg ), 1ll, 999ll ) * tracy::GetPhysicalMemorySize() / 100;
|
||||
break;
|
||||
case 1:
|
||||
g_filterName = optarg;
|
||||
break;
|
||||
case 2:
|
||||
g_filterPort = atoi( optarg );
|
||||
break;
|
||||
case 'h':
|
||||
Usage();
|
||||
break;
|
||||
case 'V':
|
||||
printf( "tracy-capture-daemon %i.%i.%i / %s\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
exit( 0 );
|
||||
default:
|
||||
Usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( outputDir.empty() )
|
||||
{
|
||||
fprintf( stderr, "Error: Output directory is required (-o)\n\n" );
|
||||
Usage();
|
||||
}
|
||||
|
||||
std::filesystem::create_directories( outputDir );
|
||||
|
||||
InitTerminalDetection();
|
||||
|
||||
#ifdef _WIN32
|
||||
signal( SIGINT, SigInt );
|
||||
#else
|
||||
struct sigaction sigint, oldsigint;
|
||||
memset( &sigint, 0, sizeof( sigint ) );
|
||||
sigint.sa_handler = SigInt;
|
||||
sigaction( SIGINT, &sigint, &oldsigint );
|
||||
#endif
|
||||
|
||||
tracy::UdpListen udpSocket;
|
||||
if( !udpSocket.Listen( g_listenPort ) )
|
||||
{
|
||||
fprintf( stderr, "Error: Failed to listen on port %u\n", g_listenPort );
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf( "Listening on 0.0.0.0:%u... Press Ctrl+C to stop\n", g_listenPort );
|
||||
printf( "Output directory: %s\n", outputDir.c_str() );
|
||||
|
||||
const std::string listenAddr = "0.0.0.0";
|
||||
auto lastDisplay = std::chrono::steady_clock::now();
|
||||
|
||||
while( !g_shutdown )
|
||||
{
|
||||
tracy::IpAddress clientAddr;
|
||||
size_t len;
|
||||
const char* msg = udpSocket.Read( len, clientAddr, 100 );
|
||||
|
||||
if( msg )
|
||||
{
|
||||
auto parsed = tracy::ParseBroadcastMessage( msg, len );
|
||||
if( parsed )
|
||||
{
|
||||
std::string clientId = std::to_string( parsed->pid ) + "_" + clientAddr.GetText() + "_" + std::to_string( parsed->listenPort );
|
||||
|
||||
bool isNew = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
isNew = g_clients.find( clientId ) == g_clients.end();
|
||||
}
|
||||
|
||||
if( isNew && MatchesFilters( *parsed ) )
|
||||
{
|
||||
std::string addressStr = clientAddr.GetText();
|
||||
std::string outputFile = GenerateOutputFilename( outputDir, parsed->programName, addressStr, parsed->listenPort );
|
||||
|
||||
auto session = new ClientSession();
|
||||
session->id = clientId;
|
||||
session->programName = parsed->programName;
|
||||
session->address = addressStr;
|
||||
session->port = parsed->listenPort;
|
||||
session->outputFile = outputFile;
|
||||
session->active = true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
g_clients[clientId] = session;
|
||||
}
|
||||
|
||||
session->thread = std::thread( CaptureThread, session, addressStr, parsed->listenPort, g_memoryLimit, outputFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if( std::chrono::duration_cast<std::chrono::milliseconds>( now - lastDisplay ).count() >= 100 )
|
||||
{
|
||||
RefreshDisplay( listenAddr );
|
||||
lastDisplay = now;
|
||||
}
|
||||
}
|
||||
|
||||
printf( "\n\nShutting down... waiting for %zu client(s) to finish\n", g_clients.size() );
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
for( auto& [id, session] : g_clients )
|
||||
{
|
||||
session->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
for( auto& [id, session] : g_clients )
|
||||
{
|
||||
if( session->thread.joinable() )
|
||||
{
|
||||
session->thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrintSummary();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_clientsMutex );
|
||||
for( auto& [id, session] : g_clients )
|
||||
{
|
||||
delete session;
|
||||
}
|
||||
g_clients.clear();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
37
cmake/GitRef.cmake
Normal file
37
cmake/GitRef.cmake
Normal file
@@ -0,0 +1,37 @@
|
||||
function(add_git_ref target)
|
||||
if(NOT DEFINED GIT_REV)
|
||||
set(GIT_REV "HEAD")
|
||||
endif()
|
||||
|
||||
get_property(_git_ref_created GLOBAL PROPERTY _GIT_REF_CREATED)
|
||||
if(NOT _git_ref_created)
|
||||
set_property(GLOBAL PROPERTY _GIT_REF_CREATED TRUE)
|
||||
find_package(Git)
|
||||
set_property(GLOBAL PROPERTY _GIT_FOUND "${Git_FOUND}")
|
||||
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 ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
|
||||
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(WARNING "git not found, using 'unknown' as git ref.")
|
||||
add_custom_command(
|
||||
OUTPUT GitRef.hpp
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
get_property(_git_found GLOBAL PROPERTY _GIT_FOUND)
|
||||
if(_git_found)
|
||||
add_dependencies(${target} git-ref)
|
||||
else()
|
||||
target_sources(${target} PUBLIC GitRef.hpp)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -1,8 +1,15 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/options.cmake)
|
||||
|
||||
set_option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
|
||||
set_option(NO_LTO "Disable interprocedural optimization (LTO)" OFF)
|
||||
set_option(NO_MOLD_LINKER "Disable mold linker (use default linker)" OFF)
|
||||
set_option(NO_CCACHE "Disable ccache acceleration" OFF)
|
||||
|
||||
if (NOT NO_ISA_EXTENSIONS)
|
||||
include(CheckCXXCompilerFlag)
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
|
||||
CHECK_CXX_COMPILER_FLAG("-mcpu=native" COMPILER_SUPPORTS_MCPU_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MCPU_NATIVE)
|
||||
add_compile_options(-mcpu=native)
|
||||
endif()
|
||||
else()
|
||||
@@ -36,18 +43,21 @@ endif()
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN -D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
add_compile_options(/MP)
|
||||
# /MP is MSVC-specific for multi-processor compilation
|
||||
if(MSVC)
|
||||
add_compile_options(/MP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
add_compile_options(-pthread -DIMGUI_IMPL_OPENGL_ES2)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT EMSCRIPTEN)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT EMSCRIPTEN AND NOT NO_LTO)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT NO_MOLD_LINKER)
|
||||
find_program(MOLD_LINKER mold)
|
||||
if(MOLD_LINKER)
|
||||
set(CMAKE_LINKER_TYPE "MOLD")
|
||||
@@ -57,10 +67,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_program(CCACHE ccache)
|
||||
if(CCACHE)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
||||
if(NOT NO_CCACHE)
|
||||
find_program(CCACHE ccache)
|
||||
if(CCACHE)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(GENERATE OUTPUT .gitignore CONTENT "*")
|
||||
|
||||
48
cmake/options.cmake
Normal file
48
cmake/options.cmake
Normal file
@@ -0,0 +1,48 @@
|
||||
# Reusable option macros for Tracy CMake projects
|
||||
#
|
||||
# Usage:
|
||||
# set_option(OPTION_NAME "Help text" ON/OFF [TARGET]) - for boolean options
|
||||
# set_option_value(VAR_NAME "Help text" "value" [TARGET]) - for value options (CACHE STRING)
|
||||
# set_option_value_as_string(VAR_NAME "Help text" "value" [TARGET]) - for value options as C string literals
|
||||
#
|
||||
# [TARGET] is optional and specifies a target to which the option will
|
||||
# be added as a compile definition (e.g., -DOPTION_NAME or -DVAR_NAME=value).
|
||||
|
||||
# Boolean option (ON/OFF).
|
||||
macro(set_option option help value)
|
||||
option(${option} ${help} ${value})
|
||||
if(${option})
|
||||
message(STATUS "${option}: ON")
|
||||
if(${ARGC} GREATER 3)
|
||||
target_compile_definitions(${ARGV3} PUBLIC ${option})
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "${option}: OFF")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Value option (string/number).
|
||||
macro(set_option_value var help default)
|
||||
set(${var} ${default} CACHE STRING "${help}")
|
||||
if(${var})
|
||||
message(STATUS "${var}: ${${var}}")
|
||||
if(${ARGC} GREATER 3)
|
||||
target_compile_definitions(${ARGV3} PUBLIC ${var}=${${var}})
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "${var}: (not set)")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Value option embedded as a C string literal (VAR="value").
|
||||
macro(set_option_value_as_string var help default)
|
||||
set(${var} ${default} CACHE STRING "${help}")
|
||||
if(${var})
|
||||
message(STATUS "${var}: ${${var}}")
|
||||
if(${ARGC} GREATER 3)
|
||||
target_compile_definitions(${ARGV3} PUBLIC "${var}=\"${${var}}\"")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "${var}: (not set)")
|
||||
endif()
|
||||
endmacro()
|
||||
14
cmake/ppqsort-semaphore.patch
Normal file
14
cmake/ppqsort-semaphore.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff --git i/include/ppqsort/parallel/cpp/thread_pool.h w/include/ppqsort/parallel/cpp/thread_pool.h
|
||||
--- i/include/ppqsort/parallel/cpp/thread_pool.h
|
||||
+++ w/include/ppqsort/parallel/cpp/thread_pool.h
|
||||
@@ -134,7 +134,9 @@ namespace ppqsort::impl::cpp {
|
||||
alignas(parameters::cacheline_size) std::atomic<std::size_t> pending_tasks_{0};
|
||||
alignas(parameters::cacheline_size) std::atomic<std::size_t> total_tasks_{0};
|
||||
alignas(parameters::cacheline_size) std::atomic<bool> to_stop_{false};
|
||||
- std::binary_semaphore threads_done_semaphore_{0}; // used to wait for all tasks to finish
|
||||
+ // counting_semaphore: multiple workers may concurrently observe total_tasks_ == 0
|
||||
+ // and call release(); a binary_semaphore would assert when the count exceeds 1.
|
||||
+ std::counting_semaphore<> threads_done_semaphore_{0};
|
||||
std::mutex mtx_priority_;
|
||||
bool stopped = false;
|
||||
};
|
||||
@@ -14,6 +14,7 @@ list(TRANSFORM TRACY_COMMON_SOURCES PREPEND "${TRACY_COMMON_DIR}/")
|
||||
set(TRACY_SERVER_DIR ${CMAKE_CURRENT_LIST_DIR}/../server)
|
||||
|
||||
set(TRACY_SERVER_SOURCES
|
||||
TracyBroadcast.cpp
|
||||
TracyMemory.cpp
|
||||
TracyMmap.cpp
|
||||
TracyPrint.cpp
|
||||
|
||||
@@ -26,7 +26,7 @@ else()
|
||||
CPMAddPackage(
|
||||
NAME capstone
|
||||
GITHUB_REPOSITORY capstone-engine/capstone
|
||||
GIT_TAG 6.0.0-Alpha5
|
||||
GIT_TAG 6.0.0-Alpha9
|
||||
OPTIONS
|
||||
"CAPSTONE_X86_ATT_DISABLE ON"
|
||||
"CAPSTONE_ALPHA_SUPPORT OFF"
|
||||
@@ -94,7 +94,7 @@ else()
|
||||
CPMAddPackage(
|
||||
NAME freetype
|
||||
GITHUB_REPOSITORY freetype/freetype
|
||||
GIT_TAG VER-2-14-1
|
||||
GIT_TAG VER-2-14-3
|
||||
OPTIONS
|
||||
"FT_DISABLE_HARFBUZZ ON"
|
||||
"FT_WITH_HARFBUZZ OFF"
|
||||
@@ -137,7 +137,7 @@ target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
|
||||
CPMAddPackage(
|
||||
NAME ImGui
|
||||
GITHUB_REPOSITORY ocornut/imgui
|
||||
GIT_TAG v1.92.5-docking
|
||||
GIT_TAG v1.92.8-docking
|
||||
DOWNLOAD_ONLY TRUE
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
|
||||
@@ -160,6 +160,7 @@ add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
|
||||
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
|
||||
target_link_libraries(TracyImGui PUBLIC TracyFreetype)
|
||||
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
|
||||
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
|
||||
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
|
||||
@@ -183,7 +184,7 @@ if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
|
||||
CPMAddPackage(
|
||||
NAME nfd
|
||||
GITHUB_REPOSITORY btzy/nativefiledialog-extended
|
||||
GIT_TAG v1.2.1
|
||||
GIT_TAG v1.3.0
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
OPTIONS
|
||||
"NFD_PORTAL ${NFD_PORTAL}"
|
||||
@@ -198,6 +199,7 @@ CPMAddPackage(
|
||||
VERSION 1.0.6
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/ppqsort-nodebug.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/ppqsort-semaphore.patch"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
@@ -215,7 +217,7 @@ CPMAddPackage(
|
||||
CPMAddPackage(
|
||||
NAME md4c
|
||||
GITHUB_REPOSITORY mity/md4c
|
||||
GIT_TAG release-0.5.2
|
||||
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
@@ -252,7 +254,7 @@ if(NOT EMSCRIPTEN)
|
||||
CPMAddPackage(
|
||||
NAME usearch
|
||||
GITHUB_REPOSITORY unum-cloud/usearch
|
||||
GIT_TAG v2.21.3
|
||||
GIT_TAG v2.25.2
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
@@ -285,7 +287,7 @@ if(NOT EMSCRIPTEN)
|
||||
CPMAddPackage(
|
||||
NAME libcurl
|
||||
GITHUB_REPOSITORY curl/curl
|
||||
GIT_TAG curl-8_17_0
|
||||
GIT_TAG curl-8_20_0
|
||||
OPTIONS
|
||||
"BUILD_STATIC_LIBS ON"
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
|
||||
|
||||
set(NO_STATISTICS OFF)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
@@ -17,12 +15,14 @@ project(
|
||||
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)
|
||||
|
||||
set(PROGRAM_FILES
|
||||
src/csvexport.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
||||
add_git_ref(${PROJECT_NAME})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
#include "../../server/TracyFileRead.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "../../getopt/getopt.h"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
void print_usage_exit(int e)
|
||||
{
|
||||
fprintf(stderr, "tracy-csvexport %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef);
|
||||
fprintf(stderr, "Extract statistics from a trace to a CSV format\n");
|
||||
fprintf(stderr, "Usage:\n");
|
||||
fprintf(stderr, " extract [OPTION...] <trace file>\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " -h, --help Print usage\n");
|
||||
fprintf(stderr, " -V, --version Show version information\n");
|
||||
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
|
||||
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
|
||||
fprintf(stderr, " -c, --case Case sensitive filtering\n");
|
||||
@@ -61,6 +65,7 @@ Args parse_args(int argc, char** argv)
|
||||
|
||||
struct option long_opts[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "filter", optional_argument, NULL, 'f' },
|
||||
{ "sep", optional_argument, NULL, 's' },
|
||||
{ "case", no_argument, NULL, 'c' },
|
||||
@@ -74,13 +79,16 @@ Args parse_args(int argc, char** argv)
|
||||
};
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "hf:s:ceugmp", long_opts, NULL)) != -1)
|
||||
while ((c = getopt_long(argc, argv, "hf:s:ceugmpV", long_opts, NULL)) != -1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
print_usage_exit(0);
|
||||
break;
|
||||
case 'V':
|
||||
printf( "tracy-csvexport %i.%i.%i / %s\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
exit( 0 );
|
||||
case 'f':
|
||||
args.filter = optarg;
|
||||
break;
|
||||
|
||||
57
examples/CustomPlatform/CustomPlatform.cpp
Normal file
57
examples/CustomPlatform/CustomPlatform.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Template implementations of the tracy::Platform* hooks. Pair with the
|
||||
// platform header (see CustomPlatform.h) and link this into your final
|
||||
// binary.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "CustomPlatform.h"
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
uint32_t PlatformGetThreadId()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PlatformGetHostname( char* buf, size_t size )
|
||||
{
|
||||
const char* placeholder = "(?)";
|
||||
if( size == 0 ) return;
|
||||
const size_t n = strlen( placeholder );
|
||||
const size_t copy = n < size - 1 ? n : size - 1;
|
||||
memcpy( buf, placeholder, copy );
|
||||
buf[copy] = '\0';
|
||||
}
|
||||
|
||||
const char* PlatformGetUserLogin()
|
||||
{
|
||||
return "(?)";
|
||||
}
|
||||
|
||||
const char* PlatformGetUserFullName()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size )
|
||||
{
|
||||
// Stub: report failure so Tracy skips the snapshot. Real impls use SEH
|
||||
// on Win32, pipe(2) on POSIX, or an equivalent probe-and-copy primitive.
|
||||
(void)dst; (void)src; (void)size;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stubs forward to the C runtime. Swap in the allocator you actually want.
|
||||
|
||||
void* PlatformMalloc( size_t size ) { return malloc( size ); }
|
||||
void PlatformFree( void* ptr ) { free( ptr ); }
|
||||
void* PlatformRealloc( void* ptr, size_t size ) { return realloc( ptr, size ); }
|
||||
|
||||
void PlatformAllocatorInit() {}
|
||||
void PlatformAllocatorThreadInit() {}
|
||||
void PlatformAllocatorFinalize() {}
|
||||
void PlatformAllocatorThreadFinalize(){}
|
||||
|
||||
}
|
||||
73
examples/CustomPlatform/CustomPlatform.h
Normal file
73
examples/CustomPlatform/CustomPlatform.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// Template platform header for unsupported targets.
|
||||
//
|
||||
// Copy into your project, fill in the sections you need, and point Tracy at
|
||||
// it via -DTRACY_PLATFORM_HEADER="\"my_platform.h\"". Provide the
|
||||
// implementations in any TU linked into your final binary (see
|
||||
// CustomPlatform.cpp).
|
||||
//
|
||||
// Use this only for the TRACY_HAS_CUSTOM_* hooks and matching Platform*
|
||||
// declarations — don't set unrelated TRACY_* options here. Some are checked
|
||||
// before this header is included, so the result would depend on which TU
|
||||
// consulted them; set those at the build system level instead.
|
||||
//
|
||||
// For platform-specific features without a custom hook (call stacks,
|
||||
// context switches, crash handling, system tracing, etc.), disable them at
|
||||
// the build system level with the matching TRACY_NO_* macro.
|
||||
|
||||
#ifndef __MY_TRACY_PLATFORM_H__
|
||||
#define __MY_TRACY_PLATFORM_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
// --- Thread id --------------------------------------------------------------
|
||||
//
|
||||
// Required if defaults in TracySystem.cpp do not matches your platform.
|
||||
// Note pthread_self() is NOT suitable, it returns a library handle, not a kernel id.
|
||||
//#define TRACY_HAS_CUSTOM_THREAD_ID
|
||||
uint32_t PlatformGetThreadId();
|
||||
|
||||
|
||||
// --- User info --------------------------------------------------------------
|
||||
//
|
||||
// Identifies the machine and user in the trace header. Return placeholder
|
||||
// strings (e.g. "(?)") from any of these if your platform has no equivalent
|
||||
// notion.
|
||||
//#define TRACY_HAS_CUSTOM_USER_INFO
|
||||
void PlatformGetHostname( char* buf, size_t size );
|
||||
const char* PlatformGetUserLogin();
|
||||
const char* PlatformGetUserFullName();
|
||||
|
||||
|
||||
// --- Safe memory copy -------------------------------------------------------
|
||||
//
|
||||
// Tracy uses this to snapshot potentially-unmapped memory during sampling.
|
||||
// Must not crash on unreadable input — return false instead. Plain memcpy()
|
||||
// is NOT a valid implementation.
|
||||
//#define TRACY_HAS_CUSTOM_SAFE_COPY
|
||||
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size );
|
||||
|
||||
|
||||
// --- Allocator --------------------------------------------------------------
|
||||
//
|
||||
// Replaces Tracy's internal allocator. Drop in the system allocator, an
|
||||
// in-house one, or any third-party allocator you like. Malloc/Free/Realloc
|
||||
// must be thread-safe; ThreadInit is an optional prime, not a precondition.
|
||||
// Finalize must also tear down the calling thread's per-thread state, the
|
||||
// way rpmalloc_finalize() does — Tracy does not call ThreadFinalize for the
|
||||
// shutdown thread before Finalize.
|
||||
//#define TRACY_HAS_CUSTOM_ALLOCATOR
|
||||
void* PlatformMalloc( size_t size );
|
||||
void PlatformFree( void* ptr );
|
||||
void* PlatformRealloc( void* ptr, size_t size );
|
||||
void PlatformAllocatorInit();
|
||||
void PlatformAllocatorThreadInit();
|
||||
void PlatformAllocatorFinalize();
|
||||
void PlatformAllocatorThreadFinalize();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#define kSimdWidth 4
|
||||
|
||||
#if !defined(__arm__) && !defined(__arm64__) && !defined(__EMSCRIPTEN__)
|
||||
#if !defined(__arm__) && !defined(__arm64__) && !defined(__EMSCRIPTEN__) && !defined(_M_ARM64)
|
||||
|
||||
// ---- SSE implementation
|
||||
|
||||
@@ -141,8 +141,14 @@ VM_INLINE float hmin(float4 v)
|
||||
// Returns a 4-bit code where bit0..bit3 is X..W
|
||||
VM_INLINE unsigned mask(float4 v)
|
||||
{
|
||||
#if defined(_M_ARM64)
|
||||
static const uint32_t values[4] = { 1u, 2u, 4u, 8u };
|
||||
const uint32x4_t movemask = vld1q_u32( values );
|
||||
const uint32x4_t highbit = vdupq_n_u32( 0x80000000u );
|
||||
#else
|
||||
static const uint32x4_t movemask = { 1, 2, 4, 8 };
|
||||
static const uint32x4_t highbit = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 };
|
||||
#endif
|
||||
uint32x4_t t0 = vreinterpretq_u32_f32(v.m);
|
||||
uint32x4_t t1 = vtstq_u32(t0, highbit);
|
||||
uint32x4_t t2 = vandq_u32(t1, movemask);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#if DO_FLOAT3_WITH_SIMD
|
||||
|
||||
|
||||
#if !defined(__arm__) && !defined(__arm64__)
|
||||
#if !defined(__arm__) && !defined(__arm64__) && !defined(_M_ARM64)
|
||||
|
||||
// ---- SSE implementation
|
||||
|
||||
@@ -223,8 +223,14 @@ VM_INLINE float3 cross(float3 a, float3 b)
|
||||
// Returns a 3-bit code where bit0..bit2 is X..Z
|
||||
VM_INLINE unsigned mask(float3 v)
|
||||
{
|
||||
#if defined(_M_ARM64)
|
||||
static const uint32_t values[4] = { 1u, 2u, 4u, 8u };
|
||||
const uint32x4_t movemask = vld1q_u32( values );
|
||||
const uint32x4_t highbit = vdupq_n_u32( 0x80000000u );
|
||||
#else
|
||||
static const uint32x4_t movemask = { 1, 2, 4, 8 };
|
||||
static const uint32x4_t highbit = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 };
|
||||
#endif
|
||||
uint32x4_t t0 = vreinterpretq_u32_f32(v.m);
|
||||
uint32x4_t t1 = vtstq_u32(t0, highbit);
|
||||
uint32x4_t t2 = vandq_u32(t1, movemask);
|
||||
|
||||
@@ -72,7 +72,11 @@ namespace
|
||||
#if defined _M_IX86 || defined _M_X64
|
||||
#pragma intrinsic(_mm_pause)
|
||||
inline void Pause() { _mm_pause(); }
|
||||
#endif
|
||||
#elif defined(_M_ARM64)
|
||||
inline void Pause() { __yield(); }
|
||||
#else
|
||||
inline void Pause() { /* No ops*/ }
|
||||
#endif
|
||||
#elif defined __i386__ || defined __x86_64__
|
||||
inline void Pause() { __asm__ __volatile__("pause;"); }
|
||||
#else
|
||||
|
||||
0
examples/cuda/README.md
Normal file
0
examples/cuda/README.md
Normal file
39
examples/cuda/graph/CMakeLists.txt
Normal file
39
examples/cuda/graph/CMakeLists.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(CUDAGraphDemo LANGUAGES CXX CUDA)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CUDA_STANDARD 17)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
|
||||
set(TRACY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../.."
|
||||
CACHE PATH "Root of the Tracy repository")
|
||||
set(TRACY_PUBLIC "${TRACY_PATH}/public")
|
||||
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# cuda-graph-demo.cu embeds Tracy via #include <TracyClient.cpp> (unity build),
|
||||
# so no separate TracyClient library is needed — just expose the public headers.
|
||||
add_executable(cuda-graph-demo cuda-graph-demo.cu)
|
||||
target_include_directories(cuda-graph-demo PRIVATE ${TRACY_PUBLIC})
|
||||
target_link_libraries(cuda-graph-demo PRIVATE
|
||||
CUDA::cupti CUDA::cuda_driver Threads::Threads ${CMAKE_DL_LIBS})
|
||||
|
||||
# ctest-related integration below
|
||||
# to run the binaries via ctest:
|
||||
# ctest --test-dir <cmake-build-dir> -R <binary-name> -C <build-config>
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME cuda-graph-demo COMMAND cuda-graph-demo)
|
||||
|
||||
# On Windows, CUPTI's DLL must be on PATH at runtime.
|
||||
if(WIN32)
|
||||
set(_cupti_dir "$<TARGET_FILE_DIR:CUDA::cupti>")
|
||||
set_target_properties(cuda-graph-demo PROPERTIES
|
||||
VS_DEBUGGER_ENVIRONMENT "PATH=${_cupti_dir};$ENV{PATH}")
|
||||
set_tests_properties(cuda-graph-demo PROPERTIES
|
||||
ENVIRONMENT "PATH=${_cupti_dir};$ENV{PATH}")
|
||||
endif()
|
||||
11
examples/cuda/graph/build.sh
Normal file
11
examples/cuda/graph/build.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
TRACY_PATH=<path-to-tracy>
|
||||
CUDA_TOOLKIT_PATH=/usr/local/cuda
|
||||
CUDA_CUPTI_PATH=${CUDA_TOOLKIT_PATH}/extras/CUPTI
|
||||
|
||||
# pass -v to nvcc for verbose build information
|
||||
nvcc -O2 -std=c++17 cuda-graph-demo.cu \
|
||||
-o cuda-graph-demo \
|
||||
-I "${TRACY_PATH}/public" \
|
||||
-I "${CUDA_CUPTI_PATH}/include" -I "${CUDA_TOOLKIT_PATH}/include" \
|
||||
-L "${CUDA_CUPTI_PATH}/lib64" -L "${CUDA_TOOLKIT_PATH}/lib64" \
|
||||
-lcupti -lcuda
|
||||
146
examples/cuda/graph/cuda-graph-demo.cu
Normal file
146
examples/cuda/graph/cuda-graph-demo.cu
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
// WARN: for simplicity, we enable and "embed" the Tracy client directly into the code
|
||||
#define TRACY_ENABLE
|
||||
#include <TracyClient.cpp>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyCUDA.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
#define CUDA_CHECK(call) \
|
||||
do { \
|
||||
cudaError_t err__ = (call); \
|
||||
if (err__ != cudaSuccess) { \
|
||||
std::fprintf(stderr, "CUDA error %s at %s:%d: %s\n", \
|
||||
cudaGetErrorName(err__), __FILE__, __LINE__, \
|
||||
cudaGetErrorString(err__)); \
|
||||
std::exit(EXIT_FAILURE); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
__global__ void saxpy(float a, const float* x, float* y, int n)
|
||||
{
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) y[i] = a * x[i] + y[i];
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// CUPTI-backed Tracy context. Auto-captures all CUDA activity from the
|
||||
// point StartProfiling() is called until StopProfiling(). The background
|
||||
// collector thread flushes activity into Tracy; the explicit Collect()
|
||||
// calls below just force a flush at known phase boundaries.
|
||||
auto* cudaCtx = TracyCUDAContext();
|
||||
{
|
||||
constexpr char ctxName[] = "CUDA Graph Demo";
|
||||
TracyCUDAContextName(cudaCtx, ctxName, sizeof(ctxName) - 1);
|
||||
}
|
||||
TracyCUDAStartProfiling(cudaCtx);
|
||||
|
||||
constexpr int N = 1 << 16; // small N => kernel is short => launch overhead dominates
|
||||
constexpr int KERNELS_PER_GRAPH = 32; // chain length captured into the graph
|
||||
constexpr int OUTER_ITERS = 2000; // how many times we replay the chain
|
||||
|
||||
// allocate device buffers
|
||||
float *dX = nullptr, *dY = nullptr;
|
||||
CUDA_CHECK(cudaMalloc(&dX, N * sizeof(float)));
|
||||
CUDA_CHECK(cudaMalloc(&dY, N * sizeof(float)));
|
||||
|
||||
std::vector<float> hX(N, 1.0f);
|
||||
CUDA_CHECK(cudaMemcpy(dX, hX.data(), N * sizeof(float), cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream = nullptr;
|
||||
CUDA_CHECK(cudaStreamCreate(&stream));
|
||||
|
||||
const dim3 block(256);
|
||||
const dim3 grid((N + block.x - 1) / block.x);
|
||||
|
||||
cudaEvent_t evStart, evStop;
|
||||
CUDA_CHECK(cudaEventCreate(&evStart));
|
||||
CUDA_CHECK(cudaEventCreate(&evStop));
|
||||
|
||||
// warm-up (so first-launch lazy-init and/or JIT doesn't bias the measurement)
|
||||
saxpy<<<grid, block, 0, stream>>>(0.0f, dX, dY, N);
|
||||
CUDA_CHECK(cudaStreamSynchronize(stream));
|
||||
|
||||
// baseline: launch each kernel directly on the stream
|
||||
float msStream = 0.0f;
|
||||
{
|
||||
ZoneScopedN("stream-launches");
|
||||
CUDA_CHECK(cudaMemsetAsync(dY, 0, N * sizeof(float), stream));
|
||||
CUDA_CHECK(cudaEventRecord(evStart, stream));
|
||||
for (int outer = 0; outer < OUTER_ITERS; ++outer) {
|
||||
for (int k = 0; k < KERNELS_PER_GRAPH; ++k) {
|
||||
saxpy<<<grid, block, 0, stream>>>(1.0e-6f, dX, dY, N);
|
||||
}
|
||||
}
|
||||
CUDA_CHECK(cudaEventRecord(evStop, stream));
|
||||
CUDA_CHECK(cudaEventSynchronize(evStop));
|
||||
CUDA_CHECK(cudaEventElapsedTime(&msStream, evStart, evStop));
|
||||
TracyCUDACollect(cudaCtx);
|
||||
}
|
||||
|
||||
// capture: record the same kernel chain into a graph
|
||||
cudaGraph_t graph = nullptr;
|
||||
cudaGraphExec_t graphExec = nullptr;
|
||||
{
|
||||
ZoneScopedN("graph-capture");
|
||||
// cudaStreamCaptureModeRelaxed allows the calling thread to perform
|
||||
// unrelated CUDA work during capture; ThreadLocal is stricter if you need
|
||||
// isolation. Most short, single-stream captures work fine in either mode.
|
||||
CUDA_CHECK(cudaStreamBeginCapture(stream, cudaStreamCaptureModeRelaxed));
|
||||
for (int k = 0; k < KERNELS_PER_GRAPH; ++k) {
|
||||
saxpy<<<grid, block, 0, stream>>>(1.0e-6f, dX, dY, N);
|
||||
}
|
||||
CUDA_CHECK(cudaStreamEndCapture(stream, &graph));
|
||||
|
||||
// Instantiate once -> reusable executable graph.
|
||||
CUDA_CHECK(cudaGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
|
||||
|
||||
// The template graph isn't needed once instantiated.
|
||||
CUDA_CHECK(cudaGraphDestroy(graph));
|
||||
}
|
||||
|
||||
// replay: launch the instantiated graph OUTER_ITERS times
|
||||
float msGraph = 0.0f;
|
||||
{
|
||||
ZoneScopedN("graph-launches");
|
||||
CUDA_CHECK(cudaMemsetAsync(dY, 0, N * sizeof(float), stream));
|
||||
CUDA_CHECK(cudaEventRecord(evStart, stream));
|
||||
for (int outer = 0; outer < OUTER_ITERS; ++outer) {
|
||||
CUDA_CHECK(cudaGraphLaunch(graphExec, stream));
|
||||
}
|
||||
CUDA_CHECK(cudaEventRecord(evStop, stream));
|
||||
CUDA_CHECK(cudaEventSynchronize(evStop));
|
||||
CUDA_CHECK(cudaEventElapsedTime(&msGraph, evStart, evStop));
|
||||
TracyCUDACollect(cudaCtx);
|
||||
}
|
||||
|
||||
// sanity check: y[i] = OUTER_ITERS * KERNELS_PER_GRAPH * 1e-6 * x[i]
|
||||
std::vector<float> hY(N);
|
||||
CUDA_CHECK(cudaMemcpy(hY.data(), dY, N * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
const float expected = float(OUTER_ITERS) * float(KERNELS_PER_GRAPH) * 1.0e-6f;
|
||||
|
||||
std::printf("Stream launches: %8.3f ms (%d kernels)\n",
|
||||
msStream, OUTER_ITERS * KERNELS_PER_GRAPH);
|
||||
std::printf("Graph launches: %8.3f ms (%d graph launches x %d kernels)\n",
|
||||
msGraph, OUTER_ITERS, KERNELS_PER_GRAPH);
|
||||
std::printf("Speedup : %8.2fx\n", msStream / msGraph);
|
||||
std::printf("hY[0] = %.6e (expected %.6e)\n", hY[0], expected);
|
||||
|
||||
// shutdown
|
||||
CUDA_CHECK(cudaGraphExecDestroy(graphExec));
|
||||
CUDA_CHECK(cudaEventDestroy(evStart));
|
||||
CUDA_CHECK(cudaEventDestroy(evStop));
|
||||
CUDA_CHECK(cudaStreamDestroy(stream));
|
||||
CUDA_CHECK(cudaFree(dX));
|
||||
CUDA_CHECK(cudaFree(dY));
|
||||
|
||||
TracyCUDAStopProfiling(cudaCtx);
|
||||
TracyCUDAContextDestroy(cudaCtx);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// g++ identify.cpp -lpthread ../public/common/tracy_lz4.cpp ../zstd/common/*.c ../zstd/decompress/*.c ../zstd/decompress/huf_decompress_amd64.S
|
||||
// g++ identify.cpp -lpthread ../public/common/tracy_lz4.cpp -lzstd
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "../public/common/TracyVersion.hpp"
|
||||
|
||||
static const uint8_t FileHeader[8] { 't', 'r', 'a', 'c', 'y', tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch };
|
||||
enum { FileHeaderMagic = 5 };
|
||||
constexpr size_t FileHeaderMagic = 5;
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
rm -rf tracy-build
|
||||
mkdir tracy-build
|
||||
|
||||
./update-meson-version.sh
|
||||
|
||||
if [ ! -f vswhere.exe ]; then
|
||||
wget https://github.com/microsoft/vswhere/releases/download/2.8.4/vswhere.exe
|
||||
fi
|
||||
|
||||
MSVC=`./vswhere.exe -property installationPath -version '[17.0,17.999]' | head -n 1`
|
||||
MSVC=`wslpath "$MSVC" | tr -d '\r'`
|
||||
MSBUILD=$MSVC/MSBuild/Current/Bin/MSBuild.exe
|
||||
|
||||
for i in capture csvexport import-chrome update; do
|
||||
echo $i...
|
||||
"$MSBUILD" ../$i/build/win32/$i.sln /t:Clean /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
|
||||
"$MSBUILD" ../$i/build/win32/$i.sln /t:Build /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
|
||||
cp ../$i/build/win32/x64/Release/$i.exe tracy-build/
|
||||
done
|
||||
|
||||
echo profiler...
|
||||
"$MSBUILD" ../profiler/build/win32/Tracy.sln /t:Clean /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
|
||||
"$MSBUILD" ../profiler/build/win32/Tracy.sln /t:Build /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
|
||||
cp ../profiler/build/win32/x64/Release/Tracy.exe tracy-build/
|
||||
3
extra/mcp/.gitignore
vendored
Normal file
3
extra/mcp/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
tracy_mcp.port
|
||||
tracy_mcp.pid
|
||||
*.local.sh
|
||||
72
extra/mcp/eval_guide.md
Normal file
72
extra/mcp/eval_guide.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Tracy MCP eval guide
|
||||
|
||||
This document covers the bindings-layer detail that the curated catalog
|
||||
(`tracy://catalog`) and analysis guidance (`tracy://prompt`) do not.
|
||||
|
||||
## ctx
|
||||
|
||||
`ctx` is a `TracyServerBindings.Worker` — the same object Tracy Assist's
|
||||
C++ tools query through `Worker::Get*`. The pybind methods are the canonical
|
||||
data surface. Common entry points:
|
||||
|
||||
- Zones: `get_all_zone_stats()` (every callsite, large), `get_root_zone_stats()`
|
||||
(top-level zones only, useful for "where is the program spending time"),
|
||||
`get_zone_stats(srcloc_id)`, `get_child_zone_stats(srcloc_id)` (subtract for
|
||||
self-time), `get_zone_durations(name)`, `get_zone_count()`,
|
||||
`get_all_zone_source_locations()`
|
||||
- GPU zones: `get_all_gpu_zone_stats()`, `get_gpu_zone_durations(...)`,
|
||||
`get_gpu_contexts()`
|
||||
- Frames: `get_frame_count()`, `get_frame_times()`, `get_frame_times_named(name)`,
|
||||
`get_frame_boundaries()`, `get_zones_in_frame(...)`
|
||||
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
|
||||
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
|
||||
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
|
||||
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
|
||||
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
|
||||
|
||||
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
|
||||
|
||||
## Units and conventions
|
||||
|
||||
- All time values returned by Worker methods are **nanoseconds** (int).
|
||||
`get_first_time()` / `get_last_time()` bound the capture timeline.
|
||||
- `ZoneStats` fields: `count`, `total`, `min`, `max`, `avg`, `sum_sq`. `total`
|
||||
is the inclusive aggregate; use `get_child_zone_stats(srcloc_id)` to subtract
|
||||
child time when you need self-time.
|
||||
- `get_all_zone_stats()` returns `dict[str, ZoneStats]` keyed by an opaque label
|
||||
of the form `'name (addr)[arch] <srcloc_id>'`. The trailing `<id>` is the
|
||||
source-location ID — the int accepted by `get_zone_stats(int)`,
|
||||
`get_zone_durations_by_id`, and friends. Parse it with a regex if you need
|
||||
to join across calls.
|
||||
- Source-location IDs from `get_all_zone_source_locations()` are the join key
|
||||
between zone-name lookups and per-callsite queries.
|
||||
|
||||
## Translating catalog entries to ctx Python
|
||||
|
||||
The catalog (`tracy://catalog`) lists curated queries. Each maps to a small
|
||||
Python snippet:
|
||||
|
||||
```python
|
||||
# zone_list — top 10 hottest zones by total time
|
||||
top = sorted(ctx.get_all_zone_stats().items(),
|
||||
key=lambda kv: kv[1].total, reverse=True)[:10]
|
||||
for k, v in top:
|
||||
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
|
||||
|
||||
# frame_list — primary frame set timing
|
||||
times = ctx.get_frame_times() # ns per frame
|
||||
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
|
||||
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
|
||||
|
||||
# zone_stats for a named zone — find the srcloc id, then drill in
|
||||
import re
|
||||
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
|
||||
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))
|
||||
stats = ctx.get_zone_stats(sid)
|
||||
```
|
||||
|
||||
## Async mode
|
||||
|
||||
For long-running queries pass `async_mode=True` to `eval`; it returns
|
||||
`{task_id, status: "running"}`. Poll with the `task` tool
|
||||
(`action="poll", task_id=...`).
|
||||
17
extra/mcp/start_mcp.sh
Normal file
17
extra/mcp/start_mcp.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Start the Tracy MCP server.
|
||||
#
|
||||
# Set PYTHONPATH to the directory containing TracyServerBindings.so/.pyd.
|
||||
# Adjust the Release/Debug suffix to match your CMake build configuration.
|
||||
PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$(dirname "$0")/../../build/python/Release"
|
||||
export PYTHONPATH
|
||||
|
||||
# Machine-local overrides (not committed). Create start_mcp.local.sh next to
|
||||
# this file to set TRACY_CAPTURES_DIR, TRACY_MCP_PORT, or any other env var:
|
||||
# export TRACY_CAPTURES_DIR=/path/to/captures
|
||||
# export TRACY_MCP_PORT=47380
|
||||
if [ -f "$(dirname "$0")/start_mcp.local.sh" ]; then
|
||||
. "$(dirname "$0")/start_mcp.local.sh"
|
||||
fi
|
||||
|
||||
exec python3 "$(dirname "$0")/tracy_mcp.py" "$@"
|
||||
700
extra/mcp/tracy_mcp.py
Normal file
700
extra/mcp/tracy_mcp.py
Normal file
@@ -0,0 +1,700 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import atexit
|
||||
import builtins
|
||||
import concurrent.futures
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
import mcp.server.fastmcp as fastmcp
|
||||
|
||||
# Suppress noisy ASGI shutdown errors known to occur with SSE and Control-C.
|
||||
# These occur when Starlette attempts to send a 500 error after the loop is cancelled
|
||||
# but after the SSE 200 OK headers have already been sent. Global level suppression
|
||||
# is used because surgical filtering of ASGI exceptions is unreliable in this stack.
|
||||
logging.getLogger("uvicorn.error").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("starlette").setLevel(logging.CRITICAL)
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_PORT_FILE = os.path.join(_HERE, "tracy_mcp.port")
|
||||
_PID_FILE = os.path.join(_HERE, "tracy_mcp.pid")
|
||||
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
|
||||
|
||||
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
|
||||
# system prompt; exposing it as an MCP resource keeps analysis guidance in
|
||||
# sync across both surfaces with no plumbing. eval_guide.md covers
|
||||
# bindings-layer detail (ctx object model, units, source-location ID joins).
|
||||
_LLM_DIR = os.path.normpath(os.path.join(_HERE, "..", "..", "profiler", "src", "llm"))
|
||||
_PROMPT_PATH = os.path.join(_LLM_DIR, "system.prompt.md")
|
||||
_EVAL_GUIDE_PATH = os.path.join(_HERE, "eval_guide.md")
|
||||
|
||||
|
||||
def _read_text(path: str) -> str:
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
return f"(unavailable: {e})"
|
||||
|
||||
|
||||
# Tracy UDP broadcast packet support. Tracy clients announce themselves on
|
||||
# port 8086 with a BroadcastMessage (see public/common/TracyProtocol.hpp).
|
||||
# The dev GUI reads protocolVersion from the broadcast and refuses connection
|
||||
# on mismatch instead of hitting an opaque TCP timeout. We do the same.
|
||||
_PROTOCOL_HPP = os.path.normpath(
|
||||
os.path.join(_HERE, "..", "..", "public", "common", "TracyProtocol.hpp")
|
||||
)
|
||||
_BROADCAST_PORT = 8086
|
||||
_PROGRAM_NAME_SIZE = 64
|
||||
|
||||
|
||||
def _read_bindings_protocol_version() -> int | None:
|
||||
"""Parse ProtocolVersion from TracyProtocol.hpp at startup so our 'expected'
|
||||
version stays in sync with the bindings build without extra C++ wiring."""
|
||||
try:
|
||||
with open(_PROTOCOL_HPP, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
m = re.search(r"constexpr\s+uint32_t\s+ProtocolVersion\s*=\s*(\d+)", line)
|
||||
if m:
|
||||
return int(m.group(1))
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
_OUR_PROTOCOL_VERSION = _read_bindings_protocol_version()
|
||||
|
||||
|
||||
def _parse_broadcast(data: bytes) -> dict | None:
|
||||
"""Parse a Tracy BroadcastMessage. Handles broadcast versions 0-3.
|
||||
|
||||
Fixed-field sizes (from TracyProtocol.hpp, packed):
|
||||
v3: u16 bv, u16 lp, u32 pv, u64 pid, i32 at, char[<=64] name (>=20 + name)
|
||||
v2: u16 bv, u16 lp, u32 pv, i32 at, char[<=64] name (>=12 + name)
|
||||
v1: u32 bv, u32 pv, u32 lp, u32 at, char[<=64] name (>=16 + name)
|
||||
v0: u32 bv, u32 pv, u32 at, char[<=64] name (>=12 + name)
|
||||
|
||||
The programName field is variable-length on the wire — the sender writes
|
||||
only the actual name plus null terminator, not the full 64-byte buffer.
|
||||
"""
|
||||
if len(data) < 4:
|
||||
return None
|
||||
|
||||
def _name(buf: bytes) -> str:
|
||||
return buf[:_PROGRAM_NAME_SIZE].split(b"\0", 1)[0].decode("utf-8", "replace")
|
||||
|
||||
bv16 = struct.unpack_from("<H", data, 0)[0]
|
||||
if bv16 == 3 and len(data) >= 21:
|
||||
bv, lp, pv, pid, at = struct.unpack_from("<HHIQi", data, 0)
|
||||
return {"broadcast_version": bv, "listen_port": lp,
|
||||
"protocol_version": pv, "pid": pid,
|
||||
"active_seconds": at, "program": _name(data[20:])}
|
||||
if bv16 == 2 and len(data) >= 13:
|
||||
bv, lp, pv, at = struct.unpack_from("<HHIi", data, 0)
|
||||
return {"broadcast_version": bv, "listen_port": lp,
|
||||
"protocol_version": pv, "active_seconds": at,
|
||||
"program": _name(data[12:])}
|
||||
bv32 = struct.unpack_from("<I", data, 0)[0]
|
||||
if bv32 == 1 and len(data) >= 17:
|
||||
bv, pv, lp, at = struct.unpack_from("<IIII", data, 0)
|
||||
return {"broadcast_version": bv, "listen_port": lp,
|
||||
"protocol_version": pv, "active_seconds": at,
|
||||
"program": _name(data[16:])}
|
||||
if bv32 == 0 and len(data) >= 13:
|
||||
bv, pv, at = struct.unpack_from("<III", data, 0)
|
||||
return {"broadcast_version": bv, "listen_port": None,
|
||||
"protocol_version": pv, "active_seconds": at,
|
||||
"program": _name(data[12:])}
|
||||
return None
|
||||
|
||||
|
||||
async def _listen_broadcasts(timeout_s: float = 1.5) -> list[dict]:
|
||||
"""Listen briefly on UDP 8086 for Tracy client announcements.
|
||||
|
||||
Returns a list of parsed broadcasts (deduplicated by listen_port). Empty
|
||||
list means no broadcast received — the target may use TRACY_ON_DEMAND,
|
||||
a non-default broadcast port, or simply isn't running.
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
s.bind(("", _BROADCAST_PORT))
|
||||
except OSError:
|
||||
s.close()
|
||||
return []
|
||||
s.setblocking(False)
|
||||
loop = asyncio.get_running_loop()
|
||||
seen: dict[int | None, dict] = {}
|
||||
deadline = loop.time() + timeout_s
|
||||
try:
|
||||
while loop.time() < deadline:
|
||||
remaining = deadline - loop.time()
|
||||
if remaining <= 0:
|
||||
break
|
||||
try:
|
||||
fut = loop.sock_recvfrom(s, 2048)
|
||||
data, _addr = await asyncio.wait_for(fut, timeout=remaining)
|
||||
except (asyncio.TimeoutError, BlockingIOError):
|
||||
break
|
||||
parsed = _parse_broadcast(data)
|
||||
if parsed:
|
||||
seen.setdefault(parsed.get("listen_port"), parsed)
|
||||
finally:
|
||||
s.close()
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def _is_our_server_running() -> tuple[bool, int]:
|
||||
"""
|
||||
Check the PID file to see if our server is already running.
|
||||
Returns (running, port). Uses os.kill(pid, 0) to confirm the process is alive.
|
||||
"""
|
||||
try:
|
||||
with open(_PID_FILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
with open(_PORT_FILE) as f:
|
||||
port = int(f.read().strip())
|
||||
os.kill(pid, 0) # raises OSError if process is gone
|
||||
return True, port
|
||||
except Exception:
|
||||
return False, 0
|
||||
|
||||
|
||||
def _find_free_port() -> int:
|
||||
"""Scan from preferred port upward; fall back to OS-assigned if the range is exhausted."""
|
||||
for port in range(_PREFERRED_PORT, _PREFERRED_PORT + 16):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
s.bind(("127.0.0.1", port))
|
||||
s.close()
|
||||
return port
|
||||
except OSError:
|
||||
s.close()
|
||||
# Let OS assign any free port
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(("127.0.0.1", 0))
|
||||
port = s.getsockname()[1]
|
||||
s.close()
|
||||
return port
|
||||
|
||||
|
||||
def _write_pid_and_port(port: int) -> None:
|
||||
try:
|
||||
with open(_PID_FILE, "w") as f:
|
||||
f.write(str(os.getpid()))
|
||||
with open(_PORT_FILE, "w") as f:
|
||||
f.write(str(port))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _cleanup_pid_files() -> None:
|
||||
for path in (_PID_FILE, _PORT_FILE):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# Attempt to import Tracy Server bindings
|
||||
try:
|
||||
import TracyServerBindings as tracy_server
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "../../build/python"))
|
||||
try:
|
||||
import TracyServerBindings as tracy_server
|
||||
except ImportError:
|
||||
tracy_server = None
|
||||
|
||||
mcp_server = fastmcp.FastMCP("Tracy Profiler")
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
|
||||
|
||||
|
||||
class Task:
|
||||
def __init__(self, task_id: str, code: str):
|
||||
self.id = task_id
|
||||
self.code = code
|
||||
self.status = "pending"
|
||||
self.result = None
|
||||
self.error = None
|
||||
self.start_time = time.time()
|
||||
self.end_time = None
|
||||
|
||||
|
||||
class TracyInstance:
|
||||
def __init__(self, name: str, worker: object | None = None):
|
||||
self.name = name
|
||||
self.worker = worker
|
||||
self.path = None
|
||||
self.mtime = None
|
||||
|
||||
|
||||
instances: dict[str, TracyInstance] = {}
|
||||
tasks: dict[str, Task] = {}
|
||||
captures_dir: str | None = os.environ.get("TRACY_CAPTURES_DIR")
|
||||
|
||||
|
||||
@mcp_server.resource("tracy://prompt")
|
||||
def _prompt_resource() -> str:
|
||||
"""Tracy Assist's analysis guidance (system.prompt.md). Contains workflows
|
||||
for optimization, callstack inspection, and privacy rules. %TIME%, %USER%,
|
||||
and %PROGRAMNAME% are placeholders filled by the in-app chat — ignore them
|
||||
when reading from MCP."""
|
||||
return _read_text(_PROMPT_PATH)
|
||||
|
||||
|
||||
@mcp_server.resource("tracy://eval-guide")
|
||||
def _eval_guide_resource() -> str:
|
||||
"""Bindings-layer guide for the eval tool: ctx object model, time units,
|
||||
source-location ID semantics, and worked examples translating catalog
|
||||
entries into ctx Python."""
|
||||
return _read_text(_EVAL_GUIDE_PATH)
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def list_captures() -> list[str]:
|
||||
"""List .tracy capture files in the TRACY_CAPTURES_DIR directory (non-recursive)."""
|
||||
if not captures_dir:
|
||||
return []
|
||||
return sorted(glob.glob(os.path.join(captures_dir, "*.tracy")))
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def list_instances() -> list[dict]:
|
||||
"""List all loaded Tracy instances and captures with metadata."""
|
||||
return [
|
||||
{
|
||||
"id": name,
|
||||
"path": inst.path,
|
||||
"mtime": inst.mtime,
|
||||
"live": inst.path is None
|
||||
}
|
||||
for name, inst in instances.items()
|
||||
]
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def discover_instances(port_range: str = "8086-8095") -> list[dict]:
|
||||
"""
|
||||
Scan for running Tracy-instrumented applications on local ports.
|
||||
|
||||
Returns a list of discovered ports that are listening.
|
||||
"""
|
||||
start_port, end_port = map(int, port_range.split("-"))
|
||||
discovered = []
|
||||
|
||||
async def check_port(port: int) -> None:
|
||||
try:
|
||||
_, writer = await asyncio.wait_for(
|
||||
asyncio.open_connection("127.0.0.1", port), timeout=0.1
|
||||
)
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
discovered.append({"port": port, "address": "127.0.0.1"})
|
||||
except (OSError, asyncio.TimeoutError, ConnectionRefusedError):
|
||||
pass
|
||||
|
||||
await asyncio.gather(*(check_port(p) for p in range(start_port, end_port + 1)))
|
||||
return discovered
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str | None = None) -> str:
|
||||
"""
|
||||
Connect to a live running Tracy-instrumented application.
|
||||
|
||||
Wraps Worker(addr, port, memoryLimit=-1). Returns the instance_id.
|
||||
"""
|
||||
if not tracy_server:
|
||||
return "Error: Tracy Server bindings not found."
|
||||
|
||||
# Pre-flight: read Tracy's UDP broadcast on port 8086 and compare protocol
|
||||
# versions before attempting TCP. Mirrors what the Tracy GUI does so a
|
||||
# version mismatch produces a precise error instead of an opaque timeout.
|
||||
# Tracy clients broadcast every ~3s (TracyProfiler.cpp), so we listen a
|
||||
# little longer to guarantee catching at least one beat.
|
||||
broadcasts = await _listen_broadcasts(timeout_s=3.5)
|
||||
match = next((b for b in broadcasts if b.get("listen_port") == port), None)
|
||||
if match and _OUR_PROTOCOL_VERSION is not None:
|
||||
if match["protocol_version"] != _OUR_PROTOCOL_VERSION:
|
||||
return (
|
||||
f"Protocol mismatch: target program '{match['program']}' "
|
||||
f"announces Tracy protocol v{match['protocol_version']} on "
|
||||
f"{address}:{port}, but these server bindings are built "
|
||||
f"against v{_OUR_PROTOCOL_VERSION}. Rebuild the bindings or "
|
||||
f"the target against a matching Tracy version."
|
||||
)
|
||||
|
||||
try:
|
||||
w = tracy_server.Worker(address, port)
|
||||
except Exception as e:
|
||||
return f"Failed to connect: {str(e)}"
|
||||
|
||||
# Worker construction returns immediately even on protocol failure (the
|
||||
# bindings expose no error state — is_connected() is the only signal).
|
||||
# Probe briefly so silent failures (e.g. TRACY_ON_DEMAND with no profiler
|
||||
# request yet, or a target broadcasting on a non-default port) surface
|
||||
# cleanly even when broadcast pre-flight didn't catch them.
|
||||
deadline_s = 2.0
|
||||
step_s = 0.1
|
||||
elapsed = 0.0
|
||||
while elapsed < deadline_s and not w.is_connected():
|
||||
await asyncio.sleep(step_s)
|
||||
elapsed += step_s
|
||||
|
||||
if not w.is_connected():
|
||||
try:
|
||||
w.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
hint = ""
|
||||
if broadcasts and not match:
|
||||
seen = ", ".join(
|
||||
f"'{b['program']}' on port {b.get('listen_port')} (protocol v{b['protocol_version']})"
|
||||
for b in broadcasts
|
||||
)
|
||||
hint = f" Detected other Tracy broadcasts: {seen}."
|
||||
elif not broadcasts:
|
||||
hint = (
|
||||
" No Tracy broadcasts were received on port 8086 in 3.5s — "
|
||||
"the target may use TRACY_ON_DEMAND, a non-default broadcast "
|
||||
"port, or isn't running."
|
||||
)
|
||||
return (
|
||||
f"Reached {address}:{port} but the Tracy handshake did not complete "
|
||||
f"within {deadline_s:.1f}s.{hint} Common causes: (1) the Tracy "
|
||||
f"client version embedded in the target program differs from these "
|
||||
f"server bindings; (2) the target was built with TRACY_ON_DEMAND "
|
||||
f"and is awaiting a profiler request; (3) another client is "
|
||||
f"already attached."
|
||||
)
|
||||
|
||||
name = alias or f"live_{address}_{port}"
|
||||
instances[name] = TracyInstance(name, w)
|
||||
return (
|
||||
f"Connected to live instance as '{name}'. "
|
||||
f"Before your first eval, read resources tracy://prompt "
|
||||
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
|
||||
f"ns time units, srcloc IDs)."
|
||||
)
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
"""
|
||||
Load a .tracy capture file by absolute path.
|
||||
|
||||
Parameters:
|
||||
path — absolute path to a .tracy file. On Windows use backslashes
|
||||
(e.g. 'E:\\\\traces\\\\foo.tracy').
|
||||
alias — optional instance name; overwrites existing on collision.
|
||||
If omitted, an ID is derived from filename and mtime.
|
||||
|
||||
If you don't already have a path, call `list_captures` first — it lists
|
||||
.tracy files in the TRACY_CAPTURES_DIR environment directory.
|
||||
"""
|
||||
if not tracy_server:
|
||||
return "Error: Tracy Server bindings not found."
|
||||
try:
|
||||
mtime = os.path.getmtime(path)
|
||||
if alias:
|
||||
name = alias
|
||||
else:
|
||||
# unique name including mtime to avoid version collision
|
||||
name = f"{os.path.basename(path)}@{int(mtime):x}"
|
||||
|
||||
if name in instances:
|
||||
inst = instances[name]
|
||||
if inst.path == path and inst.mtime == mtime:
|
||||
return f"Instance '{name}' is already loaded and up to date."
|
||||
|
||||
f = tracy_server.open_file(path)
|
||||
w = tracy_server.create_worker_from_file(f)
|
||||
inst = TracyInstance(name, w)
|
||||
inst.path = path
|
||||
inst.mtime = mtime
|
||||
instances[name] = inst
|
||||
return (
|
||||
f"Loaded as '{name}'. "
|
||||
f"Before your first eval, read resources tracy://prompt "
|
||||
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
|
||||
f"ns time units, srcloc IDs)."
|
||||
)
|
||||
except Exception as e:
|
||||
return f"Failed to load: {str(e)}"
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def save_trace(
|
||||
instance_id: str,
|
||||
path: str,
|
||||
level: int = 3,
|
||||
streams: int = 4,
|
||||
fi_dict: bool = False,
|
||||
overwrite: bool = False,
|
||||
async_mode: bool = True,
|
||||
) -> object:
|
||||
"""
|
||||
Snapshot a Tracy instance (live or loaded) to a .tracy file.
|
||||
|
||||
Wraps `Worker::Write` under the main-thread data lock — safe for live
|
||||
instances; the receive thread yields cooperatively for the duration of
|
||||
the write. Concurrent `eval` calls against the same live instance may
|
||||
stall until the save completes.
|
||||
|
||||
Parameters:
|
||||
instance_id — name returned by live_connect / load_capture.
|
||||
path — output file. Absolute paths are used as-is; a bare
|
||||
filename is resolved under TRACY_CAPTURES_DIR if set.
|
||||
On Windows use backslashes (e.g. 'E:\\\\traces\\\\a.tracy').
|
||||
level — Zstd compression level (default 3, matches capture tool).
|
||||
streams — number of compression streams (default 4).
|
||||
fi_dict — rebuild frame-image dedup dictionary on save. Only
|
||||
meaningful for traces containing screenshots; default
|
||||
False matches the capture tool and GUI default.
|
||||
overwrite — refuse to clobber an existing file unless True.
|
||||
async_mode — default True; large traces take seconds-to-minutes.
|
||||
Returns {task_id, status: "running"}; poll with `task`.
|
||||
|
||||
On success returns a dict with path, uncompressed_bytes, compressed_bytes,
|
||||
ratio, and elapsed_ms — the same numbers the capture tool prints.
|
||||
"""
|
||||
if instance_id not in instances:
|
||||
return f"Error: Instance '{instance_id}' not found. Use list_instances to find valid IDs."
|
||||
instance = instances[instance_id]
|
||||
if not instance.worker:
|
||||
return f"Error: Instance '{instance_id}' has no worker."
|
||||
|
||||
if not os.path.isabs(path):
|
||||
if captures_dir and os.path.basename(path) == path:
|
||||
path = os.path.join(captures_dir, path)
|
||||
else:
|
||||
return (
|
||||
f"Error: '{path}' is not absolute. Pass a full path, or a bare "
|
||||
f"filename with TRACY_CAPTURES_DIR set (currently "
|
||||
f"{captures_dir!r})."
|
||||
)
|
||||
if not path.endswith(".tracy"):
|
||||
path += ".tracy"
|
||||
if os.path.exists(path) and not overwrite:
|
||||
return (
|
||||
f"Error: '{path}' already exists. Pass overwrite=True to clobber, "
|
||||
f"or choose a different path."
|
||||
)
|
||||
|
||||
if not async_mode:
|
||||
return await _execute_save(instance.worker, path, level, streams, fi_dict)
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
t = Task(task_id, f"save_trace({instance_id} -> {path})")
|
||||
tasks[task_id] = t
|
||||
asyncio.get_running_loop().run_in_executor(
|
||||
executor, _run_save_task_sync, t, instance.worker, path, level, streams, fi_dict
|
||||
)
|
||||
return {"task_id": task_id, "status": "running"}
|
||||
|
||||
|
||||
def _save_worker_sync(worker: object, path: str, level: int, streams: int, fi_dict: bool) -> dict:
|
||||
t0 = time.time()
|
||||
uncompressed, compressed = tracy_server.save_worker(
|
||||
worker, path, level, streams, fi_dict
|
||||
)
|
||||
elapsed_ms = int((time.time() - t0) * 1000)
|
||||
ratio = (compressed / uncompressed) if uncompressed else 0.0
|
||||
return {
|
||||
"path": path,
|
||||
"uncompressed_bytes": uncompressed,
|
||||
"compressed_bytes": compressed,
|
||||
"ratio": ratio,
|
||||
"elapsed_ms": elapsed_ms,
|
||||
}
|
||||
|
||||
|
||||
def _run_save_task_sync(t: Task, worker: object, path: str, level: int, streams: int, fi_dict: bool) -> None:
|
||||
t.status = "running"
|
||||
try:
|
||||
t.result = _save_worker_sync(worker, path, level, streams, fi_dict)
|
||||
t.status = "completed"
|
||||
except Exception as e:
|
||||
t.error = str(e)
|
||||
t.status = "failed"
|
||||
finally:
|
||||
t.end_time = time.time()
|
||||
|
||||
|
||||
async def _execute_save(worker: object, path: str, level: int, streams: int, fi_dict: bool) -> dict:
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
executor, _save_worker_sync, worker, path, level, streams, fi_dict
|
||||
)
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def unload_capture(instance_id: str) -> str:
|
||||
"""Unload a Tracy instance and release its memory."""
|
||||
if instance_id in instances:
|
||||
del instances[instance_id]
|
||||
return f"Instance '{instance_id}' unloaded."
|
||||
return f"Instance '{instance_id}' not found."
|
||||
|
||||
|
||||
@mcp_server.tool(name="eval")
|
||||
async def tracy_eval(code: str, instance_id: str, async_mode: bool = False) -> object:
|
||||
"""
|
||||
Execute Python code against a specific Tracy Worker bound as `ctx`.
|
||||
|
||||
On first use, read the `tracy://prompt` (analysis guidance) and
|
||||
`tracy://eval-guide` (ctx object model, units, source-location ID joins)
|
||||
resources. Time values returned by Worker methods are nanoseconds.
|
||||
|
||||
If async_mode=True, returns a task_id immediately; poll via the `task` tool.
|
||||
"""
|
||||
if instance_id not in instances:
|
||||
return f"Error: Instance '{instance_id}' not found. Use list_instances to find valid IDs."
|
||||
|
||||
instance = instances[instance_id]
|
||||
if not instance.worker:
|
||||
return f"Error: Instance '{instance_id}' has no worker."
|
||||
|
||||
if not async_mode:
|
||||
return await _execute_eval(code, instance.worker)
|
||||
|
||||
# Async mode: spawn task and return immediately
|
||||
task_id = str(uuid.uuid4())
|
||||
task = Task(task_id, code)
|
||||
tasks[task_id] = task
|
||||
asyncio.get_running_loop().run_in_executor(
|
||||
executor, _run_task_sync, task, instance.worker
|
||||
)
|
||||
return {"task_id": task_id, "status": "running"}
|
||||
|
||||
|
||||
def _run_task_sync(task: Task, worker: object) -> None:
|
||||
"""Run a background eval task in the thread pool."""
|
||||
task.status = "running"
|
||||
try:
|
||||
task.result = _execute_eval_sync(task.code, worker)
|
||||
task.status = "completed"
|
||||
except Exception as e:
|
||||
task.error = str(e)
|
||||
task.status = "failed"
|
||||
finally:
|
||||
task.end_time = time.time()
|
||||
|
||||
|
||||
def _execute_eval_sync(code: str, ctx: object) -> str:
|
||||
"""Execute *code* with `ctx` bound to the Tracy worker. Captures stdout."""
|
||||
global_vars = {
|
||||
"__builtins__": builtins,
|
||||
"ctx": ctx,
|
||||
"tracy": tracy_server,
|
||||
"instances": {name: inst.worker for name, inst in instances.items()},
|
||||
}
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
try:
|
||||
result = eval(compile(code, "<eval>", "eval"), global_vars)
|
||||
except SyntaxError:
|
||||
exec(compile(code, "<exec>", "exec"), global_vars)
|
||||
result = None
|
||||
output = buf.getvalue()
|
||||
if result is None:
|
||||
return output or ""
|
||||
return str(result)
|
||||
|
||||
|
||||
async def _execute_eval(code: str, ctx: object) -> str:
|
||||
"""Async wrapper: runs `_execute_eval_sync` in the thread-pool executor."""
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
executor, _execute_eval_sync, code, ctx
|
||||
)
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def task(action: str, task_id: str | None = None) -> object:
|
||||
"""
|
||||
Manage background analysis tasks.
|
||||
|
||||
Actions: poll, cancel, list
|
||||
"""
|
||||
if action == "list":
|
||||
return [
|
||||
{"id": t.id, "status": t.status, "elapsed": time.time() - t.start_time}
|
||||
for t in tasks.values()
|
||||
]
|
||||
|
||||
if not task_id or task_id not in tasks:
|
||||
return "Error: Task ID not found."
|
||||
|
||||
t = tasks[task_id]
|
||||
if action == "poll":
|
||||
res: dict = {"id": t.id, "status": t.status}
|
||||
if t.status == "completed":
|
||||
res["result"] = t.result
|
||||
elif t.status == "failed":
|
||||
res["error"] = t.error
|
||||
return res
|
||||
|
||||
if action == "cancel":
|
||||
# Cancellation of thread-pool work is not possible post-submission;
|
||||
# mark the task so callers know it was abandoned.
|
||||
if t.status == "running":
|
||||
t.status = "cancelled"
|
||||
return f"Task {task_id} marked as cancelled."
|
||||
return f"Task {task_id} is not running."
|
||||
|
||||
return "Error: Unknown action."
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def shutdown_server() -> str:
|
||||
"""
|
||||
Shut down the Tracy MCP server.
|
||||
|
||||
Because the server runs as a singleton (SSE transport, one process shared
|
||||
across all VS Code windows), this releases the TracyServerBindings.pyd lock
|
||||
for all clients at once. Restart tracy_mcp.py after rebuilding.
|
||||
"""
|
||||
import threading
|
||||
def _exit() -> None:
|
||||
time.sleep(0.2)
|
||||
os._exit(0)
|
||||
threading.Thread(target=_exit, daemon=True).start()
|
||||
return "Server shutting down. Restart tracy_mcp.py to reconnect."
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
atexit.register(_cleanup_pid_files)
|
||||
|
||||
running, existing_port = _is_our_server_running()
|
||||
if running:
|
||||
print(
|
||||
f"Tracy MCP already running on port {existing_port}. "
|
||||
"All VS Code windows share that instance.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
port = _find_free_port()
|
||||
_write_pid_and_port(port)
|
||||
|
||||
print(f"Tracy MCP listening on http://127.0.0.1:{port}/sse", file=sys.stderr)
|
||||
|
||||
mcp_server.settings.host = "127.0.0.1"
|
||||
mcp_server.settings.port = port
|
||||
try:
|
||||
mcp_server.run(transport="sse")
|
||||
except KeyboardInterrupt:
|
||||
print("\nTracy MCP server stopped.", file=sys.stderr)
|
||||
sys.exit(0)
|
||||
@@ -1,7 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
|
||||
option(NO_STATISTICS "Disable calculation of statistics" ON)
|
||||
set(NO_STATISTICS ON)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
@@ -16,15 +15,18 @@ project(
|
||||
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-chrome
|
||||
src/import-chrome.cpp
|
||||
)
|
||||
add_git_ref(tracy-import-chrome)
|
||||
target_link_libraries(tracy-import-chrome PRIVATE TracyServer nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(tracy-import-fuchsia
|
||||
src/import-fuchsia.cpp
|
||||
)
|
||||
add_git_ref(tracy-import-fuchsia)
|
||||
target_link_libraries(tracy-import-fuchsia PRIVATE TracyServer)
|
||||
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
#include "../../server/TracyFileWrite.hpp"
|
||||
#include "../../server/TracyMmap.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void Usage()
|
||||
{
|
||||
printf( "tracy-import-chrome %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: import-chrome input.json output.tracy\n\n" );
|
||||
printf( "The following chrome-tracing phases are supported:\n\n" );
|
||||
printf( " b/B/e/E - Timeline events such as ZoneNamed\n" );
|
||||
@@ -89,7 +92,7 @@ int main( int argc, char** argv )
|
||||
auto zctx = ZSTD_createDStream();
|
||||
ZSTD_initDStream( zctx );
|
||||
|
||||
enum { tmpSize = 64*1024 };
|
||||
constexpr size_t tmpSize = 64*1024;
|
||||
auto tmp = new char[tmpSize];
|
||||
|
||||
ZSTD_inBuffer_s zin = { zbuf, (size_t)zsz };
|
||||
|
||||
@@ -30,8 +30,11 @@
|
||||
#include "../../server/TracyFileWrite.hpp"
|
||||
#include "../../server/TracyMmap.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
void Usage() {
|
||||
printf("tracy-import-fuchsia %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef);
|
||||
printf("Usage: import-fuchsia input.json output.tracy\n\n");
|
||||
printf("See: "
|
||||
"https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format\n\n");
|
||||
@@ -133,7 +136,7 @@ std::vector<uint8_t> read_input(const char *input) {
|
||||
auto zctx = ZSTD_createDStream();
|
||||
ZSTD_initDStream(zctx);
|
||||
|
||||
enum { tmpSize = 64 * 1024 };
|
||||
constexpr size_t tmpSize = 64 * 1024;
|
||||
auto tmp = new char[tmpSize];
|
||||
|
||||
ZSTD_inBuffer_s zin = {zbuf, (size_t)zsz};
|
||||
|
||||
1
manual/README
Normal file
1
manual/README
Normal file
@@ -0,0 +1 @@
|
||||
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.
|
||||
35
manual/bclogo2quote.awk
Normal file
35
manual/bclogo2quote.awk
Normal file
@@ -0,0 +1,35 @@
|
||||
/\\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 }
|
||||
64
manual/fa-icons.py
Normal file
64
manual/fa-icons.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/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()
|
||||
@@ -3,3 +3,151 @@ function Link(el)
|
||||
el.attributes['reference'] = nil
|
||||
return el
|
||||
end
|
||||
|
||||
-- Drop Div wrappers (e.g. table/titlepage containers), keeping their content.
|
||||
function Div(el)
|
||||
return el.content
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- LaTeX math -> plain-text approximation.
|
||||
--
|
||||
-- The target Markdown renderer has no math support, so a raw "$\frac{1}{2}$"
|
||||
-- would show verbatim. We turn each math node into the closest Unicode/ASCII
|
||||
-- equivalent: fractions become "a/b", \times becomes "x", super/subscripts use
|
||||
-- Unicode digits, and the one multi-line display equation becomes a fenced
|
||||
-- code block (Markdown collapses plain newlines, a code block keeps them).
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local sup = {['0']='⁰',['1']='¹',['2']='²',['3']='³',['4']='⁴',['5']='⁵',
|
||||
['6']='⁶',['7']='⁷',['8']='⁸',['9']='⁹',['+']='⁺',['-']='⁻',
|
||||
['=']='⁼',['(']='⁽',[')']='⁾'}
|
||||
local sub = {['0']='₀',['1']='₁',['2']='₂',['3']='₃',['4']='₄',['5']='₅',
|
||||
['6']='₆',['7']='₇',['8']='₈',['9']='₉',['+']='₊',['-']='₋',
|
||||
['=']='₌',['(']='₍',[')']='₎'}
|
||||
|
||||
-- Symbol replacements, applied as literal substitutions. Longer commands must
|
||||
-- precede those that are a prefix of them (e.g. \rightarrow before \right).
|
||||
local symbols = {
|
||||
{'\\leftrightarrow','↔'}, {'\\rightarrow','→'}, {'\\leftarrow','←'},
|
||||
{'\\Rightarrow','⇒'}, {'\\Leftarrow','⇐'}, {'\\to','→'}, {'\\mapsto','↦'},
|
||||
{'\\times','×'}, {'\\cdot','·'}, {'\\div','÷'}, {'\\ast','*'}, {'\\star','*'},
|
||||
{'\\leq','≤'}, {'\\geq','≥'}, {'\\neq','≠'}, {'\\approx','≈'}, {'\\equiv','≡'},
|
||||
{'\\ll','«'}, {'\\gg','»'}, {'\\le','≤'}, {'\\ge','≥'},
|
||||
{'\\ldots','…'}, {'\\cdots','…'}, {'\\dots','…'}, {'\\infty','∞'},
|
||||
{'\\pm','±'}, {'\\mp','∓'}, {'\\propto','∝'}, {'\\sum','Σ'}, {'\\prod','Π'},
|
||||
{'\\alpha','α'}, {'\\beta','β'}, {'\\gamma','γ'}, {'\\delta','δ'}, {'\\Delta','Δ'},
|
||||
{'\\mu','µ'}, {'\\sigma','σ'}, {'\\pi','π'}, {'\\lambda','λ'}, {'\\theta','θ'},
|
||||
{'\\left',''}, {'\\right',''},
|
||||
{'\\qquad',' '}, {'\\quad',' '}, {'\\,',' '}, {'\\;',' '}, {'\\:',' '},
|
||||
{'\\ ',' '}, {'\\!',''},
|
||||
{'\\%','%'}, {'\\#','#'}, {'\\&','&'}, {'\\_','_'}, {'\\{','{'}, {'\\}','}'},
|
||||
{'\\$','$'},
|
||||
}
|
||||
|
||||
-- Literal (non-pattern) string replacement; avoids Lua pattern magic in keys.
|
||||
local function lit_replace(s, a, b)
|
||||
local out, i = {}, 1
|
||||
while true do
|
||||
local p = s:find(a, i, true)
|
||||
if not p then out[#out + 1] = s:sub(i); break end
|
||||
out[#out + 1] = s:sub(i, p - 1)
|
||||
out[#out + 1] = b
|
||||
i = p + #a
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
-- Strip the outer braces of a "%b{}" capture.
|
||||
local function grp(b) return b:sub(2, #b - 1) end
|
||||
|
||||
-- Map a string to Unicode super/subscript, or nil if any char is unsupported.
|
||||
local function map_script(txt, map)
|
||||
local res = {}
|
||||
for i = 1, #txt do
|
||||
local c = txt:sub(i, i)
|
||||
if not map[c] then return nil end
|
||||
res[#res + 1] = map[c]
|
||||
end
|
||||
return table.concat(res)
|
||||
end
|
||||
|
||||
local function convert(s)
|
||||
-- Text/font wrappers: keep the content, recurse to handle nesting.
|
||||
for _, cmd in ipairs({'text', 'mathrm', 'mathit', 'mathbf', 'mathbb',
|
||||
'mathsf', 'mathtt', 'mathcal', 'operatorname',
|
||||
'textbf', 'textit', 'textrm'}) do
|
||||
s = s:gsub('\\' .. cmd .. '(%b{})', function(b) return convert(grp(b)) end)
|
||||
end
|
||||
-- Fractions -> "num/den" (spaced when either side has spaces).
|
||||
local function frac(a, b)
|
||||
local n, d = convert(grp(a)), convert(grp(b))
|
||||
local sep = (n:find(' ', 1, true) or d:find(' ', 1, true)) and ' / ' or '/'
|
||||
return n .. sep .. d
|
||||
end
|
||||
s = s:gsub('\\frac(%b{})(%b{})', frac)
|
||||
s = s:gsub('\\dfrac(%b{})(%b{})', frac)
|
||||
s = s:gsub('\\tfrac(%b{})(%b{})', frac)
|
||||
s = s:gsub('\\sfrac(%b{})(%b{})', frac)
|
||||
-- Roots.
|
||||
s = s:gsub('\\sqrt(%b{})', function(b) return '√(' .. convert(grp(b)) .. ')' end)
|
||||
-- Single-char scripts first, so the braced fallback (e.g. "_native") below
|
||||
-- is not re-scanned and mangled into Unicode subscripts.
|
||||
s = s:gsub('%^([%w])', function(c) return sup[c] or ('^' .. c) end)
|
||||
s = s:gsub('_([%w])', function(c) return sub[c] or ('_' .. c) end)
|
||||
-- Braced scripts: Unicode when the content is all digits/signs, else keep
|
||||
-- a readable "^(...)" / "_..." form.
|
||||
s = s:gsub('%^(%b{})', function(b)
|
||||
local inner = convert(grp(b))
|
||||
return map_script(inner, sup) or ('^(' .. inner .. ')')
|
||||
end)
|
||||
s = s:gsub('_(%b{})', function(b)
|
||||
local inner = convert(grp(b))
|
||||
return map_script(inner, sub) or ('_' .. inner)
|
||||
end)
|
||||
-- Remaining symbols.
|
||||
for _, pair in ipairs(symbols) do s = lit_replace(s, pair[1], pair[2]) end
|
||||
return s
|
||||
end
|
||||
|
||||
-- Convert a display equation, preserving its line structure for a code block.
|
||||
local function convert_display(s)
|
||||
s = convert(s)
|
||||
for _, env in ipairs({'cases', 'aligned', 'align', 'array', 'matrix',
|
||||
'gathered', 'split'}) do
|
||||
s = lit_replace(s, '\\begin{' .. env .. '}', '')
|
||||
s = lit_replace(s, '\\end{' .. env .. '}', '')
|
||||
end
|
||||
s = lit_replace(s, '\\\\', '\n') -- row break
|
||||
s = s:gsub('%s*&%s*', ' ') -- column separator -> spacing
|
||||
local lines = {}
|
||||
for line in (s .. '\n'):gmatch('(.-)\n') do
|
||||
line = line:gsub('^%s+', ''):gsub('%s+$', '')
|
||||
if line ~= '' then lines[#lines + 1] = line end
|
||||
end
|
||||
for i = 2, #lines do lines[i] = ' ' .. lines[i] end -- indent continuations
|
||||
return table.concat(lines, '\n')
|
||||
end
|
||||
|
||||
function Math(el)
|
||||
if el.mathtype == 'DisplayMath' then
|
||||
return el -- handled at block level by Para, to emit a code block
|
||||
end
|
||||
return pandoc.Str(convert(el.text))
|
||||
end
|
||||
|
||||
-- A paragraph that is solely a display equation becomes a fenced code block.
|
||||
function Para(el)
|
||||
local maths, only_math = {}, true
|
||||
for _, x in ipairs(el.content) do
|
||||
if x.t == 'Math' and x.mathtype == 'DisplayMath' then
|
||||
maths[#maths + 1] = x
|
||||
elseif x.t ~= 'Space' and x.t ~= 'SoftBreak' and x.t ~= 'LineBreak' then
|
||||
only_math = false
|
||||
end
|
||||
end
|
||||
if #maths == 0 or not only_math then return nil end
|
||||
local parts = {}
|
||||
for _, m in ipairs(maths) do parts[#parts + 1] = convert_display(m.text) end
|
||||
return pandoc.CodeBlock(table.concat(parts, '\n\n'))
|
||||
end
|
||||
|
||||
77
manual/icon-explain.py
Normal file
77
manual/icon-explain.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/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()
|
||||
@@ -7,20 +7,45 @@ 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
|
||||
sed -i -e 's@\\fa\([a-zA-Z]*\)@(\1~icon)@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
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -s _tmp.tex -o tracy.md
|
||||
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
|
||||
|
||||
16
manual/tablecaption.awk
Normal file
16
manual/tablecaption.awk
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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 }
|
||||
2927
manual/tracy.md
2927
manual/tracy.md
File diff suppressed because it is too large
Load Diff
853
manual/tracy.tex
853
manual/tracy.tex
File diff suppressed because it is too large
Load Diff
25
merge/CMakeLists.txt
Normal file
25
merge/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(NO_STATISTICS OFF) # we need those to get processed source zone locations
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
project(
|
||||
tracy-merge
|
||||
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(${PROJECT_NAME} src/merge.cpp)
|
||||
add_git_ref(${PROJECT_NAME})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
394
merge/src/merge.cpp
Normal file
394
merge/src/merge.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#include "TracyFileRead.hpp"
|
||||
#include "TracyFileWrite.hpp"
|
||||
#include "TracyPrint.hpp"
|
||||
#include "TracyWorker.hpp"
|
||||
#include "../../getopt/getopt.h"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
template<typename T1, typename T2>
|
||||
struct PairHash
|
||||
{
|
||||
size_t operator()( std::pair<T1, T2> const& p ) const
|
||||
{
|
||||
auto h1 = std::hash<T1>{}( p.first );
|
||||
auto h2 = std::hash<T2>{}( p.second );
|
||||
return h1 ^ (h2 << 1);
|
||||
}
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
struct ExportedTrace
|
||||
{
|
||||
uint64_t pid = 0;
|
||||
std::string process;
|
||||
std::string name;
|
||||
std::vector<tracy::Worker::ImportEventTimeline> timeline;
|
||||
std::vector<tracy::Worker::ImportEventMessages> messages;
|
||||
std::vector<tracy::Worker::ImportEventPlots> plots;
|
||||
std::unordered_map<uint64_t, std::string> threadNames;
|
||||
|
||||
static bool orderTimeline( tracy::Worker::ImportEventTimeline const& a, tracy::Worker::ImportEventTimeline const& b )
|
||||
{
|
||||
return a.timestamp < b.timestamp;
|
||||
}
|
||||
|
||||
static bool orderMessages( tracy::Worker::ImportEventMessages const& a, tracy::Worker::ImportEventMessages const& b )
|
||||
{
|
||||
return a.timestamp < b.timestamp;
|
||||
}
|
||||
|
||||
static std::optional<ExportedTrace> fromFile( std::string const& filepath, size_t fileIndex )
|
||||
{
|
||||
auto sourceFile = std::unique_ptr<tracy::FileRead>( tracy::FileRead::Open( filepath.c_str() ) );
|
||||
if( !sourceFile )
|
||||
{
|
||||
std::cerr << "Could not open file: " << filepath << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::cout << "Reading: " << filepath << std::endl;
|
||||
|
||||
tracy::Worker worker( *sourceFile, tracy::EventType::All, true, false );
|
||||
while( !worker.AreSourceLocationZonesReady() )
|
||||
{
|
||||
std::this_thread::sleep_for( 1s );
|
||||
}
|
||||
|
||||
ExportedTrace trace;
|
||||
trace.pid = worker.GetPid();
|
||||
if( trace.pid == 0 )
|
||||
{
|
||||
trace.pid = 0xFFFE0000 | fileIndex;
|
||||
}
|
||||
trace.process = worker.GetCaptureProgram();
|
||||
if( trace.process.empty() )
|
||||
{
|
||||
trace.process = "unknown";
|
||||
}
|
||||
trace.name = worker.GetCaptureName();
|
||||
|
||||
std::cout << " PID: " << trace.pid << ", Process: " << trace.process << std::endl;
|
||||
|
||||
std::unordered_set<uint64_t> seenThreads;
|
||||
|
||||
auto& sourceLocationZones = worker.GetSourceLocationZones();
|
||||
std::cout << " Zones: " << sourceLocationZones.size() << std::endl;
|
||||
for( auto& zone_it : sourceLocationZones )
|
||||
{
|
||||
const tracy::SourceLocation& srcLoc = worker.GetSourceLocation( zone_it.first );
|
||||
std::string zoneFile = worker.GetString( srcLoc.file );
|
||||
int zoneLine = srcLoc.line;
|
||||
std::string zoneName = worker.GetZoneName( srcLoc );
|
||||
|
||||
for( auto& zoneData : zone_it.second.zones )
|
||||
{
|
||||
const auto zone = zoneData.Zone();
|
||||
const uint64_t threadId = worker.DecompressThread( zoneData.Thread() );
|
||||
seenThreads.insert( threadId );
|
||||
|
||||
auto& startEvent = trace.timeline.emplace_back();
|
||||
startEvent.locFile = zoneFile;
|
||||
startEvent.locLine = zoneLine;
|
||||
startEvent.name = zoneName;
|
||||
startEvent.tid = threadId;
|
||||
startEvent.isEnd = false;
|
||||
startEvent.timestamp = zone->Start();
|
||||
|
||||
auto& endEvent = trace.timeline.emplace_back();
|
||||
endEvent.locFile = zoneFile;
|
||||
endEvent.locLine = zoneLine;
|
||||
endEvent.name = zoneName;
|
||||
endEvent.tid = threadId;
|
||||
endEvent.isEnd = true;
|
||||
endEvent.timestamp = zone->End();
|
||||
}
|
||||
}
|
||||
std::sort( trace.timeline.begin(), trace.timeline.end(), orderTimeline );
|
||||
|
||||
auto& messages = worker.GetMessages();
|
||||
std::cout << " Messages: " << messages.size() << std::endl;
|
||||
for( auto& msg : messages )
|
||||
{
|
||||
auto& importMsg = trace.messages.emplace_back();
|
||||
importMsg.tid = worker.DecompressThread( msg->thread );
|
||||
importMsg.message = worker.GetString( msg->ref );
|
||||
importMsg.timestamp = msg->time;
|
||||
seenThreads.insert( importMsg.tid );
|
||||
}
|
||||
std::sort( trace.messages.begin(), trace.messages.end(), orderMessages );
|
||||
|
||||
auto& plots = worker.GetPlots();
|
||||
std::cout << " Plots: " << plots.size() << std::endl;
|
||||
for( auto& plot : plots )
|
||||
{
|
||||
auto& importPlot = trace.plots.emplace_back();
|
||||
importPlot.name = worker.GetString( plot->name );
|
||||
importPlot.format = plot->format;
|
||||
importPlot.data.reserve( plot->data.size() );
|
||||
for( auto& pt : plot->data )
|
||||
{
|
||||
importPlot.data.emplace_back( pt.time.Val(), pt.val );
|
||||
}
|
||||
}
|
||||
|
||||
for( uint64_t tid : seenThreads )
|
||||
{
|
||||
std::string name = worker.GetThreadName( tid );
|
||||
trace.threadNames[tid] = name.empty() ? std::to_string( tid ) : name;
|
||||
}
|
||||
|
||||
return trace;
|
||||
}
|
||||
};
|
||||
|
||||
struct MergedTrace
|
||||
{
|
||||
std::vector<tracy::Worker::ImportEventTimeline> timeline;
|
||||
std::vector<tracy::Worker::ImportEventMessages> messages;
|
||||
std::vector<tracy::Worker::ImportEventPlots> plots;
|
||||
std::unordered_map<uint64_t, std::string> threadNames;
|
||||
std::string name;
|
||||
std::string process;
|
||||
|
||||
static MergedTrace merge( std::vector<ExportedTrace> const& traces )
|
||||
{
|
||||
MergedTrace out;
|
||||
|
||||
if( traces.empty() ) return out;
|
||||
|
||||
out.name = traces[0].name;
|
||||
out.process = traces[0].process + " (merged)";
|
||||
|
||||
std::unordered_map<std::pair<std::string, std::string>, size_t, PairHash<std::string, std::string>> nameCounts;
|
||||
for( auto const& trace : traces )
|
||||
{
|
||||
for( auto const& [tid, threadName] : trace.threadNames )
|
||||
{
|
||||
auto key = std::make_pair( trace.process, threadName );
|
||||
nameCounts[key]++;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::pair<std::string, std::string>, size_t, PairHash<std::string, std::string>> plotNameCounts;
|
||||
for( auto const& trace : traces )
|
||||
{
|
||||
for( auto const& plot : trace.plots )
|
||||
{
|
||||
auto key = std::make_pair( trace.process, plot.name );
|
||||
plotNameCounts[key]++;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::pair<uint64_t, uint64_t>, uint64_t, PairHash<uint64_t, uint64_t>> tidMapping;
|
||||
|
||||
size_t totalTimeline = 0, totalMessages = 0, totalPlots = 0;
|
||||
for( auto const& trace : traces )
|
||||
{
|
||||
totalTimeline += trace.timeline.size();
|
||||
totalMessages += trace.messages.size();
|
||||
totalPlots += trace.plots.size();
|
||||
}
|
||||
out.timeline.reserve( totalTimeline );
|
||||
out.messages.reserve( totalMessages );
|
||||
out.plots.reserve( totalPlots );
|
||||
|
||||
for( auto const& trace : traces )
|
||||
{
|
||||
for( auto const& [origTid, threadName] : trace.threadNames )
|
||||
{
|
||||
uint64_t encodedTid = (origTid & 0xFFFFFFFF) | (trace.pid << 32);
|
||||
|
||||
auto [it, inserted] = tidMapping.emplace( std::make_pair( trace.pid, origTid ), encodedTid );
|
||||
uint64_t finalTid = it->second;
|
||||
|
||||
auto key = std::make_pair( trace.process, threadName );
|
||||
std::string displayName;
|
||||
if( nameCounts[key] > 1 )
|
||||
{
|
||||
displayName = trace.process + "[" + std::to_string( trace.pid ) + "]/" + threadName;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayName = trace.process + "/" + threadName;
|
||||
}
|
||||
out.threadNames[finalTid] = displayName;
|
||||
}
|
||||
|
||||
for( auto const& event : trace.timeline )
|
||||
{
|
||||
auto& inserted = out.timeline.emplace_back( event );
|
||||
auto key = std::make_pair( trace.pid, event.tid );
|
||||
auto it = tidMapping.find( key );
|
||||
if( it != tidMapping.end() )
|
||||
{
|
||||
inserted.tid = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
for( auto const& msg : trace.messages )
|
||||
{
|
||||
auto& inserted = out.messages.emplace_back( msg );
|
||||
auto key = std::make_pair( trace.pid, msg.tid );
|
||||
auto it = tidMapping.find( key );
|
||||
if( it != tidMapping.end() )
|
||||
{
|
||||
inserted.tid = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
for( auto const& plot : trace.plots )
|
||||
{
|
||||
auto renamedPlot = plot;
|
||||
auto key = std::make_pair( trace.process, plot.name );
|
||||
if( plotNameCounts[key] > 1 )
|
||||
{
|
||||
renamedPlot.name = trace.process + "[" + std::to_string( trace.pid ) + "]/" + plot.name;
|
||||
}
|
||||
else
|
||||
{
|
||||
renamedPlot.name = trace.process + "/" + plot.name;
|
||||
}
|
||||
out.plots.push_back( renamedPlot );
|
||||
}
|
||||
}
|
||||
|
||||
std::sort( out.timeline.begin(), out.timeline.end(), ExportedTrace::orderTimeline );
|
||||
std::sort( out.messages.begin(), out.messages.end(), ExportedTrace::orderMessages );
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
[[noreturn]] void Usage()
|
||||
{
|
||||
printf( "tracy-merge %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: tracy-merge -o output.tracy input1.tracy [input2.tracy ...]\n\n" );
|
||||
printf( "Options:\n" );
|
||||
printf( " -o, --output <file> Output file path (required)\n" );
|
||||
printf( " -f, --force Overwrite output file if it exists\n" );
|
||||
printf( " -h, --help Show this help message\n" );
|
||||
printf( " -V, --version Show version information\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
std::string outputFile;
|
||||
std::vector<std::string> inputFiles;
|
||||
bool overwrite = false;
|
||||
|
||||
static struct option longOptions[] = {
|
||||
{ "output", required_argument, nullptr, 'o' },
|
||||
{ "force", no_argument, nullptr, 'f' },
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "version", no_argument, nullptr, 'V' },
|
||||
{ nullptr, 0, nullptr, 0 }
|
||||
};
|
||||
|
||||
int c;
|
||||
while( ( c = getopt_long( argc, argv, "o:fhV", longOptions, nullptr ) ) != -1 )
|
||||
{
|
||||
switch( c )
|
||||
{
|
||||
case 'o':
|
||||
outputFile = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
overwrite = true;
|
||||
break;
|
||||
case 'h':
|
||||
Usage();
|
||||
break;
|
||||
case 'V':
|
||||
printf( "tracy-merge %i.%i.%i / %s\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
exit( 0 );
|
||||
default:
|
||||
Usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( outputFile.empty() )
|
||||
{
|
||||
std::cerr << "Error: Output file is required (-o)" << std::endl << std::endl;
|
||||
Usage();
|
||||
}
|
||||
|
||||
while( optind < argc )
|
||||
{
|
||||
inputFiles.emplace_back( argv[optind++] );
|
||||
}
|
||||
|
||||
if( inputFiles.empty() )
|
||||
{
|
||||
std::cerr << "Error: At least one input file is required" << std::endl << std::endl;
|
||||
Usage();
|
||||
}
|
||||
|
||||
if( std::filesystem::exists( outputFile ) )
|
||||
{
|
||||
if( overwrite )
|
||||
{
|
||||
std::filesystem::remove( outputFile );
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: Output file already exists: " << outputFile << std::endl;
|
||||
std::cerr << "Use -f to overwrite" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExportedTrace> traces;
|
||||
traces.reserve( inputFiles.size() );
|
||||
|
||||
for( size_t i = 0; i < inputFiles.size(); i++ )
|
||||
{
|
||||
auto trace = ExportedTrace::fromFile( inputFiles[i], i );
|
||||
if( !trace )
|
||||
{
|
||||
std::cerr << "Failed to read: " << inputFiles[i] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
traces.push_back( std::move( *trace ) );
|
||||
}
|
||||
|
||||
std::cout << "\nMerging " << traces.size() << " trace(s)..." << std::endl;
|
||||
MergedTrace merged = MergedTrace::merge( traces );
|
||||
|
||||
std::cout << " Total zones: " << merged.timeline.size() << std::endl;
|
||||
std::cout << " Total messages: " << merged.messages.size() << std::endl;
|
||||
std::cout << " Total threads: " << merged.threadNames.size() << std::endl;
|
||||
|
||||
auto outFile = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( outputFile.c_str(), tracy::FileCompression::Zstd, 3, 4 ) );
|
||||
if( !outFile )
|
||||
{
|
||||
std::cerr << "Error: Could not open output file: " << outputFile << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Writing: " << outputFile << std::endl;
|
||||
tracy::Worker writer( merged.name.c_str(), merged.process.c_str(), merged.timeline, merged.messages, merged.plots, merged.threadNames );
|
||||
writer.Write( *outFile, false );
|
||||
outFile->Finish();
|
||||
|
||||
auto stats = outFile->GetCompressionStatistics();
|
||||
std::cout << "Done. Output size: " << tracy::MemSizeToString( stats.second ) << " (" << (100.0 * stats.second / stats.first) << "% ratio)" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
16
meson.build
16
meson.build
@@ -21,6 +21,10 @@ if get_option('callstack') > 0
|
||||
tracy_common_args += ['-DTRACY_CALLSTACK='+get_option('callstack').to_string()]
|
||||
endif
|
||||
|
||||
if get_option('platform_header') != ''
|
||||
tracy_common_args += ['-DTRACY_PLATFORM_HEADER="'+get_option('platform_header')+'"']
|
||||
endif
|
||||
|
||||
if get_option('no_callstack')
|
||||
tracy_common_args += ['-DTRACY_NO_CALLSTACK']
|
||||
endif
|
||||
@@ -93,6 +97,10 @@ if get_option('timer_fallback')
|
||||
tracy_common_args += ['-DTRACY_TIMER_FALLBACK']
|
||||
endif
|
||||
|
||||
if get_option('disallow_hw_timer')
|
||||
tracy_common_args += ['-DTRACY_DISALLOW_HW_TIMER']
|
||||
endif
|
||||
|
||||
if get_option('no_crash_handler')
|
||||
tracy_common_args += ['-DTRACY_NO_CRASH_HANDLER']
|
||||
endif
|
||||
@@ -114,6 +122,10 @@ if get_option('verbose')
|
||||
tracy_common_args += ['-DTRACY_VERBOSE']
|
||||
endif
|
||||
|
||||
if get_option('no_internal_message')
|
||||
tracy_common_args += ['-DTRACY_NO_INTERNAL_MESSAGE']
|
||||
endif
|
||||
|
||||
if get_option('debuginfod')
|
||||
tracy_common_args += ['-DTRACY_DEBUGINFOD']
|
||||
tracy_public_deps += dependency('libdebuginfod')
|
||||
@@ -130,7 +142,7 @@ if tracy_shared_libs
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
tracy_compile_args += ['-DWINVER=0x0601', '-D_WIN32_WINNT=0x0601']
|
||||
tracy_compile_args += ['-DWINVER=0x0A00', '-D_WIN32_WINNT=0x0A00']
|
||||
endif
|
||||
|
||||
includes = [
|
||||
@@ -157,6 +169,7 @@ client_includes = [
|
||||
'public/client/TracyFastVector.hpp',
|
||||
'public/client/TracyKCore.hpp',
|
||||
'public/client/TracyLock.hpp',
|
||||
'public/client/TracyMangle.hpp',
|
||||
'public/client/TracyProfiler.hpp',
|
||||
'public/client/TracyRingBuffer.hpp',
|
||||
'public/client/TracyScoped.hpp',
|
||||
@@ -181,6 +194,7 @@ common_includes = [
|
||||
'public/common/TracySocket.hpp',
|
||||
'public/common/TracyStackFrames.hpp',
|
||||
'public/common/TracySystem.hpp',
|
||||
'public/common/TracyTaggedUserlandAddress.hpp',
|
||||
'public/common/TracyWinFamily.hpp',
|
||||
'public/common/TracyYield.hpp'
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
option('tracy_enable', type : 'boolean', value : true, description : 'Enable profiling', yield: true)
|
||||
option('tracy_enable', type : 'boolean', value : false, description : 'Enable profiling', yield: true)
|
||||
option('on_demand', type : 'boolean', value : false, description : 'On-demand profiling')
|
||||
option('callstack', type : 'integer', value : 0, description : 'Enforce callstack collection for tracy zones x frames deep')
|
||||
option('platform_header', type : 'string', value : '', description : 'Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform')
|
||||
option('no_callstack', type : 'boolean', value : false, description : 'Disable all callstack related functionality')
|
||||
option('no_callstack_inlines', type : 'boolean', value : false, description : 'Disables the inline functions in callstacks')
|
||||
option('only_localhost', type : 'boolean', value : false, description : 'Only listen on the localhost interface')
|
||||
@@ -16,6 +17,7 @@ option('no_frame_image', type : 'boolean', value : false, description : 'Disable
|
||||
option('no_system_tracing', type : 'boolean', value : false, description : 'Disable systrace sampling')
|
||||
option('patchable_nopsleds', type : 'boolean', value : false, description : 'Enable nopsleds for efficient patching by system-level tools (e.g. rr)')
|
||||
option('timer_fallback', type : 'boolean', value : false, description : 'Use lower resolution timers')
|
||||
option('disallow_hw_timer', type : 'boolean', value : false, description : 'Disallow hardware timer (may be useful on VMs). Requires timer_fallback.')
|
||||
option('libunwind_backtrace', type : 'boolean', value : false, description : 'Use libunwind backtracing where supported')
|
||||
option('symbol_offline_resolve', type : 'boolean', value : false, description : 'Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution')
|
||||
option('libbacktrace_elf_dynload_support', type : 'boolean', value : false, description : 'Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation')
|
||||
@@ -24,5 +26,6 @@ option('manual_lifetime', type : 'boolean', value : false, description : 'Enable
|
||||
option('fibers', type : 'boolean', value : false, description : 'Enable fibers support')
|
||||
option('no_crash_handler', type : 'boolean', value : false, description : 'Disable crash handling')
|
||||
option('verbose', type : 'boolean', value : false, description : 'Enable verbose logging')
|
||||
option('no_internal_message', type : 'boolean', value : false, description : 'Prevent the profiler from logging messages')
|
||||
option('debuginfod', type : 'boolean', value : false, description : 'Enable debuginfod support')
|
||||
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation')
|
||||
|
||||
42
monitor/CMakeLists.txt
Normal file
42
monitor/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(TRACY_PUBLIC_DIR ${CMAKE_CURRENT_LIST_DIR}/../public)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
project(
|
||||
tracy-monitor
|
||||
LANGUAGES C CXX
|
||||
VERSION ${TRACY_VERSION_STRING}
|
||||
)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
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
|
||||
src/monitor.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${TRACY_PUBLIC_DIR}/TracyClient.cpp)
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC
|
||||
$<BUILD_INTERFACE:${TRACY_PUBLIC_DIR}>
|
||||
$<INSTALL_INTERFACE:include/tracy>)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
TRACY_ENABLE
|
||||
TRACY_DELAYED_INIT
|
||||
TRACY_MANUAL_LIFETIME
|
||||
TRACY_NO_FRAME_IMAGE
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Threads::Threads
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
add_git_ref(${PROJECT_NAME})
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
407
monitor/src/monitor.cpp
Normal file
407
monitor/src/monitor.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../public/tracy/Tracy.hpp"
|
||||
#include "../public/common/TracyVersion.hpp"
|
||||
#include "../public/client/TracyCallstack.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
namespace tracy {
|
||||
extern uint32_t ___tracy_magic_pid_override;
|
||||
extern char ___tracy_magic_process_name[64];
|
||||
}
|
||||
|
||||
static volatile sig_atomic_t s_shouldQuit = 0;
|
||||
static pid_t s_targetPid = 0;
|
||||
static bool s_isForked = false;
|
||||
|
||||
static void SignalHandler( int sig )
|
||||
{
|
||||
s_shouldQuit = 1;
|
||||
if( s_isForked && s_targetPid != 0 )
|
||||
{
|
||||
// We launched the target under ptrace, so forward the signal to wake
|
||||
// a blocking waitpid and let the child exit. kill() is async-signal-safe.
|
||||
kill( s_targetPid, SIGINT );
|
||||
}
|
||||
}
|
||||
|
||||
static bool ReadProcessName( pid_t pid, char* buf, size_t bufSize )
|
||||
{
|
||||
char path[64];
|
||||
snprintf( path, sizeof( path ), "/proc/%d/comm", (int)pid );
|
||||
FILE* f = fopen( path, "r" );
|
||||
if( !f ) return false;
|
||||
if( !fgets( buf, bufSize, f ) )
|
||||
{
|
||||
fclose( f );
|
||||
return false;
|
||||
}
|
||||
fclose( f );
|
||||
// Remove trailing newline
|
||||
size_t len = strlen( buf );
|
||||
while( len > 0 && ( buf[len-1] == '\n' || buf[len-1] == '\r' ) ) len--;
|
||||
buf[len] = '\0';
|
||||
return len > 0;
|
||||
}
|
||||
|
||||
static bool CheckPerfPermissions()
|
||||
{
|
||||
FILE* f = fopen( "/proc/sys/kernel/perf_event_paranoid", "r" );
|
||||
if( !f )
|
||||
{
|
||||
fprintf( stderr, "Warning: Cannot read /proc/sys/kernel/perf_event_paranoid\n" );
|
||||
return true; // Assume OK
|
||||
}
|
||||
int paranoid = 2;
|
||||
if( fscanf( f, "%d", ¶noid ) != 1 ) paranoid = 2;
|
||||
fclose( f );
|
||||
|
||||
if( paranoid > 1 && geteuid() != 0 )
|
||||
{
|
||||
fprintf( stderr, "Warning: perf_event_paranoid = %d. Profiling another process may require:\n", paranoid );
|
||||
fprintf( stderr, " - Running as root, or\n" );
|
||||
fprintf( stderr, " - Setting /proc/sys/kernel/perf_event_paranoid to -1 or 0, or\n" );
|
||||
fprintf( stderr, " - Granting CAP_PERFMON + CAP_SYS_PTRACE capabilities\n" );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ProcessIsAlive( pid_t pid )
|
||||
{
|
||||
return kill( pid, 0 ) == 0;
|
||||
}
|
||||
|
||||
// Try opening one perf event against the target so we fail fast with a clear
|
||||
// message instead of starting the profiler and silently producing no samples.
|
||||
static bool PreflightPerfEventOpen( pid_t pid )
|
||||
{
|
||||
perf_event_attr pe = {};
|
||||
pe.type = PERF_TYPE_SOFTWARE;
|
||||
pe.size = sizeof( perf_event_attr );
|
||||
pe.config = PERF_COUNT_SW_CPU_CLOCK;
|
||||
pe.disabled = 1;
|
||||
pe.exclude_kernel = 1;
|
||||
pe.exclude_hv = 1;
|
||||
|
||||
const long fd = syscall( __NR_perf_event_open, &pe, pid, 0, -1, 0 );
|
||||
if( fd < 0 )
|
||||
{
|
||||
const int err = errno;
|
||||
if( err == EACCES || err == EPERM )
|
||||
{
|
||||
fprintf( stderr, "Cannot open perf events for pid %d: %s\n", (int)pid, strerror( err ) );
|
||||
fprintf( stderr, "Profiling another process requires one of:\n" );
|
||||
fprintf( stderr, " - running as root, or\n" );
|
||||
fprintf( stderr, " - /proc/sys/kernel/perf_event_paranoid <= 0, or\n" );
|
||||
fprintf( stderr, " - CAP_PERFMON + CAP_SYS_PTRACE capabilities\n" );
|
||||
return false;
|
||||
}
|
||||
if( err == ESRCH )
|
||||
{
|
||||
fprintf( stderr, "Target process %d no longer exists.\n", (int)pid );
|
||||
return false;
|
||||
}
|
||||
// Other errors may still be recoverable in the real setup path.
|
||||
fprintf( stderr, "Warning: perf_event_open preflight failed: %s\n", strerror( err ) );
|
||||
return true;
|
||||
}
|
||||
close( (int)fd );
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PrintUsage( const char* progName )
|
||||
{
|
||||
printf( "tracy-monitor %i.%i.%i / %s\n\n", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: %s [OPTIONS] program [arguments...]\n", progName );
|
||||
printf( " %s [OPTIONS] -p PID\n", progName );
|
||||
printf( "\n" );
|
||||
printf( "Options:\n" );
|
||||
printf( " -p PID Attach to existing process (PID)\n" );
|
||||
printf( " -h Show this help message\n" );
|
||||
printf( "\n" );
|
||||
printf( "Examples:\n" );
|
||||
printf( " %s ./my_program arg1 arg2\n", progName );
|
||||
printf( " %s -p 1234\n", progName );
|
||||
printf( "\n" );
|
||||
printf( "The monitor captures sampling profiling data from an external process\n" );
|
||||
printf( "and streams it to a Tracy server for visualization.\n" );
|
||||
printf( "\n" );
|
||||
printf( "In launch mode, the target program is started under ptrace control to\n" );
|
||||
printf( "ensure profiling begins before the first instruction executes.\n" );
|
||||
printf( "\n" );
|
||||
printf( "In attach mode (-p), the target must already be running.\n" );
|
||||
}
|
||||
|
||||
static int RunAttached( pid_t pid )
|
||||
{
|
||||
if( !ProcessIsAlive( pid ) )
|
||||
{
|
||||
fprintf( stderr, "Process %d does not exist or is not accessible.\n", (int)pid );
|
||||
return 1;
|
||||
}
|
||||
|
||||
s_targetPid = pid;
|
||||
|
||||
char procName[64] = {};
|
||||
if( ReadProcessName( pid, procName, sizeof( procName ) ) )
|
||||
{
|
||||
memcpy( tracy::___tracy_magic_process_name, procName, sizeof( tracy::___tracy_magic_process_name ) );
|
||||
}
|
||||
|
||||
printf( "Attaching to process %d", (int)pid );
|
||||
if( tracy::___tracy_magic_process_name[0] ) printf( " (%s)", tracy::___tracy_magic_process_name );
|
||||
printf( "...\n" );
|
||||
fflush( stdout );
|
||||
|
||||
if( !PreflightPerfEventOpen( pid ) ) return 1;
|
||||
|
||||
tracy::InitExternalImageCache( pid );
|
||||
tracy::___tracy_magic_pid_override = (uint32_t)pid;
|
||||
tracy::StartupProfiler();
|
||||
|
||||
printf( "Profiling started. Waiting for Tracy server connection...\n" );
|
||||
|
||||
// Wait for the target process to exit, or for a signal
|
||||
while( !s_shouldQuit && ProcessIsAlive( pid ) )
|
||||
{
|
||||
usleep( 100000 ); // 100ms poll
|
||||
}
|
||||
|
||||
if( s_shouldQuit )
|
||||
{
|
||||
printf( "\nShutting down profiler...\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "Target process %d exited.\n", (int)pid );
|
||||
}
|
||||
|
||||
tracy::ShutdownProfiler();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int RunForked( int argc, char** argv )
|
||||
{
|
||||
pid_t childPid = fork();
|
||||
if( childPid < 0 )
|
||||
{
|
||||
fprintf( stderr, "Unable to fork: %s\n", strerror( errno ) );
|
||||
return 2;
|
||||
}
|
||||
|
||||
if( childPid == 0 )
|
||||
{
|
||||
// Child process: request ptrace stop at exec, then exec the target
|
||||
if( ptrace( PTRACE_TRACEME, 0, nullptr, nullptr ) < 0 )
|
||||
{
|
||||
fprintf( stderr, "ptrace(TRACEME) failed: %s\n", strerror( errno ) );
|
||||
_exit( 2 );
|
||||
}
|
||||
execvp( argv[0], argv );
|
||||
fprintf( stderr, "Unable to exec '%s': %s\n", argv[0], strerror( errno ) );
|
||||
_exit( 2 );
|
||||
}
|
||||
|
||||
// Parent: wait for the child to stop at the exec boundary (SIGTRAP)
|
||||
s_targetPid = childPid;
|
||||
s_isForked = true;
|
||||
|
||||
int status;
|
||||
for(;;)
|
||||
{
|
||||
if( waitpid( childPid, &status, 0 ) >= 0 ) break;
|
||||
if( errno == EINTR ) continue;
|
||||
fprintf( stderr, "waitpid failed: %s\n", strerror( errno ) );
|
||||
kill( childPid, SIGKILL );
|
||||
waitpid( childPid, nullptr, 0 );
|
||||
return 2;
|
||||
}
|
||||
|
||||
if( !WIFSTOPPED( status ) )
|
||||
{
|
||||
// Child exited or was killed before reaching the post-exec SIGTRAP.
|
||||
if( s_shouldQuit )
|
||||
{
|
||||
fprintf( stderr, "\nInterrupted before target started.\n" );
|
||||
}
|
||||
else if( WIFEXITED( status ) )
|
||||
{
|
||||
fprintf( stderr, "Target exited before profiling began (status %d) -- exec failed?\n", WEXITSTATUS( status ) );
|
||||
}
|
||||
else if( WIFSIGNALED( status ) )
|
||||
{
|
||||
fprintf( stderr, "Target killed by signal %d before profiling began.\n", WTERMSIG( status ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "Child process did not stop as expected (status=0x%x).\n", status );
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
// The child has exec'd but is stopped. Its address space is now the target program.
|
||||
// Read its process name and memory maps.
|
||||
char procName[64] = {};
|
||||
if( ReadProcessName( childPid, procName, sizeof( procName ) ) )
|
||||
{
|
||||
memcpy( tracy::___tracy_magic_process_name, procName, sizeof( tracy::___tracy_magic_process_name ) );
|
||||
}
|
||||
|
||||
printf( "Profiling '%s' (pid %d)...\n",
|
||||
tracy::___tracy_magic_process_name[0] ? tracy::___tracy_magic_process_name : argv[0],
|
||||
(int)childPid );
|
||||
fflush( stdout );
|
||||
|
||||
if( !PreflightPerfEventOpen( childPid ) )
|
||||
{
|
||||
kill( childPid, SIGKILL );
|
||||
waitpid( childPid, nullptr, 0 );
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize the external image cache (target's /proc/pid/maps)
|
||||
tracy::InitExternalImageCache( childPid );
|
||||
|
||||
// Set up the profiler to target the child
|
||||
tracy::___tracy_magic_pid_override = (uint32_t)childPid;
|
||||
tracy::StartupProfiler();
|
||||
|
||||
// Detach ptrace and let the child run. If detach fails the child stays
|
||||
// stopped forever, so this has to be fatal.
|
||||
if( ptrace( PTRACE_DETACH, childPid, nullptr, nullptr ) < 0 )
|
||||
{
|
||||
fprintf( stderr, "ptrace(DETACH) failed: %s -- killing child.\n", strerror( errno ) );
|
||||
kill( childPid, SIGKILL );
|
||||
waitpid( childPid, nullptr, 0 );
|
||||
tracy::ShutdownProfiler();
|
||||
return 2;
|
||||
}
|
||||
|
||||
printf( "Profiling started. Waiting for Tracy server connection...\n" );
|
||||
|
||||
// Wait for child to exit, or for a signal
|
||||
for(;;)
|
||||
{
|
||||
if( s_shouldQuit ) break;
|
||||
|
||||
int wstatus;
|
||||
pid_t ret = waitpid( childPid, &wstatus, WNOHANG );
|
||||
if( ret > 0 )
|
||||
{
|
||||
if( WIFEXITED( wstatus ) )
|
||||
{
|
||||
printf( "Target process exited with status %d.\n", WEXITSTATUS( wstatus ) );
|
||||
}
|
||||
else if( WIFSIGNALED( wstatus ) )
|
||||
{
|
||||
printf( "Target process killed by signal %d.\n", WTERMSIG( wstatus ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if( ret < 0 && errno != EINTR )
|
||||
{
|
||||
// Child already gone
|
||||
break;
|
||||
}
|
||||
usleep( 100000 );
|
||||
}
|
||||
|
||||
if( s_shouldQuit && ProcessIsAlive( childPid ) )
|
||||
{
|
||||
printf( "\nForwarding signal to child and shutting down...\n" );
|
||||
kill( childPid, SIGINT );
|
||||
// Give it a moment to exit
|
||||
usleep( 500000 );
|
||||
if( ProcessIsAlive( childPid ) )
|
||||
{
|
||||
kill( childPid, SIGKILL );
|
||||
waitpid( childPid, nullptr, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
tracy::ShutdownProfiler();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
auto progName = argv[0];
|
||||
|
||||
if( argc < 2 )
|
||||
{
|
||||
PrintUsage( progName );
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Install signal handlers for graceful shutdown
|
||||
struct sigaction sa = {};
|
||||
sa.sa_handler = SignalHandler;
|
||||
sigemptyset( &sa.sa_mask );
|
||||
sa.sa_flags = 0;
|
||||
sigaction( SIGINT, &sa, nullptr );
|
||||
sigaction( SIGTERM, &sa, nullptr );
|
||||
sigaction( SIGHUP, &sa, nullptr );
|
||||
sigaction( SIGQUIT, &sa, nullptr );
|
||||
|
||||
pid_t attachPid = 0;
|
||||
bool wantAttach = false;
|
||||
|
||||
static struct option longOptions[] = {
|
||||
{ "pid", required_argument, nullptr, 'p' },
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ nullptr, 0, nullptr, 0 }
|
||||
};
|
||||
|
||||
int c;
|
||||
while( ( c = getopt_long( argc, argv, "+p:h", longOptions, nullptr ) ) != -1 )
|
||||
{
|
||||
switch( c )
|
||||
{
|
||||
case 'p':
|
||||
attachPid = atoi( optarg );
|
||||
wantAttach = true;
|
||||
break;
|
||||
case 'h':
|
||||
PrintUsage( argv[0] );
|
||||
return 0;
|
||||
case '?':
|
||||
fprintf( stderr, "Unknown option. Use -h for help.\n" );
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
argv += optind;
|
||||
argc -= optind;
|
||||
|
||||
CheckPerfPermissions();
|
||||
|
||||
if( wantAttach )
|
||||
{
|
||||
if( attachPid <= 0 )
|
||||
{
|
||||
fprintf( stderr, "Invalid PID specified.\n" );
|
||||
return 1;
|
||||
}
|
||||
return RunAttached( attachPid );
|
||||
}
|
||||
|
||||
if( argc < 1 )
|
||||
{
|
||||
PrintUsage( progName ); // argv[0] was shifted, use original
|
||||
return 1;
|
||||
}
|
||||
|
||||
return RunForked( argc, argv );
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
option(NO_FILESELECTOR "Disable the file selector" OFF)
|
||||
option(GTK_FILESELECTOR "Use the GTK file selector on Linux instead of the xdg-portal one" OFF)
|
||||
option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
|
||||
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
|
||||
option(NO_STATISTICS "Disable calculation of statistics" OFF)
|
||||
option(SELF_PROFILE "Enable self-profiling" OFF)
|
||||
option(SANITIZE "Sanitizer parameters" OFF)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/options.cmake)
|
||||
|
||||
set_option(NO_FILESELECTOR "Disable the file selector" OFF)
|
||||
set_option(GTK_FILESELECTOR "Use the GTK file selector on Linux instead of the xdg-portal one" OFF)
|
||||
set_option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
|
||||
set_option(SELF_PROFILE "Enable self-profiling" OFF)
|
||||
set_option_value(SANITIZE "Sanitizer parameters" "")
|
||||
|
||||
set(NO_STATISTICS OFF)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
@@ -31,6 +33,7 @@ endif()
|
||||
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(ExternalProject)
|
||||
ExternalProject_Add(embed
|
||||
@@ -40,16 +43,12 @@ ExternalProject_Add(embed
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT data
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory data
|
||||
)
|
||||
|
||||
function(Embed LIST NAME FILE)
|
||||
add_custom_command(
|
||||
OUTPUT data/${NAME}.cpp data/${NAME}.hpp
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory data
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
|
||||
DEPENDS data embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
|
||||
DEPENDS embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
|
||||
)
|
||||
list(APPEND ${LIST} data/${NAME}.cpp)
|
||||
return(PROPAGATE ${LIST})
|
||||
@@ -61,6 +60,7 @@ set(SERVER_FILES
|
||||
TracyBadVersion.cpp
|
||||
TracyColor.cpp
|
||||
TracyConfig.cpp
|
||||
TracyDisassembly.cpp
|
||||
TracyEmbed.cpp
|
||||
TracyEventDebug.cpp
|
||||
TracyFileselector.cpp
|
||||
@@ -146,15 +146,33 @@ set(PROFILER_FILES
|
||||
)
|
||||
|
||||
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md)
|
||||
Embed(PROFILER_FILES SystemReminder src/llm/system.reminder.md)
|
||||
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md)
|
||||
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md)
|
||||
Embed(PROFILER_FILES ToolsJson src/llm/tools.json)
|
||||
|
||||
Embed(PROFILER_FILES FontFixed src/font/FiraCode-Retina.ttf)
|
||||
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 6\ Free-Solid-900.otf)
|
||||
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 7\ Free-Solid-900.otf)
|
||||
Embed(PROFILER_FILES FontNormal src/font/Roboto-Regular.ttf)
|
||||
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)
|
||||
|
||||
Embed(PROFILER_FILES Manual ../manual/tracy.md)
|
||||
|
||||
Embed(PROFILER_FILES Text100Million src/achievements/100Million.md)
|
||||
Embed(PROFILER_FILES TextConnectToClient src/achievements/ConnectToClient.md)
|
||||
Embed(PROFILER_FILES TextFindZone src/achievements/FindZone.md)
|
||||
Embed(PROFILER_FILES TextFrameImages src/achievements/FrameImages.md)
|
||||
Embed(PROFILER_FILES TextGlobalSettings src/achievements/GlobalSettings.md)
|
||||
Embed(PROFILER_FILES TextInstrumentationIntro src/achievements/InstrumentationIntro.md)
|
||||
Embed(PROFILER_FILES TextInstrumentationStatistics src/achievements/InstrumentationStatistics.md)
|
||||
Embed(PROFILER_FILES TextInstrumentFrames src/achievements/InstrumentFrames.md)
|
||||
Embed(PROFILER_FILES TextIntro src/achievements/Intro.md)
|
||||
Embed(PROFILER_FILES TextLoadTrace src/achievements/LoadTrace.md)
|
||||
Embed(PROFILER_FILES TextSamplingIntro src/achievements/SamplingIntro.md)
|
||||
Embed(PROFILER_FILES TextSaveTrace src/achievements/SaveTrace.md)
|
||||
|
||||
set(INCLUDES "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
set(LIBS "")
|
||||
|
||||
@@ -171,7 +189,7 @@ if(USE_WAYLAND)
|
||||
CPMAddPackage(
|
||||
NAME wayland-protocols
|
||||
GIT_REPOSITORY https://gitlab.freedesktop.org/wayland/wayland-protocols.git
|
||||
GIT_TAG 1.37
|
||||
GIT_TAG 1.47
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
|
||||
@@ -263,30 +281,7 @@ if(NOT EMSCRIPTEN)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED GIT_REV)
|
||||
set(GIT_REV "HEAD")
|
||||
endif()
|
||||
|
||||
find_package(Git)
|
||||
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 ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
|
||||
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(${PROJECT_NAME} git-ref)
|
||||
else()
|
||||
message(WARNING "git not found, using 'unknown' as git ref.")
|
||||
add_custom_command(
|
||||
OUTPUT GitRef.hpp
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(${PROJECT_NAME} PUBLIC GitRef.hpp)
|
||||
endif()
|
||||
add_git_ref(${PROJECT_NAME})
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
if(NOT NO_FILESELECTOR)
|
||||
@@ -298,7 +293,7 @@ if(NOT EMSCRIPTEN)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE -pthread -sASSERTIONS=0 -sINITIAL_MEMORY=384mb -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sSTACK_SIZE=1048576 -sWASM_BIGINT=1 -sPTHREAD_POOL_SIZE=8 -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile -sEXPORTED_RUNTIME_METHODS=ccall -sENVIRONMENT=web,worker --preload-file embed.tracy)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE -pthread -sASSERTIONS=0 -sINITIAL_MEMORY=384mb -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sSTACK_SIZE=1048576 -sWASM_BIGINT=1 -sPTHREAD_POOL_SIZE=8 -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard -sEXPORTED_RUNTIME_METHODS=ccall -sENVIRONMENT=web,worker --preload-file embed.tracy)
|
||||
|
||||
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
@@ -24,6 +24,30 @@ static float s_prevScale = -1;
|
||||
static int s_width, s_height;
|
||||
static uint64_t s_time;
|
||||
static const char* s_prevCursor = nullptr;
|
||||
static std::string s_clipboard;
|
||||
|
||||
extern "C" void tracy_paste_clipboard( const char* text )
|
||||
{
|
||||
s_clipboard = text;
|
||||
ImGui::GetIO().AddInputCharactersUTF8( text );
|
||||
}
|
||||
|
||||
static void SetClipboard( ImGuiContext*, const char* text )
|
||||
{
|
||||
s_clipboard = text;
|
||||
EM_ASM( {
|
||||
var text = UTF8ToString($0);
|
||||
if( navigator.clipboard && navigator.clipboard.writeText )
|
||||
{
|
||||
navigator.clipboard.writeText( text );
|
||||
}
|
||||
}, text );
|
||||
}
|
||||
|
||||
static const char* GetClipboard( ImGuiContext* )
|
||||
{
|
||||
return s_clipboard.c_str();
|
||||
}
|
||||
|
||||
static ImGuiKey TranslateKeyCode( const char* code )
|
||||
{
|
||||
@@ -183,6 +207,10 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.BackendPlatformName = "wasm (tracy profiler)";
|
||||
|
||||
auto& platform = ImGui::GetPlatformIO();
|
||||
platform.Platform_SetClipboardTextFn = SetClipboard;
|
||||
platform.Platform_GetClipboardTextFn = GetClipboard;
|
||||
|
||||
emscripten_set_mousedown_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
|
||||
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, true );
|
||||
tracy::s_wasActive = true;
|
||||
@@ -218,7 +246,7 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
|
||||
const auto code = TranslateKeyCode( e->code );
|
||||
if( code == ImGuiKey_None ) return EM_FALSE;
|
||||
ImGui::GetIO().AddKeyEvent( code, true );
|
||||
if( e->key[0] && !e->key[1] ) ImGui::GetIO().AddInputCharacter( *e->key );
|
||||
if( e->key[0] && !e->key[1] && !e->ctrlKey && !e->metaKey ) ImGui::GetIO().AddInputCharacter( *e->key );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
|
||||
@@ -227,6 +255,12 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
|
||||
ImGui::GetIO().AddKeyEvent( code, false );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
EM_ASM( {
|
||||
document.addEventListener( 'paste', function( e ) {
|
||||
var text = ( e.clipboardData || window.clipboardData ).getData( 'text' );
|
||||
if( text ) ccall( 'tracy_paste_clipboard', 'void', ['string'], [text] );
|
||||
} );
|
||||
} );
|
||||
|
||||
s_time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
|
||||
}
|
||||
@@ -265,8 +299,12 @@ void Backend::NewFrame( int& w, int& h )
|
||||
|
||||
if( s_width != w || s_height != h )
|
||||
{
|
||||
EM_ASM( Module.canvas.style.width = window.innerWidth + 'px'; Module.canvas.style.height = window.innerHeight + 'px' );
|
||||
EM_ASM( Module.canvas.width = $0; Module.canvas.height = $1, w, h );
|
||||
EM_ASM( {
|
||||
Module.canvas.style.width = ($0 / $2) + 'px';
|
||||
Module.canvas.style.height = ($1 / $2) + 'px';
|
||||
Module.canvas.width = $0;
|
||||
Module.canvas.height = $1;
|
||||
}, w, h, double( scale ) );
|
||||
|
||||
s_width = w;
|
||||
s_height = h;
|
||||
|
||||
@@ -134,7 +134,18 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
|
||||
# endif
|
||||
#endif
|
||||
s_window = glfwCreateWindow( m_winPos.w, m_winPos.h, title, NULL, NULL );
|
||||
if( !s_window ) exit( 1 );
|
||||
if( !s_window ) {
|
||||
const char* description;
|
||||
int code = glfwGetError( &description );
|
||||
if( description ) {
|
||||
fprintf( stderr, "ERROR: Tracy (GLFW): %s\n", description );
|
||||
#ifdef _WIN32
|
||||
MessageBoxA( NULL, description, "ERROR: Tracy (GLFW)", MB_OK );
|
||||
OutputDebugStringA( description );
|
||||
#endif
|
||||
}
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
glfwSetWindowPos( s_window, m_winPos.x, m_winPos.y );
|
||||
#if GLFW_VERSION_MAJOR > 3 || ( GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 2 )
|
||||
|
||||
@@ -119,7 +119,7 @@ constexpr ImGuiKey s_keyTable[] = {
|
||||
/* 81 */ ImGuiKey_Keypad3,
|
||||
/* 82 */ ImGuiKey_Keypad0,
|
||||
/* 83 */ ImGuiKey_KeypadDecimal,
|
||||
/* 84 */ ImGuiKey_RightAlt,
|
||||
/* 84 */ ImGuiKey_None,
|
||||
/* 85 */ ImGuiKey_None,
|
||||
/* 86 */ ImGuiKey_Backslash,
|
||||
/* 87 */ ImGuiKey_F11,
|
||||
@@ -135,7 +135,7 @@ constexpr ImGuiKey s_keyTable[] = {
|
||||
/* 97 */ ImGuiKey_RightCtrl,
|
||||
/* 98 */ ImGuiKey_KeypadDivide,
|
||||
/* 99 */ ImGuiKey_PrintScreen,
|
||||
/* 100 */ ImGuiKey_RightAlt,
|
||||
/* 100 */ ImGuiKey_None,
|
||||
/* 101 */ ImGuiKey_None,
|
||||
/* 102 */ ImGuiKey_Home,
|
||||
/* 103 */ ImGuiKey_UpArrow,
|
||||
@@ -338,8 +338,8 @@ static void PointerFrame( void*, struct wl_pointer* pointer )
|
||||
if( s_wheel )
|
||||
{
|
||||
s_wheel = false;
|
||||
s_wheelAxisX /= 8;
|
||||
s_wheelAxisY /= 8;
|
||||
s_wheelAxisX /= 15;
|
||||
s_wheelAxisY /= 15;
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.AddMouseWheelEvent( wl_fixed_to_double( s_wheelAxisX ), wl_fixed_to_double( s_wheelAxisY ) );
|
||||
s_wheelAxisX = s_wheelAxisY = 0;
|
||||
|
||||
29
profiler/src/EmscriptenShim.hpp
Normal file
29
profiler/src/EmscriptenShim.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef __EMSCRIPTENSHIM_HPP__
|
||||
#define __EMSCRIPTENSHIM_HPP__
|
||||
|
||||
#if defined __EMSCRIPTEN__ || defined __APPLE__
|
||||
|
||||
namespace std {
|
||||
|
||||
template<typename T>
|
||||
struct atomic<shared_ptr<T>> {
|
||||
shared_ptr<T> value;
|
||||
|
||||
bool compare_exchange_weak(shared_ptr<T>& expected, const shared_ptr<T>& desired) noexcept {
|
||||
return atomic_compare_exchange_weak_explicit(&value, &expected, &desired, std::memory_order_acq_rel, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
shared_ptr<T> load(memory_order order = std::memory_order_seq_cst) const noexcept {
|
||||
return atomic_load_explicit(&value, order);
|
||||
}
|
||||
|
||||
void store(shared_ptr<T> desired, memory_order order = std::memory_order_seq_cst) noexcept {
|
||||
atomic_store_explicit(&value, desired, order);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <misc/freetype/imgui_freetype.h>
|
||||
|
||||
#include "Fonts.hpp"
|
||||
#include "profiler/IconsFontAwesome6.h"
|
||||
#include "profiler/TracyEmbed.hpp"
|
||||
|
||||
#include "data/FontFixed.hpp"
|
||||
@@ -13,6 +12,7 @@
|
||||
#include "data/FontBold.hpp"
|
||||
#include "data/FontBoldItalic.hpp"
|
||||
#include "data/FontItalic.hpp"
|
||||
#include "data/FontEmoji.hpp"
|
||||
|
||||
FontData g_fonts;
|
||||
|
||||
@@ -34,29 +34,35 @@ void LoadFonts( float scale )
|
||||
configFixed.GlyphExtraAdvanceX = -1;
|
||||
configFixed.FontDataOwnedByAtlas = false;
|
||||
|
||||
auto fontFixed = Unembed( FontFixed );
|
||||
auto fontIcons = Unembed( FontIcons );
|
||||
auto fontNormal = Unembed( FontNormal );
|
||||
auto fontBold = Unembed( FontBold );
|
||||
auto fontBoldItalic = Unembed( FontBoldItalic );
|
||||
auto fontItalic = Unembed( FontItalic );
|
||||
static auto fontFixed = Unembed( FontFixed );
|
||||
static auto fontIcons = Unembed( FontIcons );
|
||||
static auto fontNormal = Unembed( FontNormal );
|
||||
static auto fontBold = Unembed( FontBold );
|
||||
static auto fontBoldItalic = Unembed( FontBoldItalic );
|
||||
static auto fontItalic = Unembed( FontItalic );
|
||||
static auto fontEmoji = Unembed( FontEmoji );
|
||||
|
||||
io.Fonts->Clear();
|
||||
|
||||
g_fonts.normal = io.Fonts->AddFontFromMemoryTTF( (void*)fontNormal->data(), fontNormal->size(), round( 15.0f * scale ), &configBasic );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
|
||||
|
||||
g_fonts.mono = io.Fonts->AddFontFromMemoryTTF( (void*)fontFixed->data(), fontFixed->size(), round( 15.0f * scale ), &configFixed );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
|
||||
|
||||
g_fonts.bold = io.Fonts->AddFontFromMemoryTTF( (void*)fontBold->data(), fontBold->size(), round( 15.0f * scale ), &configBasic );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
|
||||
|
||||
g_fonts.boldItalic = io.Fonts->AddFontFromMemoryTTF( (void*)fontBoldItalic->data(), fontBoldItalic->size(), round( 15.0f * scale ), &configBasic );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
|
||||
|
||||
g_fonts.italic = io.Fonts->AddFontFromMemoryTTF( (void*)fontItalic->data(), fontItalic->size(), round( 15.0f * scale ), &configBasic );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
|
||||
io.Fonts->AddFontFromMemoryTTF( (void*)fontEmoji->data(), fontEmoji->size(), round( 14.0f * scale ), &configMerge );
|
||||
|
||||
FontNormal = round( scale * 15.f );
|
||||
FontSmall = round( scale * 15 * 2.f / 3.f );
|
||||
|
||||
12
profiler/src/achievements/100Million.md
Normal file
12
profiler/src/achievements/100Million.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# It's over 100 million!
|
||||
|
||||
Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!
|
||||
|
||||
Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it.
|
||||
|
||||
Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case.
|
||||
|
||||
> [!TIP]
|
||||
> Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory.
|
||||
>
|
||||
> To capture 100 million zones, you will need approximately 4 GB of RAM.
|
||||
10
profiler/src/achievements/ConnectToClient.md
Normal file
10
profiler/src/achievements/ConnectToClient.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# First profiling session
|
||||
|
||||
Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:
|
||||
|
||||
1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project.
|
||||
2. Make sure that `TracyClient.cpp` (or the Tracy library) is included in your build.
|
||||
3. Define `TRACY_ENABLE` in your build configuration, for the whole application. Do not do it in a single source file because it won't work.
|
||||
4. Start your application, and * Connect* to it with the profiler.
|
||||
|
||||
Please refer to the [user manual](https://github.com/wolfpld/tracy/releases) for more details.
|
||||
11
profiler/src/achievements/FindZone.md
Normal file
11
profiler/src/achievements/FindZone.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Find some zones
|
||||
|
||||
You can search for zones in the trace by opening the search window with the * Find zone* button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code.
|
||||
|
||||
The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them.
|
||||
|
||||
Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone.
|
||||
|
||||
When a zone is selected, a number of statistics are displayed to help you understand the performance of your application. In addition, a histogram of the zone execution times is displayed to make it easier for you to determine the performance of the profiled code. Be sure to select a zone with a large number of calls to make the histogram look interesting!
|
||||
|
||||
Note that you can draw a range on the histogram to limit the number of entries displayed in the zone list below. This list allows you to examine each zone individually. There are also a number of zone groupings that you can select. Each group can be selected and the time associated with the selected group will be highlighted on the histogram.
|
||||
11
profiler/src/achievements/FrameImages.md
Normal file
11
profiler/src/achievements/FrameImages.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# A picture is worth a thousand words
|
||||
|
||||
Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the `FrameImage` macro.
|
||||
|
||||
You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way.
|
||||
|
||||
The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen.
|
||||
|
||||
You can even view a recording of what your application was doing by clicking the * Tools* icon and then selecting the * Playback* option. Try it out!
|
||||
|
||||
The `FrameImage` macro is a great way to see what happened in your application at a particular time. Maybe you have a performance problem that only occurs when a certain object is on the screen?
|
||||
5
profiler/src/achievements/GlobalSettings.md
Normal file
5
profiler/src/achievements/GlobalSettings.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Global settings
|
||||
|
||||
Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the * Wrench* icon on the welcome screen. This will open the about window, where you can expand the * Global settings* menu.
|
||||
|
||||
The settings are saved between sessions, so you only need to set them once.
|
||||
22
profiler/src/achievements/InstrumentFrames.md
Normal file
22
profiler/src/achievements/InstrumentFrames.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Instrumenting frames
|
||||
|
||||
In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application.
|
||||
|
||||
To instrument frames, you need to add the `FrameMark` macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame.
|
||||
|
||||
```c++
|
||||
#include "Tracy.hpp"
|
||||
|
||||
void Render()
|
||||
{
|
||||
// Render the frame
|
||||
SwapBuffers();
|
||||
FrameMark;
|
||||
}
|
||||
```
|
||||
|
||||
When you profile your application, you will see a new frame appear on the timeline each time the `FrameMark` macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second.
|
||||
|
||||
The `FrameMark` macro is a great way to see at a glance how your application is performing over time. Maybe there are some performance problems that only appear after a few minutes of running the application? A frame graph is drawn at the top of the profiler window where you can see the timing of all frames.
|
||||
|
||||
Note that some applications do not have a frame-based structure, and in such cases, frame instrumentation may not be useful. That's ok.
|
||||
22
profiler/src/achievements/InstrumentationIntro.md
Normal file
22
profiler/src/achievements/InstrumentationIntro.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Instrumentating your application
|
||||
|
||||
Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up.
|
||||
|
||||
To get started, open a source file and include the `Tracy.hpp` header. This will give you access to a variety of macros provided by Tracy. Next, add the `ZoneScoped` macro to the beginning of one of your functions, like this:
|
||||
|
||||
```c++
|
||||
#include "Tracy.hpp"
|
||||
|
||||
void SomeFunction()
|
||||
{
|
||||
ZoneScoped;
|
||||
// Your code here
|
||||
}
|
||||
```
|
||||
|
||||
Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called.
|
||||
|
||||
> [!NOTE]
|
||||
> The `ZoneScoped` macro is just one of the many macros provided by Tracy. See the documentation for more information.
|
||||
|
||||
The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information.
|
||||
5
profiler/src/achievements/InstrumentationStatistics.md
Normal file
5
profiler/src/achievements/InstrumentationStatistics.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Show me the stats!
|
||||
|
||||
Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called.
|
||||
|
||||
To view the statistics, click on the * Statistics* button on the top bar. This will open a new window with a list of all zones in the trace.
|
||||
12
profiler/src/achievements/Intro.md
Normal file
12
profiler/src/achievements/Intro.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Click here to discover achievements!
|
||||
|
||||
Clicking on the * Achievements* button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done.
|
||||
|
||||
As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!
|
||||
|
||||
To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements * Achievements* button will glow yellow when there are new things to see.
|
||||
|
||||
- New tasks: orange
|
||||
- Completed tasks: green
|
||||
|
||||
Good luck!
|
||||
3
profiler/src/achievements/LoadTrace.md
Normal file
3
profiler/src/achievements/LoadTrace.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Load a trace
|
||||
|
||||
You can open a previously saved trace file (or one received from a friend) with the * Open saved trace* button on the welcome screen.
|
||||
10
profiler/src/achievements/SamplingIntro.md
Normal file
10
profiler/src/achievements/SamplingIntro.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Sampling program execution
|
||||
|
||||
Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often.
|
||||
|
||||
While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return.
|
||||
|
||||
Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work.
|
||||
|
||||
> [!WARNING]
|
||||
> Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information.
|
||||
12
profiler/src/achievements/SaveTrace.md
Normal file
12
profiler/src/achievements/SaveTrace.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Save a trace
|
||||
|
||||
Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the * Connection* icon in the top left corner of the screen and then clicking on the * Save trace* button.
|
||||
|
||||
Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before.
|
||||
|
||||
You can also share the trace with your friends or co-workers by sending them the trace file.
|
||||
|
||||
> [!WARNING]
|
||||
> **Warning**
|
||||
>
|
||||
> Trace files can contain sensitive information about your application, such as program code, or even the contents of source files. Be careful when sharing them with others.
|
||||
Binary file not shown.
BIN
profiler/src/font/Font Awesome 7 Free-Solid-900.otf
Normal file
BIN
profiler/src/font/Font Awesome 7 Free-Solid-900.otf
Normal file
Binary file not shown.
BIN
profiler/src/font/NotoEmoji-Regular.ttf
Normal file
BIN
profiler/src/font/NotoEmoji-Regular.ttf
Normal file
Binary file not shown.
111
profiler/src/llm/skill.callstack.md
Normal file
111
profiler/src/llm/skill.callstack.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Call stacks
|
||||
|
||||
A call stack is a trace of program execution at the moment of capture. This capture can happen at any time, but it usually occurs when a program crashes, so the developer can trace the path that led to the failure. The case of call stacks in a profiler is very different. During profiling, the operating system halts execution of the program at a predefined rate-for example, 10 kHz-notes where the program execution is and the function calls that led it there, then resumes the program. As can be seen, the call stacks in a profiler are slices of a normal workflow you can use to explore execution characteristics, not indications of failure.
|
||||
|
||||
# Call stack structure
|
||||
|
||||
The top call stack frame is numbered 0, and it always is the place *where the program execution is* during the stack capture. Stack frame numbers increase the farther toward the origin function we go, as the usual convention goes. A stack trace is always a snapshot of what a *single thread* is doing.
|
||||
|
||||
In Tracy Profiler, each execution frame can have multiple *inline* frames. When a program is compiled, the compiler, as an optimization, may inline function calls into the base function ("symbol"), and the profiler can track that.
|
||||
|
||||
To show how it works, let's consider the following source code:
|
||||
|
||||
```c++
|
||||
float Square(float val) { return val*val; }
|
||||
float Distance(Point p1, Point p2) { return sqrt(Square(p1.x-p2.x)+Square(p1.y-p2.y)); }
|
||||
bool CanReach(Player p, Item i) { return Distance(p.pos, i.pos)<5; }
|
||||
```
|
||||
|
||||
Now, let's say we capture a call stack inside the `Square` function. This is how the call stack can look:
|
||||
|
||||
```callstack
|
||||
0. Square() [inline 0]
|
||||
0. Distance() [inline 1]
|
||||
0. CanReach() [inline 2]
|
||||
1. ItemsLoop()
|
||||
2. PlayerLogic()
|
||||
3. ...
|
||||
```
|
||||
|
||||
There are three frames with index 0, which means that both `Square` and `Distance` have been inlined into the `CanReach` function, forming a symbol named `CanReach`. Following the inline stack frame indices, we can also see that the call order is `CanReach` -> `Distance` -> `Square`, which matches what the source code does.
|
||||
|
||||
Note that while the example is at the top level, inline frames can appear at any depth of the call stack.
|
||||
|
||||
# Call stacks are return stacks
|
||||
|
||||
You need to be very careful when reading call stacks. The usual notion is that call stacks (as the name suggests) show function call stacks, that is, which function called which to get where we are. Unfortunately, this is not true. In reality, call stacks are *function return stacks*. The call stack shows where each function will **return**, not from where it was called.
|
||||
|
||||
## Example 1
|
||||
|
||||
To fully understand how this works, consider the following source code:
|
||||
|
||||
```c++
|
||||
int main()
|
||||
{
|
||||
auto app = std::make_unique<Application>();
|
||||
app->Run();
|
||||
app.reset();
|
||||
}
|
||||
```
|
||||
|
||||
Let's assume the `Application` instance (`app`) is already created and we have entered the `Run` method, where, somewhere inside, we're capturing a call stack. Here's a result we might get:
|
||||
|
||||
```callstack
|
||||
0. Application::Run()
|
||||
1. std::unique_ptr<Application>::reset()
|
||||
2. main()
|
||||
```
|
||||
|
||||
At the first glance it may look like `unique_ptr::reset` was the *call site* of the `Application::Run`, which would make no sense, but this is not the case here. When you remember these are the *function return points*, it becomes much more clear what is happening. As an optimization, `Application::Run` is returning directly into `unique_ptr::reset`, skipping the return to `main` and an unnecessary `reset` function call.
|
||||
|
||||
## Example 2
|
||||
|
||||
Here you will see how a function on the call chain can be entirely absent:
|
||||
|
||||
```c++
|
||||
int ComputeHash(const Buffer& b) {
|
||||
// expensive hashing
|
||||
...
|
||||
}
|
||||
|
||||
int Validate(const Buffer& b) {
|
||||
if(b.empty()) return 0;
|
||||
return ComputeHash(b);
|
||||
}
|
||||
|
||||
void HandleRequest(Request& r) {
|
||||
int h = Validate(r.payload);
|
||||
Store(r, h);
|
||||
}
|
||||
```
|
||||
|
||||
A sample lands inside ComputeHash. The captured stack looks like this:
|
||||
|
||||
```callstack
|
||||
0. ComputeHash()
|
||||
1. HandleRequest()
|
||||
2. RequestLoop()
|
||||
```
|
||||
|
||||
**Naive (call-stack) reading:** `HandleRequest` called `ComputeHash` directly. Conclusion: optimize the call site in `HandleRequest`, or rethink why `HandleRequest` is hashing.
|
||||
|
||||
**Correct (return-stack) reading:** Frame 1 is where `ComputeHash` will return to, not who called it. `Validate` ended with return `ComputeHash(b)`, so the compiler turned that into a tail call — `Validate`'s own frame was reused for `ComputeHash`. When `ComputeHash` returns, it skips `Validate` entirely and lands in `HandleRequest`. `Validate` is on the actual call chain (`HandleRequest` -> `Validate` -> `ComputeHash`) but is missing from the stack.
|
||||
|
||||
**Why the divergence matters:** The naive reading sends you to fix the wrong code. `HandleRequest` doesn't decide to hash anything — `Validate` does. If you act on the naive interpretation, you investigate a function that has no agency in this hot path while the actual decision-maker (`Validate`) is invisible.
|
||||
|
||||
**Lesson the example teaches:** Any frame below a given frame in the stack may not be its caller. It is only guaranteed to be the return target. Functions on the real call chain can be entirely absent — tail-called away or inlined.
|
||||
|
||||
# Crash handler
|
||||
|
||||
Tracy Profiler can intercept crashes and report them to the user for analysis. To do this, some code machinery is needed, and then the Tracy crash handler needs to run, capture the call stack, and send it over the network. All this only happens after the actual crash occurred; otherwise, there would be no reason to run the crash handler. As a consequence, the retrieved crash trace may include parts of the crash handler stack, which you must ignore.
|
||||
|
||||
# Base address and instruction pointer
|
||||
|
||||
Each frame in a call stack has an associated instruction pointer, `ip` – the return address where the execution will return from the function a frame above. This address is somewhere in the symbol code. The start of the symbol is provided as the `baseAddr` value. This base address identifies the symbol and can be used in various symbol-related tool calls as the symbol address.
|
||||
|
||||
# Inspecting call stacks
|
||||
|
||||
1. Focus on user's code. Ignore standard library boilerplate.
|
||||
2. Retrieve source code to verify call stack validity. Frames in call stacks are return locations, and the call site may actually be near the reported source line, or in a different function altogether.
|
||||
3. Top of the call stack is the most interesting, as it shows what the program is doing *now*. The bottom of the call stack shows what the program did to do what it's doing.
|
||||
4. If the call stack contains Tracy's crash handler, the profiled program has crashed. In this case, ignore the crash handler and any functions it may be calling. The crash happened *before* the handler intercepted it.
|
||||
70
profiler/src/llm/skill.optimization.md
Normal file
70
profiler/src/llm/skill.optimization.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Program optimization
|
||||
|
||||
The user may ask you to optimize a particular functionality, routine, or code fragment. While doing so, the user may include attachments of various types:
|
||||
|
||||
- Source code listings, with or without per-line performance data.
|
||||
- A machine instructions list (disassembly of binary code), accompanied by per-instruction performance data.
|
||||
|
||||
You should try to find where the optimization opportunities are. Note that some code may already be optimized very well, and there may be little or nothing left to gain.
|
||||
|
||||
# Symbols
|
||||
|
||||
When a source code function is compiled, the compiler may inline multiple auxiliary functions into the produced machine code block. This block is called a *symbol*. The symbol may contain multiple source-level functions (some of which may be repeated multiple times), which may come from multiple source files.
|
||||
|
||||
# Assembly listings
|
||||
|
||||
The assembly instruction listing of a symbol must be mapped to the source code. The assembly attachment contains the code itself, and an array of source files named `files`. The format of an assembly line is:
|
||||
|
||||
fileIdx:line:offset:cost:callCost:assembly
|
||||
|
||||
To identify the source file name of any assembly instruction, you must access `files[fileIdx]`. The `fileIdx` value is strictly internal and should never be presented to the user. Always show the source file name and line number in your answers. Since symbols can be constructed from multiple source files, you must specify both the source file name and line number, or user won't know which file you refer to.
|
||||
|
||||
The `offset` value represents the byte offset at which the machine instruction lies in the symbol code.
|
||||
|
||||
The `cost` value shows how much time the CPU spent executing the given machine instruction. If the cost is not present, the profiler recorded no activity for the given instruction. The `callCost` value shows how much time was spent executing the called external functions. The cost values are percentages relative to the total execution cost of an entire symbol, including external function calls.
|
||||
|
||||
The `assembly` value is the actual disassembled machine code. It may also contain a comment with:
|
||||
- Local jump target, `label`, for example `.L6`.
|
||||
- Name of the external function call, `destination`.
|
||||
|
||||
## Measurement skid
|
||||
|
||||
The measurements present in the attachment may be slightly imprecise due to the way the profiler infrastructure or the CPU works, especially considering out-of-order architectures. As a result, some cost value may be wrongly attributed to the instruction in the immediate vicinity of the instruction that produced the cost.
|
||||
|
||||
Take the following example:
|
||||
|
||||
```asm
|
||||
5% mov rax, [rbx]
|
||||
40% inc rax
|
||||
```
|
||||
|
||||
The first instruction that loads the value from memory is the high-latency one, but it can be dispatched for execution fairly quickly. The second instruction, which needs the output of the first instruction, is actually very fast to execute but is blocked by the slow memory access of the first instruction, taking the majority of the cost on itself.
|
||||
|
||||
A careful investigation of the cost attribution is thus needed.
|
||||
|
||||
# Symbols vs source code
|
||||
|
||||
Analyzing a user program can be done in two complementary ways.
|
||||
|
||||
1. You can retrieve the function source code and look at what it does. This is enough for simple checks.
|
||||
2. Alternatively, you can get the disassembly of the binary code of a symbol. This method of analysis contains source line information, which can be used to match the assembly against the source code, as well as CPU usage data, allowing you to see which individual assembly instructions have the most performance cost associated with them. Doing this deep dive is important for thorough analysis of code performance characteristics.
|
||||
|
||||
# Which code paths are important
|
||||
|
||||
When looking at code, you may find many places that use inefficient algorithms or implementations. While pointing out such cases may sometimes be useful, you must check whether the problematic code is actually on the hot path, as indicated by the profiling data included with the disassembly. The profiling data the user provides are highly targeted at specific workflows, and the primary optimization target should be the code that was actually executing, not something that could run theoretically. Avoid including optimization advice for code paths that might run but did not.
|
||||
|
||||
# Context is important
|
||||
|
||||
When reasoning about the performance of a symbol, you should look at the environment where it is used. You can do this by:
|
||||
1. Following function calls and inspecting the source code and disassembly performance data. Maybe there's some important insight that shows an inefficiency in how the symbol is used?
|
||||
2. Looking at the entry call stacks, which show how the symbol is reached in the program. Maybe the key to optimization is not the symbol itself but how it is called by the parent function?
|
||||
|
||||
# General optimization procedure
|
||||
|
||||
1. Start by mapping the assembly instructions to the source code. All reasoning should be performed with source code first. The assembly can only be used as a supplementary source.
|
||||
2. Analyze the available data, looking for where the majority of the run time is spent. Always look at the code as a whole. Do not stop after finding a bunch of interesting spots.
|
||||
3. Consider the external calls the function is making. If appropriate, look at the performance characteristics of the called code.
|
||||
4. Figure out what algorithms are in use, how the data is structured, how it flows, and reason about trade-offs taken.
|
||||
5. Determine whether the code can be made to perform better. Note that some code will already be optimal, despite having hot spots.
|
||||
6. Formulate the optimization opportunities and present them to the user. Tell the user where the problems are, what causes them, and the potential solutions.
|
||||
7. Do not provide concrete speedup percentages. It is only possible to know how much faster the code is by measuring it after the changes. You can't do that.
|
||||
@@ -1,203 +1,43 @@
|
||||
You are a language model, designed to provide precise answers based on available tools and your knowledge. Your operation must strictly adhere to the instructions below.
|
||||
|
||||
You're a language model, meant to give exact answers using the tools you have and what you know. The current time is %TIME%. Your operation has to follow these instructions exactly.
|
||||
|
||||
# Core Principles:
|
||||
|
||||
1. *Never guess or invent information.* If you do not have the necessary data, use the available tools to gather it.
|
||||
2. Always protect privacy of the user.
|
||||
3. If the tools return no data or you still lack the required information after using the tools, attempt to answer using your internal knowledge, while clearly informing the user that the response might be incorrect, invalid, or wrong, and that the tools returned no data.
|
||||
4. *Never ask the user* for permission to use tools or perform further queries. You *MUST* conduct the entire information retrieval process independently, and *ONLY THEN* reply to the user.
|
||||
5. *Language Consistency:* If the user's query is in a language other than English, you MUST translate *all* tool output and internally generated responses into the user's query language *before* formulating your final response. Your final response to the user must always be in the language they used. Do not output information in any other language.
|
||||
6. Prioritize information obtained via tools (from `<tool_output>`) over your internal knowledge when constructing your response. Treat tool outputs as the leading source of information, but be aware that they may contain irrelevant details, inconsistencies, or inaccuracies. Critically evaluate all tool outputs: check for relevance to the user's query, cross-reference information across different tool outputs, and assess consistency with your internal knowledge. If multiple tool outputs provide conflicting but equally plausible information, you may state the different findings or, if possible, explain the discrepancy if it leads to a clearer answer. Avoid presenting information as definitively true if its source is uncertain.
|
||||
|
||||
|
||||
# Thinking Process and Tool Usage:
|
||||
|
||||
Your operation process will be strictly structured using `<think>` and `<tool>` tags.
|
||||
|
||||
1. *Thinking Process (`<think>`):*
|
||||
- Always start with a `<think>` block.
|
||||
- This block is for planning, analyzing the user's query, deciding which tools are needed (if any), processing results from `<tool_output>`, and formulating the structure of the response.
|
||||
- You must analyse the question or any attachments provided by the user to decide which tools you can use.
|
||||
- The tag name MUST be exactly `think`.
|
||||
|
||||
2. *Tool Usage (`<tool>`):*
|
||||
- If, in the `<think>` block, you decide you need to use a tool, the next block generated *MUST* be a `<tool>` block.
|
||||
- The tag name MUST be exactly `tool`.
|
||||
- There can be ONLY ONE tool call in the `<tool>` block.
|
||||
- Only ONE tool call is permitted PER TURN.
|
||||
- *After generating a `<tool>` block, you MUST END YOUR RESPONSE FOR THIS TURN.* Do not generate any other text or tags after the `<tool>` block. The system will process this tool call and provide you with the result in the next step.
|
||||
- The tool name and its parameters (if applicable) must be passed as a json data. For example:
|
||||
<think>
|
||||
The user is asking about the weather in San Francisco. I need to use the weather checking tool. The tool name is 'check_weather', the parameter is the city name.
|
||||
</think>
|
||||
<tool>
|
||||
{"tool": "check_weather", "city": "San Francisco"}
|
||||
</tool>
|
||||
|
||||
3. *Tool Output (`<tool_output>`):*
|
||||
- After the system executes the tool call from the `<tool>` block, you will receive the result in an `<tool_output>` block.
|
||||
- *You MUST process this result in the subsequent `<think>` block.* Analyze the data received. Based on it, decide if further tool calls are necessary or if you have enough information to answer the user.
|
||||
- *Never show the user the raw text from `<tool_output>`*. All processing happens internally within the `<think>` block.
|
||||
|
||||
|
||||
# Available Tools:
|
||||
|
||||
These are the tools you can use. *You have no access to any other tools or means to search the web outside of these.*
|
||||
|
||||
```json
|
||||
{
|
||||
"tool": "search_wikipedia",
|
||||
"description": "Search the Wikipedia with given query. The `key` field in the response is the Wikipedia page name.",
|
||||
"network": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"description": "The search terms in the language matching the second parameter."
|
||||
},
|
||||
{
|
||||
"name": "language",
|
||||
"description": "Language code matching the search query. For example, `en` for English or `pl` for Polish."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool": "get_wikipedia",
|
||||
"description": "Retrieve the Wikipedia article on given subject. The response may be trimmed.",
|
||||
"network": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"description": "The `key` field from the search response, specifying the topic you want to retrieve.",
|
||||
},
|
||||
{
|
||||
"name": "language",
|
||||
"description": "Language code."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool": "get_dictionary",
|
||||
"description": "Retrieve description of a word from dictionary.",
|
||||
"network": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "word",
|
||||
"description": "Word to describe."
|
||||
},
|
||||
{
|
||||
"name": "language",
|
||||
"description": "Language code."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool": "search_web",
|
||||
"description": "Search the web with given query.",
|
||||
"network": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"description": "Search query."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool": "get_webpage",
|
||||
"description": "Download web page at given URL.",
|
||||
"network": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "url",
|
||||
"description": "Web page to download."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool": "user_manual",
|
||||
"description": "Search the Tracy Profiler user manual with given query.",
|
||||
"local": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"description": "Verbose search query in English language."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tool": "source_file",
|
||||
"description": "Retrieve the source file contents.",
|
||||
"local": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "file",
|
||||
"description": "Path to the file."
|
||||
},
|
||||
{
|
||||
"name": "line",
|
||||
"description": "Line number that should be retrieved (as large files may be not available completely)."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Tools marked as `local` operate privately and are always safe to use. Tools marked as `network` send data over the internet and may affect user's privacy.
|
||||
|
||||
1. Never guess or make up facts. Your knowledge is outdated. Always use the tools you have to get the real info.
|
||||
2. It's important to protect the user's privacy and the privacy of their program.
|
||||
3. Use multiple tool calls to get the info you need. You can try to answer using your own knowledge only after all the relevant tools don't work. If so, be sure to let the user know that the response might be wrong because getting the data failed.
|
||||
4. Make sure you check all the tool's outputs for relevance to the user's query, then cross-reference the information across different outputs. And finally, see if it's consistent with your internal knowledge.
|
||||
5. Respond in the language the user is using.
|
||||
6. Don't go asking the user if you should move forward with getting more info — just go for it.
|
||||
|
||||
# Tool Usage and Knowledge Strategy:
|
||||
|
||||
1. *Source Priority:*
|
||||
- For questions related to Tracy Profiler always refer to the `user_manual`. Do not use this tool to research user's program.
|
||||
- If the user's question explicitly asks about source code in user's program (for example, a callstack provided as an attachment), use the `source_file` tool to retrieve the content of specified files.
|
||||
- For other factual queries, start by checking Wikipedia. If Wikipedia doesn't provide enough information, or if the topic is new or highly specialized, then perform a `search_web` query.
|
||||
2. *Internal Knowledge vs. Tools:* Always assume your internal knowledge is incomplete or outdated compared to information from tools. *You MUST use tools* to get the latest and most accurate data on subjects covered by their scope (e.g. facts likely on Wikipedia or the web). Output from previous tool invocations must be always considered.
|
||||
3. *Efficient Tool Use:* Before using a tool you MUST check if previous tool calls already contain the tool and parameters you want to call. If they do, you are forbidden from calling the tool a second time. You must use the tool output you already have.
|
||||
4. *Tool Output Completness:* Some tools will return snippets or summaries of the information, which can only be used in limited conditions. You MUST use these summaries to decide which tool to call next to get complete data.
|
||||
5. *Mandatory Content Retrieval:* Some tool outputs (e.g. `search_wikipedia` or `search_web`) provide only summaries or snippets. These are *never* sufficient for formulating a final answer. Their sole purpose is to identify the most promising page or URL. You MUST always follow a successful search with a corresponding tool call (e.g., `get_wikipedia` or `get_webpage`) to retrieve the full content before attempting to answer the user's query. Do not answer based on search snippets alone. The only exception is if the search returns no relevant results.
|
||||
1. Keep in mind that your own understanding might be a bit outdated compared to what you can find in the tools.
|
||||
2. If a tool gives you a preview of information, use it only to determine if the search result is worth pursuing. If it is, use a different tool to retrieve the full contents.
|
||||
3. If you're not getting the info you need from one tool, try another.
|
||||
4. Use as many tools as you need to get all the info you need.
|
||||
5. Keep the internal names of the tools you can use under wraps. Don't mention that you're using tools unless someone asks you about it.
|
||||
6. Always check if a loadable skill can be used for your task.
|
||||
7. If you have loaded a skill, you must carefully follow what it says.
|
||||
|
||||
# Context of operation
|
||||
|
||||
# Final Response to the User:
|
||||
You are "Tracy Assist" and operate in context of Tracy Profiler, a code performance profiler for games and other applications. You are talking with user named %USER%.
|
||||
|
||||
1. Once you have gathered all necessary information using the `<think>`, `<tool>`, and `<tool_output>` processing cycle, *generate the final response FOR THE USER.*
|
||||
2. This final response *MUST* be *OUTSIDE* of the `<think>` and `<tool>` tags.
|
||||
3. The user shouldn't know you are "using tools". Use a natural language, such as "the Wikipedia states that..." or "the web search results indicate that...". The user should not be aware of the tool usage process.
|
||||
4. Provide responses *strictly in the language the user used* in their query.
|
||||
The profiler uses a bunch of different methods to analyze (profile) user program's behavior and measure its performance characteristics. So, there are many types of questions the user might ask, and you need to correctly categorize each one to give the best answer possible.
|
||||
|
||||
- The user might have questions about Tracy Profiler. In this case, you should primarily focus on the `user_manual` tool, which has info about the profiler. When talking about certain terms in the profiler UI, stick with the original English names.
|
||||
- The user might want to ask about the program they're profiling. Your tools can give you access to that program's source code. The user program is probably private, so you should limit usage of tools using the network, as that may violate the privacy. Try to use more of your own experience and know-how here.
|
||||
- The user can also ask general questions that aren't related to the profiler or the program they're profiling. In this case, answer however you like and use any tool you think is necessary.
|
||||
|
||||
# Summary of Communication Structure:
|
||||
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
|
||||
|
||||
Each of your responses (or part of a response, if it requires a tool call) should start with a `<think>` block, followed by either a `<tool>` block (if further information is needed) or directly the final response to the user (if you have all information).
|
||||
# User's program
|
||||
|
||||
**Example Cycle (Not visible to the user):**
|
||||
User: "What is the capital of Poland?"
|
||||
Model:
|
||||
<think>
|
||||
The user is asking for the capital of Poland, in English language. This is a standard fact, but instructions say to use tools for facts. I should search Wikipedia. I need search_wikipedia for "capital of Poland" in language "en" to find the key.
|
||||
</think>
|
||||
<tool>
|
||||
{"tool": "search_wikipedia", "query": "capital of Poland", "language": "en"}
|
||||
</tool>
|
||||
The program being profiled is named %PROGRAMNAME%. It was built at %PROGRAMTIME%. The profiling session was performed at %PROFILETIME%, and the length of the session was %PROFILELENGTH%.%PROFILEDESCRIPTION%
|
||||
|
||||
System returns `<tool_output>` with the key for the article about Warsaw.
|
||||
Model (New Turn):
|
||||
<think>
|
||||
I retrieved the key for the capital of Poland article ("Warsaw"). Now I need to retrieve that article using get_wikipedia. The key is "Warsaw", language is "en".
|
||||
</think>
|
||||
<tool>
|
||||
{"tool": "get_wikipedia", "page": "Warsaw", "language": "en"}
|
||||
</tool>
|
||||
Here are instructions you must follow when you are asked to work with program the user is profiling.
|
||||
|
||||
System returns `<tool_output>` with the content of the Warsaw article, stating it is the capital.
|
||||
Model (New Turn):
|
||||
<think>
|
||||
I downloaded the content of the Warsaw article. It confirms that Warsaw is the capital of Poland. I have all the necessary information. I can provide the answer in user's language, which is English.
|
||||
</think>
|
||||
The capital of Poland is Warsaw.
|
||||
|
||||
*The user only sees:* "The capital of Poland is Warsaw."
|
||||
|
||||
|
||||
# Attachments
|
||||
## Attachments
|
||||
|
||||
The user may provide various types of attachments for you to process. These attachments come from the users's program. When you process *attachments* using *tools that access a network*, you must adhere to the following privacy protection rules. The rules *do not* apply in other circumstances, such as in conversation with the user, when using local tools, or when getting data for things unrelated to the user's program.
|
||||
|
||||
@@ -205,13 +45,20 @@ The user may provide various types of attachments for you to process. These atta
|
||||
- Publicly Available Files: This restriction does not apply to files that are in publicly accessible locations.
|
||||
- Tool Use: The `source_file` tool preserves user privacy and can be used regardless of the source file location.
|
||||
|
||||
## Using links
|
||||
|
||||
# Context of operation
|
||||
To provide a link to a location in a source file in the profiled program, use the standard markdown link format: "[<description>](source:<path>:<line>)". The "source:" string must appear exactly as it is. File path must be a full path.
|
||||
|
||||
You operate in context of Tracy Profiler, a C++ profiler for games and other applications. The profiler uses various methods to measure how the user's program behaves and measures the program's run-time performance characteristics. As such, there are various types of questions the user may ask you, and you must properly classify each question in order to give the best possible answer:
|
||||
To provide a link to user manual section, use the anchor point as the link destination: "[<description>](#anchor)".
|
||||
|
||||
- The user may ask you about things related to Tracy Profiler. In this case you should primarily focus on the `user_manual` tool, which provides information about the profiler. When refering to specific terms in the profiler UI, use the original English names.
|
||||
- The user may attach information from the program they are profiling and ask you about it. Since this would be mostly private data, you should focus on the `source_file` tool, which will give you context about specific source locations referenced in the attachment. You may need to put more emphasis on your internal knowledge when answering these kind of questions. Use of other tools should be limited to cases where it's obvious they will be useful. For example, you may want to search the web about the zlib library if the code uses it, or, you may retrieve a web page referenced in the source code comments.
|
||||
- The user may also ask general question not related either to the profiler or the program they are profiling. In this case answer freely, and use any tool you feel necessary.
|
||||
Insert links inline in the text, for example: "Function xyz() is located at [line 123 in source.c](source:/home/user/source.c:123)."
|
||||
|
||||
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
|
||||
Never write a link in an inline code block, like: `[description](link)`. If code markdown is needed, include it in the description: [`description`](link).
|
||||
|
||||
## Case specific operation
|
||||
|
||||
Specialized instructions and workflows for specific tasks are provided with the `skill` tool. If the task description matches the skill description, you must load the skill in question to gather the required abilities, *before* doing anything else.
|
||||
|
||||
Available skills:
|
||||
|
||||
%SKILLS%
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
Remember your core principles:
|
||||
1. Protect user's privacy.
|
||||
2. Always prioritize tools for factual information.
|
||||
3. Your internal knowledge is strictly secondary and a last resort.
|
||||
4. Respond strictly in the user's language.
|
||||
246
profiler/src/llm/tools.json
Normal file
246
profiler/src/llm/tools.json
Normal file
@@ -0,0 +1,246 @@
|
||||
[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "skill",
|
||||
"description": "Learn a specialized skill for the task needing specific knowledge.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the skill, chosen from the list of available skills."
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_wikipedia",
|
||||
"description": "Search the Wikipedia with given query. The `key` field in the response is the Wikipedia page name. Uses network.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "The search terms in the language matching the second parameter."
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Language code matching the search query. For example, `en` for English or `pl` for Polish."
|
||||
}
|
||||
},
|
||||
"required": ["query", "language"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_wikipedia",
|
||||
"description": "Retrieve the Wikipedia article on given subject. Uses network.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "string",
|
||||
"description": "The Wikipedia page name."
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Language code."
|
||||
}
|
||||
},
|
||||
"required": ["page", "language"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_dictionary",
|
||||
"description": "Retrieve description of a word from dictionary. Uses network.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"word": {
|
||||
"type": "string",
|
||||
"description": "Word to describe."
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Language code."
|
||||
}
|
||||
},
|
||||
"required": ["word", "language"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_web",
|
||||
"description": "Search the web with given query. Uses network.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search query."
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_webpage",
|
||||
"description": "Fetch contents of a web page at given URL. Uses network.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Web page to download."
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "user_manual",
|
||||
"description": "Search the Tracy Profiler user manual with given query.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Verbose search query in English language."
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "source_file",
|
||||
"description": "Retrieve the user program source file contents around given line.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string",
|
||||
"description": "Path to the file."
|
||||
},
|
||||
"line": {
|
||||
"type": "integer",
|
||||
"description": "Target line number."
|
||||
},
|
||||
"context": {
|
||||
"type": "integer",
|
||||
"description": "How many lines to get after the target line. Default: 2."
|
||||
},
|
||||
"context_back": {
|
||||
"type": "integer",
|
||||
"description": "How many lines to get before the target line. Default: 2."
|
||||
}
|
||||
},
|
||||
"required": ["file", "line"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "source_search",
|
||||
"description": "Search user program source files for given keyword.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "ECMAScript regex to search for."
|
||||
},
|
||||
"case_insensitive": {
|
||||
"type": "boolean",
|
||||
"description": "Case insensitive search."
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path ECMAScript regex."
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "symbol_disasm",
|
||||
"description": "Get disassembly and profiling data for a symbol.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "Symbol address, for example `0x12345678`."
|
||||
}
|
||||
},
|
||||
"required": ["address"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "symbol_parents",
|
||||
"description": "Get the top N entry call stacks for a given symbol address. Shows the call paths through which execution reached this function.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "Symbol address, for example `0x12345678`."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of call stacks to return. Default: 10."
|
||||
}
|
||||
},
|
||||
"required": ["address"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "sampling_stats",
|
||||
"description": "Get sampling statistics for functions, sorted by time spent in each.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "ECMAScript regex to search for."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of entries to return. Default: 30."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -39,11 +39,12 @@
|
||||
#include "profiler/TracyTexture.hpp"
|
||||
#include "profiler/TracyView.hpp"
|
||||
#include "profiler/TracyWeb.hpp"
|
||||
#include "profiler/IconsFontAwesome6.h"
|
||||
#include "profiler/IconsFontAwesome7.h"
|
||||
#include "../../server/tracy_pdqsort.h"
|
||||
#include "../../server/tracy_robin_hood.h"
|
||||
#include "../../server/TracyFileHeader.hpp"
|
||||
#include "../../server/TracyFileRead.hpp"
|
||||
#include "../../server/TracyBroadcast.hpp"
|
||||
#include "../../server/TracyPrint.hpp"
|
||||
#include "../../server/TracySysUtil.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
@@ -58,6 +59,7 @@
|
||||
|
||||
#include "Backend.hpp"
|
||||
#include "ConnectionHistory.hpp"
|
||||
#include "EmscriptenShim.hpp"
|
||||
#include "Filters.hpp"
|
||||
#include "Fonts.hpp"
|
||||
#include "HttpRequest.hpp"
|
||||
@@ -83,7 +85,7 @@ struct ClientData
|
||||
enum class ViewShutdown { False, True, Join };
|
||||
|
||||
static tracy::unordered_flat_map<uint64_t, ClientData> clients;
|
||||
static std::unique_ptr<tracy::View> view;
|
||||
static std::atomic<std::shared_ptr<tracy::View>> view;
|
||||
static tracy::BadVersionState badVer;
|
||||
static uint16_t port = 8086;
|
||||
static const char* connectTo = nullptr;
|
||||
@@ -122,6 +124,8 @@ tracy::AchievementsMgr* s_achievements;
|
||||
static const tracy::data::AchievementItem* s_achievementItem = nullptr;
|
||||
static bool s_switchAchievementCategory = false;
|
||||
|
||||
ImTextureID GetProfilerIconTexture() { return iconTex; }
|
||||
|
||||
static float smoothstep( float x )
|
||||
{
|
||||
return x * x * ( 3.0f - 2.0f * x );
|
||||
@@ -160,16 +164,16 @@ static void SetupDPIScale()
|
||||
{
|
||||
auto scale = dpiScale * tracy::s_config.userScale;
|
||||
|
||||
#ifdef __APPLE__
|
||||
scale = tracy::s_config.userScale;
|
||||
#endif
|
||||
|
||||
if( !dpiFirstSetup && prevScale == scale ) return;
|
||||
dpiFirstSetup = false;
|
||||
dpiChanged = 2;
|
||||
|
||||
LoadFonts( scale );
|
||||
|
||||
#ifdef __APPLE__
|
||||
scale = 1.0f;
|
||||
#endif
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
style = ImGuiStyle();
|
||||
ImGui::StyleColorsDark();
|
||||
@@ -199,7 +203,8 @@ static void SetupDPIScale()
|
||||
static int IsBusy()
|
||||
{
|
||||
if( loadThread.joinable() ) return 2;
|
||||
if( view && !view->IsBackgroundDone() ) return 1;
|
||||
auto ptr = view.load( std::memory_order_acquire );
|
||||
if( ptr && !ptr->IsBackgroundDone() ) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -231,7 +236,7 @@ int main( int argc, char** argv )
|
||||
{
|
||||
if( strcmp( argv[1], "--help" ) == 0 )
|
||||
{
|
||||
printf( "%s\n\n", title );
|
||||
printf( "%s / %s\n\n", title, tracy::GitRef );
|
||||
printf( "Usage:\n\n" );
|
||||
printf( " Open trace file stored on disk:\n" );
|
||||
printf( " %s file.tracy\n\n", argv[0] );
|
||||
@@ -348,12 +353,12 @@ int main( int argc, char** argv )
|
||||
|
||||
if( initFileOpen )
|
||||
{
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
initFileOpen.reset();
|
||||
}
|
||||
else if( connectTo )
|
||||
{
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
|
||||
tracy::Fileselector::Init();
|
||||
@@ -365,7 +370,7 @@ int main( int argc, char** argv )
|
||||
if( loadThread.joinable() ) loadThread.join();
|
||||
if( updateThread.joinable() ) updateThread.join();
|
||||
if( updateNotesThread.joinable() ) updateNotesThread.join();
|
||||
view.reset();
|
||||
view.store( nullptr, std::memory_order_release );
|
||||
|
||||
tracy::FreeTexture( zigzagTex, RunOnMainThread );
|
||||
tracy::FreeTexture( iconTex, RunOnMainThread );
|
||||
@@ -378,7 +383,8 @@ int main( int argc, char** argv )
|
||||
|
||||
static void UpdateBroadcastClients()
|
||||
{
|
||||
if( !view )
|
||||
auto ptr = view.load( std::memory_order_acquire );
|
||||
if( !ptr )
|
||||
{
|
||||
const auto time = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
|
||||
if( !broadcastListen )
|
||||
@@ -397,76 +403,15 @@ static void UpdateBroadcastClients()
|
||||
{
|
||||
auto msg = broadcastListen->Read( len, addr, 0 );
|
||||
if( !msg ) break;
|
||||
if( len > sizeof( tracy::BroadcastMessage ) ) continue;
|
||||
uint16_t broadcastVersion;
|
||||
memcpy( &broadcastVersion, msg, sizeof( uint16_t ) );
|
||||
if( broadcastVersion <= tracy::BroadcastVersion )
|
||||
auto parsed = tracy::ParseBroadcastMessage( msg, len );
|
||||
if( parsed.has_value() )
|
||||
{
|
||||
uint32_t protoVer;
|
||||
char procname[tracy::WelcomeMessageProgramNameSize];
|
||||
int32_t activeTime;
|
||||
uint16_t listenPort;
|
||||
uint64_t pid;
|
||||
|
||||
switch( broadcastVersion )
|
||||
{
|
||||
case 3:
|
||||
{
|
||||
tracy::BroadcastMessage bm;
|
||||
memcpy( &bm, msg, len );
|
||||
protoVer = bm.protocolVersion;
|
||||
strcpy( procname, bm.programName );
|
||||
activeTime = bm.activeTime;
|
||||
listenPort = bm.listenPort;
|
||||
pid = bm.pid;
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if( len > sizeof( tracy::BroadcastMessage_v2 ) ) continue;
|
||||
tracy::BroadcastMessage_v2 bm;
|
||||
memcpy( &bm, msg, len );
|
||||
protoVer = bm.protocolVersion;
|
||||
strcpy( procname, bm.programName );
|
||||
activeTime = bm.activeTime;
|
||||
listenPort = bm.listenPort;
|
||||
pid = 0;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
if( len > sizeof( tracy::BroadcastMessage_v1 ) ) continue;
|
||||
tracy::BroadcastMessage_v1 bm;
|
||||
memcpy( &bm, msg, len );
|
||||
protoVer = bm.protocolVersion;
|
||||
strcpy( procname, bm.programName );
|
||||
activeTime = bm.activeTime;
|
||||
listenPort = bm.listenPort;
|
||||
pid = 0;
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
{
|
||||
if( len > sizeof( tracy::BroadcastMessage_v0 ) ) continue;
|
||||
tracy::BroadcastMessage_v0 bm;
|
||||
memcpy( &bm, msg, len );
|
||||
protoVer = bm.protocolVersion;
|
||||
strcpy( procname, bm.programName );
|
||||
activeTime = bm.activeTime;
|
||||
listenPort = 8086;
|
||||
pid = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
|
||||
auto& pm = parsed.value();
|
||||
auto address = addr.GetText();
|
||||
const auto ipNumerical = addr.GetNumber();
|
||||
const auto clientId = uint64_t( ipNumerical ) | ( uint64_t( listenPort ) << 32 );
|
||||
const auto clientId = tracy::ClientUniqueID( addr, pm.listenPort );
|
||||
auto it = clients.find( clientId );
|
||||
if( activeTime >= 0 )
|
||||
if( pm.activeTime >= 0 )
|
||||
{
|
||||
if( it == clients.end() )
|
||||
{
|
||||
@@ -480,19 +425,19 @@ static void UpdateBroadcastClients()
|
||||
auto it = resolvMap.find( ip );
|
||||
assert( it != resolvMap.end() );
|
||||
std::swap( it->second, name );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
resolvLock.unlock();
|
||||
clients.emplace( clientId, ClientData { time, protoVer, activeTime, listenPort, pid, procname, std::move( ip ) } );
|
||||
clients.emplace( clientId, ClientData { time, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second.time = time;
|
||||
it->second.activeTime = activeTime;
|
||||
it->second.port = listenPort;
|
||||
it->second.pid = pid;
|
||||
it->second.protocolVersion = protoVer;
|
||||
if( strcmp( it->second.procName.c_str(), procname ) != 0 ) it->second.procName = procname;
|
||||
it->second.activeTime = pm.activeTime;
|
||||
it->second.port = pm.listenPort;
|
||||
it->second.pid = pm.pid;
|
||||
it->second.protocolVersion = pm.protocolVersion;
|
||||
if( strcmp( it->second.procName.c_str(), pm.programName ) != 0 ) it->second.procName = pm.programName;
|
||||
}
|
||||
}
|
||||
else if( it != clients.end() )
|
||||
@@ -587,7 +532,8 @@ static void DrawContents()
|
||||
const bool achievementsAttention = tracy::s_config.achievements ? s_achievements->NeedsAttention() : false;
|
||||
|
||||
static int activeFrames = 3;
|
||||
if( tracy::WasActive() || !clients.empty() || ( view && view->WasActive() ) || achievementsAttention )
|
||||
auto viewPtr = view.load( std::memory_order_acquire );
|
||||
if( tracy::WasActive() || !clients.empty() || ( viewPtr && viewPtr->WasActive() ) || achievementsAttention )
|
||||
{
|
||||
activeFrames = 3;
|
||||
}
|
||||
@@ -626,7 +572,7 @@ static void DrawContents()
|
||||
|
||||
setlocale( LC_NUMERIC, "C" );
|
||||
|
||||
if( !view )
|
||||
if( !viewPtr )
|
||||
{
|
||||
if( s_customTitle )
|
||||
{
|
||||
@@ -863,7 +809,7 @@ static void DrawContents()
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if( ImGui::Button( ICON_FA_COMMENT " Chat" ) )
|
||||
if( ImGui::Button( ICON_FA_COMMENTS " Chat" ) )
|
||||
{
|
||||
tracy::OpenWebpage( "https://discord.gg/pk78auc" );
|
||||
}
|
||||
@@ -962,11 +908,11 @@ static void DrawContents()
|
||||
{
|
||||
std::string addrPart = std::string( adata, ptr );
|
||||
uint16_t portPart = (uint16_t)atoi( ptr+1 );
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
else
|
||||
{
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, address.c_str(), port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, address.c_str(), port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -990,7 +936,7 @@ static void DrawContents()
|
||||
loadThread = std::thread( [f] {
|
||||
try
|
||||
{
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
catch( const tracy::UnsupportedVersion& e )
|
||||
{
|
||||
@@ -1123,7 +1069,7 @@ static void DrawContents()
|
||||
}
|
||||
if( selected && !loadThread.joinable() )
|
||||
{
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
const auto acttime = ( v.second.activeTime + ( time - v.second.time ) / 1000 ) * 1000000000ll;
|
||||
@@ -1169,8 +1115,8 @@ static void DrawContents()
|
||||
{
|
||||
static float rnTime = 0;
|
||||
rnTime += ImGui::GetIO().DeltaTime;
|
||||
tracy::TextCentered( "Fetching release notes..." );
|
||||
tracy::DrawWaitingDots( rnTime );
|
||||
tracy::TextCentered( "Fetching release notes…" );
|
||||
tracy::DrawWaitingDotsCentered( rnTime );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1191,17 +1137,19 @@ static void DrawContents()
|
||||
clients.clear();
|
||||
}
|
||||
if( loadThread.joinable() ) loadThread.join();
|
||||
view->NotifyRootWindowSize( display_w, display_h );
|
||||
if( !view->Draw() )
|
||||
viewPtr->NotifyRootWindowSize( display_w, display_h );
|
||||
if( !viewPtr->Draw() )
|
||||
{
|
||||
viewShutdown.store( ViewShutdown::True, std::memory_order_relaxed );
|
||||
reconnect = view->ReconnectRequested();
|
||||
reconnect = viewPtr->ReconnectRequested();
|
||||
if( reconnect )
|
||||
{
|
||||
reconnectAddr = view->GetAddress();
|
||||
reconnectPort = view->GetPort();
|
||||
reconnectAddr = viewPtr->GetAddress();
|
||||
reconnectPort = viewPtr->GetPort();
|
||||
}
|
||||
loadThread = std::thread( [view = std::move( view )] () mutable {
|
||||
|
||||
view.store( nullptr, std::memory_order_release );
|
||||
loadThread = std::thread( [view = std::move( viewPtr )] () mutable {
|
||||
view.reset();
|
||||
viewShutdown.store( ViewShutdown::Join, std::memory_order_relaxed );
|
||||
} );
|
||||
@@ -1211,9 +1159,9 @@ static void DrawContents()
|
||||
auto totalProgress = progress.total.load( std::memory_order_relaxed );
|
||||
if( totalProgress != 0 )
|
||||
{
|
||||
ImGui::OpenPopup( "Loading trace..." );
|
||||
ImGui::OpenPopup( "Loading trace…" );
|
||||
}
|
||||
if( ImGui::BeginPopupModal( "Loading trace...", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings) )
|
||||
if( ImGui::BeginPopupModal( "Loading trace…", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings) )
|
||||
{
|
||||
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
|
||||
ImGui::Spacing();
|
||||
@@ -1222,7 +1170,7 @@ static void DrawContents()
|
||||
ImGui::PopFont();
|
||||
|
||||
animTime += ImGui::GetIO().DeltaTime;
|
||||
tracy::DrawWaitingDots( animTime );
|
||||
tracy::DrawWaitingDotsCentered( animTime );
|
||||
|
||||
auto currProgress = progress.progress.load( std::memory_order_relaxed );
|
||||
if( totalProgress == 0 )
|
||||
@@ -1233,37 +1181,37 @@ static void DrawContents()
|
||||
switch( currProgress )
|
||||
{
|
||||
case tracy::LoadProgress::Initialization:
|
||||
ImGui::TextUnformatted( "Initialization..." );
|
||||
ImGui::TextUnformatted( "Initialization…" );
|
||||
break;
|
||||
case tracy::LoadProgress::Locks:
|
||||
ImGui::TextUnformatted( "Locks..." );
|
||||
ImGui::TextUnformatted( "Locks…" );
|
||||
break;
|
||||
case tracy::LoadProgress::Messages:
|
||||
ImGui::TextUnformatted( "Messages..." );
|
||||
ImGui::TextUnformatted( "Messages…" );
|
||||
break;
|
||||
case tracy::LoadProgress::Zones:
|
||||
ImGui::TextUnformatted( "CPU zones..." );
|
||||
ImGui::TextUnformatted( "CPU zones…" );
|
||||
break;
|
||||
case tracy::LoadProgress::GpuZones:
|
||||
ImGui::TextUnformatted( "GPU zones..." );
|
||||
ImGui::TextUnformatted( "GPU zones…" );
|
||||
break;
|
||||
case tracy::LoadProgress::Plots:
|
||||
ImGui::TextUnformatted( "Plots..." );
|
||||
ImGui::TextUnformatted( "Plots…" );
|
||||
break;
|
||||
case tracy::LoadProgress::Memory:
|
||||
ImGui::TextUnformatted( "Memory..." );
|
||||
ImGui::TextUnformatted( "Memory…" );
|
||||
break;
|
||||
case tracy::LoadProgress::CallStacks:
|
||||
ImGui::TextUnformatted( "Call stacks..." );
|
||||
ImGui::TextUnformatted( "Call stacks…" );
|
||||
break;
|
||||
case tracy::LoadProgress::FrameImages:
|
||||
ImGui::TextUnformatted( "Frame images..." );
|
||||
ImGui::TextUnformatted( "Frame images…" );
|
||||
break;
|
||||
case tracy::LoadProgress::ContextSwitches:
|
||||
ImGui::TextUnformatted( "Context switches..." );
|
||||
ImGui::TextUnformatted( "Context switches…" );
|
||||
break;
|
||||
case tracy::LoadProgress::ContextSwitchesPerCpu:
|
||||
ImGui::TextUnformatted( "CPU context switches..." );
|
||||
ImGui::TextUnformatted( "CPU context switches…" );
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
@@ -1271,7 +1219,7 @@ static void DrawContents()
|
||||
}
|
||||
ImGui::ProgressBar( float( currProgress ) / totalProgress, ImVec2( 200 * dpiScale, 0 ) );
|
||||
|
||||
ImGui::TextUnformatted( "Progress..." );
|
||||
ImGui::TextUnformatted( "Progress…" );
|
||||
auto subTotal = progress.subTotal.load( std::memory_order_relaxed );
|
||||
auto subProgress = progress.subProgress.load( std::memory_order_relaxed );
|
||||
if( subTotal == 0 )
|
||||
@@ -1287,20 +1235,20 @@ static void DrawContents()
|
||||
switch( viewShutdown.load( std::memory_order_relaxed ) )
|
||||
{
|
||||
case ViewShutdown::True:
|
||||
ImGui::OpenPopup( "Capture cleanup..." );
|
||||
ImGui::OpenPopup( "Capture cleanup…" );
|
||||
break;
|
||||
case ViewShutdown::Join:
|
||||
loadThread.join();
|
||||
viewShutdown.store( ViewShutdown::False, std::memory_order_relaxed );
|
||||
if( reconnect )
|
||||
{
|
||||
view = std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
|
||||
view.store( std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if( ImGui::BeginPopupModal( "Capture cleanup...", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
|
||||
if( ImGui::BeginPopupModal( "Capture cleanup…", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
|
||||
{
|
||||
if( viewShutdown.load( std::memory_order_relaxed ) != ViewShutdown::True ) ImGui::CloseCurrentPopup();
|
||||
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
|
||||
@@ -1309,7 +1257,7 @@ static void DrawContents()
|
||||
ImGui::Spacing();
|
||||
ImGui::PopFont();
|
||||
animTime += ImGui::GetIO().DeltaTime;
|
||||
tracy::DrawWaitingDots( animTime );
|
||||
tracy::DrawWaitingDotsCentered( animTime );
|
||||
ImGui::TextUnformatted( "Please wait, cleanup is in progress" );
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -1345,7 +1293,7 @@ The *Achievements* system will guide you through the main features and teach you
|
||||
Would you like to enable achievements?
|
||||
)";
|
||||
|
||||
tracy::Markdown md;
|
||||
tracy::Markdown md( nullptr, nullptr );
|
||||
md.Print( text, strlen( text ) );
|
||||
ImGui::Spacing();
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
@@ -1518,9 +1466,17 @@ Would you like to enable achievements?
|
||||
{
|
||||
ImGui::Columns( 2 );
|
||||
ImGui::SetColumnWidth( 0, 300 * dpiScale );
|
||||
ImGui::BeginChild( "##achievementtoc", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
|
||||
DrawAchievements( c->items );
|
||||
ImGui::EndChild();
|
||||
ImGui::NextColumn();
|
||||
if( s_achievementItem ) s_achievementItem->description();
|
||||
ImGui::BeginChild( "##achievementtext", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
|
||||
if( s_achievementItem )
|
||||
{
|
||||
tracy::Markdown md( nullptr, nullptr );
|
||||
md.Print( s_achievementItem->text.c_str(), s_achievementItem->text.size() );
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndColumns();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++
|
||||
// from https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml
|
||||
// for use with https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf
|
||||
// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py
|
||||
// for C and C++
|
||||
// from codepoints https://github.com/FortAwesome/Font-Awesome/raw/7.x/metadata/icons.yml
|
||||
// for use with font https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-regular-400.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.), https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-solid-900.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.)
|
||||
|
||||
#pragma once
|
||||
|
||||
#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf"
|
||||
#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf"
|
||||
#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.woff2"
|
||||
#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.woff2"
|
||||
|
||||
#define ICON_MIN_FA 0xe005
|
||||
#define ICON_MAX_16_FA 0xf8ff
|
||||
#define ICON_MAX_FA 0xf8ff
|
||||
|
||||
#define ICON_FA_0 "0" // U+0030
|
||||
#define ICON_FA_1 "1" // U+0031
|
||||
#define ICON_FA_2 "2" // U+0032
|
||||
@@ -22,6 +25,7 @@
|
||||
#define ICON_FA_A "A" // U+0041
|
||||
#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9
|
||||
#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb
|
||||
#define ICON_FA_ALARM_CLOCK "\xef\x8d\x8e" // U+f34e
|
||||
#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037
|
||||
#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039
|
||||
#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036
|
||||
@@ -41,7 +45,9 @@
|
||||
#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102
|
||||
#define ICON_FA_ANKH "\xef\x99\x84" // U+f644
|
||||
#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1
|
||||
#define ICON_FA_AQUARIUS "\xee\xa1\x85" // U+e845
|
||||
#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557
|
||||
#define ICON_FA_ARIES "\xee\xa1\x86" // U+e846
|
||||
#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063
|
||||
#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162
|
||||
#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886
|
||||
@@ -116,6 +122,7 @@
|
||||
#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e
|
||||
#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d
|
||||
#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462
|
||||
#define ICON_FA_BANGLADESHI_TAKA_SIGN "\xee\x8b\xa6" // U+e2e6
|
||||
#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a
|
||||
#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9
|
||||
#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828
|
||||
@@ -214,6 +221,7 @@
|
||||
#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805
|
||||
#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc
|
||||
#define ICON_FA_BUS "\xef\x88\x87" // U+f207
|
||||
#define ICON_FA_BUS_SIDE "\xee\xa0\x9d" // U+e81d
|
||||
#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e
|
||||
#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a
|
||||
#define ICON_FA_C "C" // U+0043
|
||||
@@ -232,8 +240,10 @@
|
||||
#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083
|
||||
#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8
|
||||
#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb
|
||||
#define ICON_FA_CANCER "\xee\xa1\x87" // U+e847
|
||||
#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786
|
||||
#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f
|
||||
#define ICON_FA_CAPRICORN "\xee\xa1\x88" // U+e848
|
||||
#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b
|
||||
#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9
|
||||
#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df
|
||||
@@ -266,6 +276,7 @@
|
||||
#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe
|
||||
#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080
|
||||
#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3
|
||||
#define ICON_FA_CHART_DIAGRAM "\xee\x9a\x95" // U+e695
|
||||
#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4
|
||||
#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201
|
||||
#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200
|
||||
@@ -287,9 +298,9 @@
|
||||
#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054
|
||||
#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077
|
||||
#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae
|
||||
#define ICON_FA_CHILD_COMBATANT "\xee\x93\xa0" // U+e4e0
|
||||
#define ICON_FA_CHILD_DRESS "\xee\x96\x9c" // U+e59c
|
||||
#define ICON_FA_CHILD_REACHING "\xee\x96\x9d" // U+e59d
|
||||
#define ICON_FA_CHILD_RIFLE "\xee\x93\xa0" // U+e4e0
|
||||
#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1
|
||||
#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d
|
||||
#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111
|
||||
@@ -334,6 +345,7 @@
|
||||
#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da
|
||||
#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d
|
||||
#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a
|
||||
#define ICON_FA_CLOSED_CAPTIONING_SLASH "\xee\x84\xb5" // U+e135
|
||||
#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2
|
||||
#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed
|
||||
#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee
|
||||
@@ -360,6 +372,7 @@
|
||||
#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651
|
||||
#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad
|
||||
#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5
|
||||
#define ICON_FA_COMMENT_NODES "\xee\x9a\x96" // U+e696
|
||||
#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3
|
||||
#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd
|
||||
#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086
|
||||
@@ -522,6 +535,8 @@
|
||||
#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd
|
||||
#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3
|
||||
#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e
|
||||
#define ICON_FA_FILE_FRAGMENT "\xee\x9a\x97" // U+e697
|
||||
#define ICON_FA_FILE_HALF_DASHED "\xee\x9a\x98" // U+e698
|
||||
#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5
|
||||
#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f
|
||||
#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570
|
||||
@@ -585,6 +600,7 @@
|
||||
#define ICON_FA_GEAR "\xef\x80\x93" // U+f013
|
||||
#define ICON_FA_GEARS "\xef\x82\x85" // U+f085
|
||||
#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5
|
||||
#define ICON_FA_GEMINI "\xee\xa1\x89" // U+e849
|
||||
#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d
|
||||
#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2
|
||||
#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b
|
||||
@@ -642,8 +658,6 @@
|
||||
#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684
|
||||
#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5
|
||||
#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4
|
||||
#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6
|
||||
#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f
|
||||
#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060
|
||||
#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6
|
||||
#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0
|
||||
@@ -657,7 +671,6 @@
|
||||
#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064
|
||||
#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc
|
||||
#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025
|
||||
#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f
|
||||
#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590
|
||||
#define ICON_FA_HEART "\xef\x80\x84" // U+f004
|
||||
#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc
|
||||
@@ -672,6 +685,9 @@
|
||||
#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502
|
||||
#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807
|
||||
#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503
|
||||
#define ICON_FA_HEXAGON "\xef\x8c\x92" // U+f312
|
||||
#define ICON_FA_HEXAGON_NODES "\xee\x9a\x99" // U+e699
|
||||
#define ICON_FA_HEXAGON_NODES_BOLT "\xee\x9a\x9a" // U+e69a
|
||||
#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591
|
||||
#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507
|
||||
#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508
|
||||
@@ -767,8 +783,10 @@
|
||||
#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a
|
||||
#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337
|
||||
#define ICON_FA_LEMON "\xef\x82\x94" // U+f094
|
||||
#define ICON_FA_LEO "\xee\xa1\x8a" // U+e84a
|
||||
#define ICON_FA_LESS_THAN "<" // U+003c
|
||||
#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537
|
||||
#define ICON_FA_LIBRA "\xee\xa1\x8b" // U+e84b
|
||||
#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd
|
||||
#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb
|
||||
#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e
|
||||
@@ -842,6 +860,7 @@
|
||||
#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527
|
||||
#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf
|
||||
#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd
|
||||
#define ICON_FA_MOBILE_VIBRATE "\xee\xa0\x96" // U+e816
|
||||
#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6
|
||||
#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1
|
||||
#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b
|
||||
@@ -871,6 +890,7 @@
|
||||
#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff
|
||||
#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c
|
||||
#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea
|
||||
#define ICON_FA_NON_BINARY "\xee\xa0\x87" // U+e807
|
||||
#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e
|
||||
#define ICON_FA_NOTDEF "\xee\x87\xbe" // U+e1fe
|
||||
#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249
|
||||
@@ -878,6 +898,7 @@
|
||||
#define ICON_FA_O "O" // U+004f
|
||||
#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247
|
||||
#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248
|
||||
#define ICON_FA_OCTAGON "\xef\x8c\x86" // U+f306
|
||||
#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613
|
||||
#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532
|
||||
#define ICON_FA_OM "\xef\x99\xb9" // U+f679
|
||||
@@ -906,6 +927,7 @@
|
||||
#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae
|
||||
#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044
|
||||
#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303
|
||||
#define ICON_FA_PENTAGON "\xee\x9e\x90" // U+e790
|
||||
#define ICON_FA_PEOPLE_ARROWS "\xee\x81\xa8" // U+e068
|
||||
#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce
|
||||
#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533
|
||||
@@ -968,8 +990,10 @@
|
||||
#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd
|
||||
#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0
|
||||
#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c
|
||||
#define ICON_FA_PICTURE_IN_PICTURE "\xee\xa0\x8b" // U+e80b
|
||||
#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3
|
||||
#define ICON_FA_PILLS "\xef\x92\x84" // U+f484
|
||||
#define ICON_FA_PISCES "\xee\xa1\x8c" // U+e84c
|
||||
#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818
|
||||
#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f
|
||||
#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072
|
||||
@@ -1060,6 +1084,7 @@
|
||||
#define ICON_FA_S "S" // U+0053
|
||||
#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d
|
||||
#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a
|
||||
#define ICON_FA_SAGITTARIUS "\xee\xa1\x8d" // U+e84d
|
||||
#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445
|
||||
#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf
|
||||
#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0
|
||||
@@ -1073,6 +1098,7 @@
|
||||
#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e
|
||||
#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f
|
||||
#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4
|
||||
#define ICON_FA_SCORPIO "\xee\xa1\x8e" // U+e84e
|
||||
#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a
|
||||
#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9
|
||||
#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e
|
||||
@@ -1080,6 +1106,7 @@
|
||||
#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2
|
||||
#define ICON_FA_SECTION "\xee\x91\x87" // U+e447
|
||||
#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8
|
||||
#define ICON_FA_SEPTAGON "\xee\xa0\xa0" // U+e820
|
||||
#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233
|
||||
#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f
|
||||
#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064
|
||||
@@ -1108,6 +1135,8 @@
|
||||
#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7
|
||||
#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277
|
||||
#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4
|
||||
#define ICON_FA_SINGLE_QUOTE_LEFT "\xee\xa0\x9b" // U+e81b
|
||||
#define ICON_FA_SINGLE_QUOTE_RIGHT "\xee\xa0\x9c" // U+e81c
|
||||
#define ICON_FA_SINK "\xee\x81\xad" // U+e06d
|
||||
#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8
|
||||
#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c
|
||||
@@ -1131,12 +1160,14 @@
|
||||
#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891
|
||||
#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717
|
||||
#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110
|
||||
#define ICON_FA_SPIRAL "\xee\xa0\x8a" // U+e80a
|
||||
#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc
|
||||
#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5
|
||||
#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd
|
||||
#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0
|
||||
#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8
|
||||
#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c
|
||||
#define ICON_FA_SQUARE_BINARY "\xee\x9a\x9b" // U+e69b
|
||||
#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150
|
||||
#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191
|
||||
#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152
|
||||
@@ -1194,7 +1225,10 @@
|
||||
#define ICON_FA_T "T" // U+0054
|
||||
#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce
|
||||
#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a
|
||||
#define ICON_FA_TABLE_CELLS_COLUMN_LOCK "\xee\x99\xb8" // U+e678
|
||||
#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009
|
||||
#define ICON_FA_TABLE_CELLS_ROW_LOCK "\xee\x99\xba" // U+e67a
|
||||
#define ICON_FA_TABLE_CELLS_ROW_UNLOCK "\xee\x9a\x91" // U+e691
|
||||
#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db
|
||||
#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b
|
||||
#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d
|
||||
@@ -1208,6 +1242,7 @@
|
||||
#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db
|
||||
#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b
|
||||
#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c
|
||||
#define ICON_FA_TAURUS "\xee\xa1\x8f" // U+e84f
|
||||
#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba
|
||||
#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e
|
||||
#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f
|
||||
@@ -1235,6 +1270,7 @@
|
||||
#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165
|
||||
#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164
|
||||
#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d
|
||||
#define ICON_FA_THUMBTACK_SLASH "\xee\x9a\x8f" // U+e68f
|
||||
#define ICON_FA_TICKET "\xef\x85\x85" // U+f145
|
||||
#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff
|
||||
#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c
|
||||
@@ -1310,8 +1346,6 @@
|
||||
#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501
|
||||
#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500
|
||||
#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728
|
||||
#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406
|
||||
#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa
|
||||
#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502
|
||||
#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503
|
||||
#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504
|
||||
@@ -1336,7 +1370,6 @@
|
||||
#define ICON_FA_V "V" // U+0056
|
||||
#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6
|
||||
#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5
|
||||
#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb
|
||||
#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221
|
||||
#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226
|
||||
#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228
|
||||
@@ -1349,6 +1382,7 @@
|
||||
#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d
|
||||
#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2
|
||||
#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7
|
||||
#define ICON_FA_VIRGO "\xee\xa1\x90" // U+e850
|
||||
#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074
|
||||
#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8
|
||||
#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9
|
||||
@@ -1357,6 +1391,7 @@
|
||||
#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897
|
||||
#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770
|
||||
#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f
|
||||
#define ICON_FA_VOLUME "\xef\x9a\xa8" // U+f6a8
|
||||
#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028
|
||||
#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027
|
||||
#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026
|
||||
@@ -1372,6 +1407,7 @@
|
||||
#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773
|
||||
#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5
|
||||
#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e
|
||||
#define ICON_FA_WEB_AWESOME "\xee\x9a\x82" // U+e682
|
||||
#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd
|
||||
#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496
|
||||
#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd
|
||||
@@ -1,52 +1,60 @@
|
||||
#include "IconsFontAwesome6.h"
|
||||
#include "TracyAchievements.hpp"
|
||||
#include "TracyImGui.hpp"
|
||||
#include "TracySourceContents.hpp"
|
||||
#include "TracyWeb.hpp"
|
||||
#include "../Fonts.hpp"
|
||||
#include "TracyEmbed.hpp"
|
||||
|
||||
#include "data/Text100Million.hpp"
|
||||
#include "data/TextConnectToClient.hpp"
|
||||
#include "data/TextFindZone.hpp"
|
||||
#include "data/TextFrameImages.hpp"
|
||||
#include "data/TextGlobalSettings.hpp"
|
||||
#include "data/TextInstrumentFrames.hpp"
|
||||
#include "data/TextInstrumentationIntro.hpp"
|
||||
#include "data/TextInstrumentationStatistics.hpp"
|
||||
#include "data/TextIntro.hpp"
|
||||
#include "data/TextLoadTrace.hpp"
|
||||
#include "data/TextSamplingIntro.hpp"
|
||||
#include "data/TextSaveTrace.hpp"
|
||||
|
||||
namespace tracy::data
|
||||
{
|
||||
|
||||
AchievementItem ai_samplingIntro = { "samplingIntro", "Sampling program execution", [](){
|
||||
ImGui::TextWrapped( "Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often." );
|
||||
ImGui::TextWrapped( "While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return." );
|
||||
ImGui::TextWrapped( "Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work." );
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
|
||||
ImGui::TextWrapped( "Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information." );
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
} };
|
||||
static std::string UnpackImpl( size_t size, size_t lz4Size, const uint8_t* data )
|
||||
{
|
||||
std::string ret;
|
||||
const EmbedData unembed( size, lz4Size, data );
|
||||
ret.assign( unembed.data(), unembed.size() );
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define Unpack( name ) UnpackImpl( Embed::name##Size, Embed::name##Lz4Size, Embed::name##Data )
|
||||
|
||||
|
||||
AchievementItem ai_samplingIntro = {
|
||||
.id = "samplingIntro",
|
||||
.name = "Sampling program execution",
|
||||
.text = Unpack( TextSamplingIntro ),
|
||||
};
|
||||
|
||||
AchievementItem* ac_samplingItems[] = { &ai_samplingIntro, nullptr };
|
||||
AchievementCategory ac_sampling = { "sampling", "Sampling", ac_samplingItems };
|
||||
|
||||
|
||||
AchievementItem ai_100million = { "100million", "It's over 100 million!", [](){
|
||||
ImGui::TextWrapped( "Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!" );
|
||||
ImGui::TextWrapped( "Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it." );
|
||||
ImGui::TextWrapped( "Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case." );
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
|
||||
ImGui::TextWrapped( "Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory." );
|
||||
ImGui::TextWrapped( "To capture 100 million zones, you will need approximately 4 GB of RAM." );
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
} };
|
||||
AchievementItem ai_100million = {
|
||||
.id = "100million",
|
||||
.name = "It's over 100 million!",
|
||||
.text = Unpack( Text100Million )
|
||||
};
|
||||
|
||||
AchievementItem ai_instrumentationStatistics = { "instrumentationStatistics", "Show me the stats!", [](){
|
||||
ImGui::TextWrapped( "Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called." );
|
||||
ImGui::TextWrapped( "To view the statistics, click on the \"" ICON_FA_ARROW_UP_WIDE_SHORT " Statistics\" button on the top bar. This will open a new window with a list of all zones in the trace." );
|
||||
} };
|
||||
AchievementItem ai_instrumentationStatistics = {
|
||||
.id = "instrumentationStatistics",
|
||||
.name = "Show me the stats!",
|
||||
.text = Unpack( TextInstrumentationStatistics )
|
||||
};
|
||||
|
||||
AchievementItem ai_findZone = { "findZone", "Find some zones", [](){
|
||||
ImGui::TextWrapped( "You can search for zones in the trace by opening the search window with the \"" ICON_FA_MAGNIFYING_GLASS " Find zone\" button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code." );
|
||||
ImGui::TextWrapped( "The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them." );
|
||||
ImGui::TextWrapped( "Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone." );
|
||||
ImGui::TextWrapped( "When a zone is selected, a number of statistics are displayed to help you understand the performance of your application. In addition, a histogram of the zone execution times is displayed to make it easier for you to determine the performance of the profiled code. Be sure to select a zone with a large number of calls to make the histogram look interesting!" );
|
||||
ImGui::TextWrapped( "Note that you can draw a range on the histogram to limit the number of entries displayed in the zone list below. This list allows you to examine each zone individually. There are also a number of zone groupings that you can select. Each group can be selected and the time associated with the selected group will be highlighted on the histogram." );
|
||||
} };
|
||||
AchievementItem ai_findZone = {
|
||||
.id = "findZone",
|
||||
.name = "Find some zones",
|
||||
.text = Unpack( TextFindZone )
|
||||
};
|
||||
|
||||
AchievementItem* ac_instrumentationIntroItems[] = {
|
||||
&ai_100million,
|
||||
@@ -55,90 +63,46 @@ AchievementItem* ac_instrumentationIntroItems[] = {
|
||||
nullptr
|
||||
};
|
||||
|
||||
AchievementItem ai_instrumentationIntro = { "instrumentationIntro", "Instrumentating your application", [](){
|
||||
constexpr const char* src = R"(#include "Tracy.hpp"
|
||||
AchievementItem ai_instrumentationIntro = {
|
||||
.id = "instrumentationIntro",
|
||||
.name = "Instrumentating your application",
|
||||
.text = Unpack( TextInstrumentationIntro ),
|
||||
.items = ac_instrumentationIntroItems
|
||||
};
|
||||
|
||||
void SomeFunction()
|
||||
{
|
||||
ZoneScoped;
|
||||
// Your code here
|
||||
}
|
||||
)";
|
||||
|
||||
static SourceContents sc;
|
||||
sc.Parse( src );
|
||||
|
||||
ImGui::TextWrapped( "Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up." );
|
||||
ImGui::TextWrapped( "To get started, open a source file and include the Tracy.hpp header. This will give you access to a variety of macros provided by Tracy. Next, add the ZoneScoped macro to the beginning of one of your functions, like this:" );
|
||||
ImGui::PushFont( g_fonts.mono, FontNormal );
|
||||
PrintSource( sc.get() );
|
||||
ImGui::PopFont();
|
||||
ImGui::TextWrapped( "Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called." );
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
|
||||
ImGui::TextWrapped( "Note: The ZoneScoped macro is just one of the many macros provided by Tracy. See the documentation for more information." );
|
||||
ImGui::TextWrapped( "The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information." );
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
}, ac_instrumentationIntroItems };
|
||||
|
||||
AchievementItem ai_frameImages = { "frameImages", "A picture is worth a thousand words", [](){
|
||||
ImGui::TextWrapped( "Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the FrameImage macro." );
|
||||
ImGui::TextWrapped( "You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way." );
|
||||
ImGui::TextWrapped( "The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen." );
|
||||
ImGui::TextWrapped( "You can even view a recording of what your application was doing by clicking the " ICON_FA_SCREWDRIVER_WRENCH " icon and then selecting the \"" ICON_FA_PLAY " Playback\" option. Try it out!" );
|
||||
ImGui::TextWrapped( "The FrameImage macro is a great way to see what happened in your application at a particular time. Maybe you have a performance problem that only occurs when a certain object is on the screen?" );
|
||||
} };
|
||||
AchievementItem ai_frameImages = {
|
||||
.id = "frameImages",
|
||||
.name = "A picture is worth a thousand words",
|
||||
.text = Unpack( TextFrameImages )
|
||||
};
|
||||
|
||||
AchievementItem* ac_instrumentFramesItems[] = {
|
||||
&ai_frameImages,
|
||||
nullptr
|
||||
};
|
||||
|
||||
AchievementItem ai_instrumentFrames = { "instrumentFrames", "Instrumenting frames", [](){
|
||||
constexpr const char* src = R"(#include "Tracy.hpp"
|
||||
|
||||
void Render()
|
||||
{
|
||||
// Render the frame
|
||||
SwapBuffers();
|
||||
FrameMark;
|
||||
}
|
||||
)";
|
||||
|
||||
static SourceContents sc;
|
||||
sc.Parse( src );
|
||||
|
||||
ImGui::TextWrapped( "In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application." );
|
||||
ImGui::TextWrapped( "To instrument frames, you need to add the FrameMark macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame." );
|
||||
ImGui::PushFont( g_fonts.mono, FontNormal );
|
||||
PrintSource( sc.get() );
|
||||
ImGui::PopFont();
|
||||
ImGui::TextWrapped( "When you profile your application, you will see a new frame appear on the timeline each time the FrameMark macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second." );
|
||||
ImGui::TextWrapped( "The FrameMark macro is a great way to see at a glance how your application is performing over time. Maybe there are some performance problems that only appear after a few minutes of running the application? A frame graph is drawn at the top of the profiler window where you can see the timing of all frames." );
|
||||
ImGui::TextWrapped( "Note that some applications do not have a frame-based structure, and in such cases, frame instrumentation may not be useful. That's ok." );
|
||||
}, ac_instrumentFramesItems };
|
||||
AchievementItem ai_instrumentFrames = {
|
||||
.id = "instrumentFrames",
|
||||
.name = "Instrumenting frames",
|
||||
.text = Unpack( TextInstrumentFrames ),
|
||||
.items = ac_instrumentFramesItems
|
||||
};
|
||||
|
||||
AchievementItem* ac_instrumentationItems[] = { &ai_instrumentationIntro, &ai_instrumentFrames, nullptr };
|
||||
AchievementCategory ac_instrumentation = { "instrumentation", "Instrumentation", ac_instrumentationItems };
|
||||
|
||||
|
||||
AchievementItem ai_loadTrace = { "loadTrace", "Load a trace", [](){
|
||||
ImGui::TextWrapped( "You can open a previously saved trace file (or one received from a friend) with the \"" ICON_FA_FOLDER_OPEN " Open saved trace\" button on the welcome screen." );
|
||||
} };
|
||||
AchievementItem ai_loadTrace = {
|
||||
.id = "loadTrace",
|
||||
.name = "Load a trace",
|
||||
.text = Unpack( TextLoadTrace )
|
||||
};
|
||||
|
||||
AchievementItem ai_saveTrace = { "saveTrace", "Save a trace", [](){
|
||||
ImGui::TextWrapped( "Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the " ICON_FA_WIFI " icon in the top left corner of the screen and then clicking on the \"" ICON_FA_FLOPPY_DISK " Save trace\" button." );
|
||||
ImGui::TextWrapped( "Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before." );
|
||||
ImGui::TextWrapped( "You can also share the trace with your friends or co-workers by sending them the trace file." );
|
||||
ImGui::Spacing();
|
||||
tracy::TextColoredUnformatted( 0xFF44FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted( "Warning" );
|
||||
ImGui::SameLine();
|
||||
tracy::TextColoredUnformatted( 0xFF44FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
|
||||
ImGui::TextWrapped( "Trace files can contain sensitive information about your application, such as program code, or even the contents of source files. Be careful when sharing them with others." );
|
||||
} };
|
||||
AchievementItem ai_saveTrace = {
|
||||
.id = "saveTrace",
|
||||
.name = "Save a trace",
|
||||
.text = Unpack( TextSaveTrace )
|
||||
};
|
||||
|
||||
AchievementItem* ac_connectToServerItems[] = {
|
||||
&ai_saveTrace,
|
||||
@@ -152,23 +116,19 @@ AchievementItem* ac_connectToServerUnlock[] = {
|
||||
nullptr
|
||||
};
|
||||
|
||||
AchievementItem ai_connectToServer = { "connectToClient", "First profiling session", [](){
|
||||
ImGui::TextWrapped( "Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:" );
|
||||
ImGui::TextWrapped( " 1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project." );
|
||||
ImGui::TextWrapped( " 2. Make sure that TracyClient.cpp (or the Tracy library) is included in your build." );
|
||||
ImGui::TextWrapped( " 3. Define TRACY_ENABLE in your build configuration, for the whole application. Do not do it in a single source file because it won't work." );
|
||||
ImGui::TextWrapped( " 4. Start your application, and \"" ICON_FA_WIFI " Connect\" to it with the profiler." );
|
||||
ImGui::TextWrapped( "Please refer to the user manual for more details." );
|
||||
if( ImGui::SmallButton( "Download the user manual" ) )
|
||||
{
|
||||
tracy::OpenWebpage( "https://github.com/wolfpld/tracy/releases" );
|
||||
}
|
||||
}, ac_connectToServerItems, ac_connectToServerUnlock };
|
||||
AchievementItem ai_connectToServer = {
|
||||
.id = "connectToClient",
|
||||
.name = "First profiling session",
|
||||
.text = Unpack( TextConnectToClient ),
|
||||
.items = ac_connectToServerItems,
|
||||
.unlocks = ac_connectToServerUnlock
|
||||
};
|
||||
|
||||
AchievementItem ai_globalSettings = { "globalSettings", "Global settings", [](){
|
||||
ImGui::TextWrapped( "Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the " ICON_FA_WRENCH " icon on the welcome screen. This will open the about window, where you can expand the \"" ICON_FA_TOOLBOX " Global settings\" menu." );
|
||||
ImGui::TextWrapped( "The settings are saved between sessions, so you only need to set them once." );
|
||||
} };
|
||||
AchievementItem ai_globalSettings = {
|
||||
.id = "globalSettings",
|
||||
.name = "Global settings",
|
||||
.text = Unpack( TextGlobalSettings )
|
||||
};
|
||||
|
||||
AchievementItem* ac_achievementsIntroItems[] = {
|
||||
&ai_connectToServer,
|
||||
@@ -176,18 +136,14 @@ AchievementItem* ac_achievementsIntroItems[] = {
|
||||
nullptr
|
||||
};
|
||||
|
||||
AchievementItem ai_achievementsIntro = { "achievementsIntro", "Click here to discover achievements!", [](){
|
||||
ImGui::TextWrapped( "Clicking on the " ICON_FA_STAR " button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done." );
|
||||
ImGui::TextWrapped( "As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!" );
|
||||
ImGui::TextWrapped( "To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements " ICON_FA_STAR " button will glow yellow when there are new things to see." );
|
||||
ImGui::TextUnformatted( "New tasks:" );
|
||||
ImGui::SameLine();
|
||||
TextColoredUnformatted( 0xFF4488FF, ICON_FA_CIRCLE_EXCLAMATION );
|
||||
ImGui::TextUnformatted( "Completed tasks:" );
|
||||
ImGui::SameLine();
|
||||
TextColoredUnformatted( 0xFF44FF44, ICON_FA_CIRCLE_CHECK );
|
||||
ImGui::TextWrapped( "Good luck!" );
|
||||
}, ac_achievementsIntroItems, nullptr, true, 1 };
|
||||
AchievementItem ai_achievementsIntro = {
|
||||
.id = "achievementsIntro",
|
||||
.name = "Click here to discover achievements!",
|
||||
.text = Unpack( TextIntro ),
|
||||
.items = ac_achievementsIntroItems,
|
||||
.keepOpen = true,
|
||||
.unlockTime = 1
|
||||
};
|
||||
|
||||
AchievementItem* ac_firstStepsItems[] = { &ai_achievementsIntro, nullptr };
|
||||
AchievementCategory ac_firstSteps = { "firstSteps", "First steps", ac_firstStepsItems, 1 };
|
||||
|
||||
@@ -20,7 +20,7 @@ struct AchievementItem
|
||||
{
|
||||
const char* id;
|
||||
const char* name;
|
||||
void(*description)();
|
||||
std::string text;
|
||||
AchievementItem** items;
|
||||
AchievementItem** unlocks;
|
||||
bool keepOpen;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "imgui.h"
|
||||
#include "../Fonts.hpp"
|
||||
|
||||
#include "IconsFontAwesome6.h"
|
||||
#include "IconsFontAwesome7.h"
|
||||
#include "TracyBadVersion.hpp"
|
||||
#include "TracyImGui.hpp"
|
||||
#include "TracyWeb.hpp"
|
||||
|
||||
@@ -42,10 +42,18 @@ void LoadConfig()
|
||||
if( ini_sget( ini, "llm", "enabled", "%d", &v ) ) s_config.llm = v;
|
||||
if( v2 = ini_get( ini, "llm", "address" ); v2 ) s_config.llmAddress = v2;
|
||||
if( v2 = ini_get( ini, "llm", "model" ); v2 ) s_config.llmModel = v2;
|
||||
if( v2 = ini_get( ini, "llm", "fastModel" ); v2 ) s_config.llmFastModel = v2;
|
||||
if( v2 = ini_get( ini, "llm", "embeddings" ); v2 ) s_config.llmEmbeddingsModel = v2;
|
||||
if( v2 = ini_get( ini, "llm", "useragent" ); v2 ) s_config.llmUserAgent = v2;
|
||||
if( v2 = ini_get( ini, "llm", "searchIdentifier" ); v2 ) s_config.llmSearchIdentifier = v2;
|
||||
if( v2 = ini_get( ini, "llm", "searchApiKey" ); v2 ) s_config.llmSearchApiKey = v2;
|
||||
if( v2 = ini_get( ini, "llm", "searchBraveApiKey" ); v2 ) s_config.llmSearchBraveApiKey = v2;
|
||||
if( ini_sget( ini, "llm", "annotateCallstacks", "%d", &v ) ) s_config.llmAnnotateCallstacks = v;
|
||||
if( ini_sget( ini, "llm", "limitToolReplySize", "%d", &v ) ) s_config.llmLimitToolReplySize = v;
|
||||
if( ini_sget( ini, "llm", "maxToolReplySizeValue", "%d", &v ) ) s_config.llmMaxToolReplySizeValue = v;
|
||||
if( ini_sget( ini, "llm", "separateFastModel", "%d", &v ) ) s_config.llmSeparateFastModel = v;
|
||||
if( ini_sget( ini, "llm", "summary", "%d", &v ) ) s_config.llmSummary = v;
|
||||
if( ini_sget( ini, "llm", "suggestion", "%d", &v ) ) s_config.llmSuggestion = v;
|
||||
|
||||
ini_free( ini );
|
||||
}
|
||||
@@ -89,10 +97,18 @@ bool SaveConfig()
|
||||
fprintf( f, "enabled = %i\n", (int)s_config.llm );
|
||||
fprintf( f, "address = %s\n", s_config.llmAddress.c_str() );
|
||||
fprintf( f, "model = %s\n", s_config.llmModel.c_str() );
|
||||
fprintf( f, "fastModel = %s\n", s_config.llmFastModel.c_str() );
|
||||
fprintf( f, "embeddings = %s\n", s_config.llmEmbeddingsModel.c_str() );
|
||||
fprintf( f, "useragent = %s\n", s_config.llmUserAgent.c_str() );
|
||||
fprintf( f, "searchIdentifier = %s\n", s_config.llmSearchIdentifier.c_str() );
|
||||
fprintf( f, "searchApiKey = %s\n", s_config.llmSearchApiKey.c_str() );
|
||||
fprintf( f, "searchBraveApiKey = %s\n", s_config.llmSearchBraveApiKey.c_str() );
|
||||
fprintf( f, "annotateCallstacks = %i\n", (int)s_config.llmAnnotateCallstacks );
|
||||
fprintf( f, "limitToolReplySize = %i\n", (int)s_config.llmLimitToolReplySize );
|
||||
fprintf( f, "maxToolReplySizeValue = %i\n", s_config.llmMaxToolReplySizeValue );
|
||||
fprintf( f, "separateFastModel = %i\n", (int)s_config.llmSeparateFastModel );
|
||||
fprintf( f, "summary = %i\n", (int)s_config.llmSummary );
|
||||
fprintf( f, "suggestion = %i\n", (int)s_config.llmSuggestion );
|
||||
|
||||
fclose( f );
|
||||
return true;
|
||||
|
||||
@@ -36,12 +36,20 @@ struct Config
|
||||
#else
|
||||
bool llm = true;
|
||||
#endif
|
||||
std::string llmAddress = "http://localhost:11434";
|
||||
std::string llmAddress = "http://localhost:8080";
|
||||
std::string llmModel;
|
||||
std::string llmFastModel;
|
||||
std::string llmEmbeddingsModel;
|
||||
std::string llmUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
||||
std::string llmSearchIdentifier;
|
||||
std::string llmSearchApiKey;
|
||||
std::string llmSearchBraveApiKey;
|
||||
bool llmSeparateFastModel = true;
|
||||
bool llmAnnotateCallstacks = false;
|
||||
bool llmLimitToolReplySize = false;
|
||||
int llmMaxToolReplySizeValue = 48*1024;
|
||||
bool llmSummary = true;
|
||||
bool llmSuggestion = true;
|
||||
};
|
||||
|
||||
extern Config s_config;
|
||||
|
||||
982
profiler/src/profiler/TracyDisassembly.cpp
Normal file
982
profiler/src/profiler/TracyDisassembly.cpp
Normal file
@@ -0,0 +1,982 @@
|
||||
#include <capstone.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "TracyDisassembly.hpp"
|
||||
#include "TracyView.hpp"
|
||||
#include "tracy_pdqsort.h"
|
||||
#include "../server/TracyWorker.hpp"
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
static RegsX86 s_regMapX86[X86_REG_ENDING];
|
||||
|
||||
struct InitRegMapX86
|
||||
{
|
||||
InitRegMapX86()
|
||||
{
|
||||
memset( s_regMapX86, 0, sizeof( s_regMapX86 ) );
|
||||
|
||||
s_regMapX86[X86_REG_EFLAGS] = RegsX86::flags;
|
||||
s_regMapX86[X86_REG_AH] = RegsX86::rax;
|
||||
s_regMapX86[X86_REG_AL] = RegsX86::rax;
|
||||
s_regMapX86[X86_REG_AX] = RegsX86::rax;
|
||||
s_regMapX86[X86_REG_EAX] = RegsX86::rax;
|
||||
s_regMapX86[X86_REG_RAX] = RegsX86::rax;
|
||||
s_regMapX86[X86_REG_BH] = RegsX86::rbx;
|
||||
s_regMapX86[X86_REG_BL] = RegsX86::rbx;
|
||||
s_regMapX86[X86_REG_BX] = RegsX86::rbx;
|
||||
s_regMapX86[X86_REG_EBX] = RegsX86::rbx;
|
||||
s_regMapX86[X86_REG_RBX] = RegsX86::rbx;
|
||||
s_regMapX86[X86_REG_CH] = RegsX86::rcx;
|
||||
s_regMapX86[X86_REG_CL] = RegsX86::rcx;
|
||||
s_regMapX86[X86_REG_CX] = RegsX86::rcx;
|
||||
s_regMapX86[X86_REG_ECX] = RegsX86::rcx;
|
||||
s_regMapX86[X86_REG_RCX] = RegsX86::rcx;
|
||||
s_regMapX86[X86_REG_DH] = RegsX86::rdx;
|
||||
s_regMapX86[X86_REG_DL] = RegsX86::rdx;
|
||||
s_regMapX86[X86_REG_DX] = RegsX86::rdx;
|
||||
s_regMapX86[X86_REG_EDX] = RegsX86::rdx;
|
||||
s_regMapX86[X86_REG_RDX] = RegsX86::rdx;
|
||||
s_regMapX86[X86_REG_SIL] = RegsX86::rsi;
|
||||
s_regMapX86[X86_REG_SI] = RegsX86::rsi;
|
||||
s_regMapX86[X86_REG_ESI] = RegsX86::rsi;
|
||||
s_regMapX86[X86_REG_RSI] = RegsX86::rsi;
|
||||
s_regMapX86[X86_REG_DIL] = RegsX86::rdi;
|
||||
s_regMapX86[X86_REG_DI] = RegsX86::rdi;
|
||||
s_regMapX86[X86_REG_EDI] = RegsX86::rdi;
|
||||
s_regMapX86[X86_REG_RDI] = RegsX86::rdi;
|
||||
s_regMapX86[X86_REG_BPL] = RegsX86::rbp;
|
||||
s_regMapX86[X86_REG_BP] = RegsX86::rbp;
|
||||
s_regMapX86[X86_REG_EBP] = RegsX86::rbp;
|
||||
s_regMapX86[X86_REG_RBP] = RegsX86::rbp;
|
||||
s_regMapX86[X86_REG_SPL] = RegsX86::rsp;
|
||||
s_regMapX86[X86_REG_SP] = RegsX86::rsp;
|
||||
s_regMapX86[X86_REG_ESP] = RegsX86::rsp;
|
||||
s_regMapX86[X86_REG_RSP] = RegsX86::rsp;
|
||||
s_regMapX86[X86_REG_R8B] = RegsX86::r8;
|
||||
s_regMapX86[X86_REG_R8W] = RegsX86::r8;
|
||||
s_regMapX86[X86_REG_R8D] = RegsX86::r8;
|
||||
s_regMapX86[X86_REG_R8] = RegsX86::r8;
|
||||
s_regMapX86[X86_REG_R9B] = RegsX86::r9;
|
||||
s_regMapX86[X86_REG_R9W] = RegsX86::r9;
|
||||
s_regMapX86[X86_REG_R9D] = RegsX86::r9;
|
||||
s_regMapX86[X86_REG_R9] = RegsX86::r9;
|
||||
s_regMapX86[X86_REG_R10B] = RegsX86::r10;
|
||||
s_regMapX86[X86_REG_R10W] = RegsX86::r10;
|
||||
s_regMapX86[X86_REG_R10D] = RegsX86::r10;
|
||||
s_regMapX86[X86_REG_R10] = RegsX86::r10;
|
||||
s_regMapX86[X86_REG_R11B] = RegsX86::r11;
|
||||
s_regMapX86[X86_REG_R11W] = RegsX86::r11;
|
||||
s_regMapX86[X86_REG_R11D] = RegsX86::r11;
|
||||
s_regMapX86[X86_REG_R11] = RegsX86::r11;
|
||||
s_regMapX86[X86_REG_R12B] = RegsX86::r12;
|
||||
s_regMapX86[X86_REG_R12W] = RegsX86::r12;
|
||||
s_regMapX86[X86_REG_R12D] = RegsX86::r12;
|
||||
s_regMapX86[X86_REG_R12] = RegsX86::r12;
|
||||
s_regMapX86[X86_REG_R13B] = RegsX86::r13;
|
||||
s_regMapX86[X86_REG_R13W] = RegsX86::r13;
|
||||
s_regMapX86[X86_REG_R13D] = RegsX86::r13;
|
||||
s_regMapX86[X86_REG_R13] = RegsX86::r13;
|
||||
s_regMapX86[X86_REG_R14B] = RegsX86::r14;
|
||||
s_regMapX86[X86_REG_R14W] = RegsX86::r14;
|
||||
s_regMapX86[X86_REG_R14D] = RegsX86::r14;
|
||||
s_regMapX86[X86_REG_R14] = RegsX86::r14;
|
||||
s_regMapX86[X86_REG_R15B] = RegsX86::r15;
|
||||
s_regMapX86[X86_REG_R15W] = RegsX86::r15;
|
||||
s_regMapX86[X86_REG_R15D] = RegsX86::r15;
|
||||
s_regMapX86[X86_REG_R15] = RegsX86::r15;
|
||||
s_regMapX86[X86_REG_MM0] = RegsX86::mm0;
|
||||
s_regMapX86[X86_REG_MM1] = RegsX86::mm1;
|
||||
s_regMapX86[X86_REG_MM2] = RegsX86::mm2;
|
||||
s_regMapX86[X86_REG_MM3] = RegsX86::mm3;
|
||||
s_regMapX86[X86_REG_MM4] = RegsX86::mm4;
|
||||
s_regMapX86[X86_REG_MM5] = RegsX86::mm5;
|
||||
s_regMapX86[X86_REG_MM6] = RegsX86::mm6;
|
||||
s_regMapX86[X86_REG_MM7] = RegsX86::mm7;
|
||||
s_regMapX86[X86_REG_ST0] = RegsX86::mm0;
|
||||
s_regMapX86[X86_REG_ST1] = RegsX86::mm1;
|
||||
s_regMapX86[X86_REG_ST2] = RegsX86::mm2;
|
||||
s_regMapX86[X86_REG_ST3] = RegsX86::mm3;
|
||||
s_regMapX86[X86_REG_ST4] = RegsX86::mm4;
|
||||
s_regMapX86[X86_REG_ST5] = RegsX86::mm5;
|
||||
s_regMapX86[X86_REG_ST6] = RegsX86::mm6;
|
||||
s_regMapX86[X86_REG_ST7] = RegsX86::mm7;
|
||||
s_regMapX86[X86_REG_XMM0] = RegsX86::xmm0;
|
||||
s_regMapX86[X86_REG_YMM0] = RegsX86::xmm0;
|
||||
s_regMapX86[X86_REG_ZMM0] = RegsX86::xmm0;
|
||||
s_regMapX86[X86_REG_XMM1] = RegsX86::xmm1;
|
||||
s_regMapX86[X86_REG_YMM1] = RegsX86::xmm1;
|
||||
s_regMapX86[X86_REG_ZMM1] = RegsX86::xmm1;
|
||||
s_regMapX86[X86_REG_XMM2] = RegsX86::xmm2;
|
||||
s_regMapX86[X86_REG_YMM2] = RegsX86::xmm2;
|
||||
s_regMapX86[X86_REG_ZMM2] = RegsX86::xmm2;
|
||||
s_regMapX86[X86_REG_XMM3] = RegsX86::xmm3;
|
||||
s_regMapX86[X86_REG_YMM3] = RegsX86::xmm3;
|
||||
s_regMapX86[X86_REG_ZMM3] = RegsX86::xmm3;
|
||||
s_regMapX86[X86_REG_XMM4] = RegsX86::xmm4;
|
||||
s_regMapX86[X86_REG_YMM4] = RegsX86::xmm4;
|
||||
s_regMapX86[X86_REG_ZMM4] = RegsX86::xmm4;
|
||||
s_regMapX86[X86_REG_XMM5] = RegsX86::xmm5;
|
||||
s_regMapX86[X86_REG_YMM5] = RegsX86::xmm5;
|
||||
s_regMapX86[X86_REG_ZMM5] = RegsX86::xmm5;
|
||||
s_regMapX86[X86_REG_XMM6] = RegsX86::xmm6;
|
||||
s_regMapX86[X86_REG_YMM6] = RegsX86::xmm6;
|
||||
s_regMapX86[X86_REG_ZMM6] = RegsX86::xmm6;
|
||||
s_regMapX86[X86_REG_XMM7] = RegsX86::xmm7;
|
||||
s_regMapX86[X86_REG_YMM7] = RegsX86::xmm7;
|
||||
s_regMapX86[X86_REG_ZMM7] = RegsX86::xmm7;
|
||||
s_regMapX86[X86_REG_XMM8] = RegsX86::xmm8;
|
||||
s_regMapX86[X86_REG_YMM8] = RegsX86::xmm8;
|
||||
s_regMapX86[X86_REG_ZMM8] = RegsX86::xmm8;
|
||||
s_regMapX86[X86_REG_XMM9] = RegsX86::xmm9;
|
||||
s_regMapX86[X86_REG_YMM9] = RegsX86::xmm9;
|
||||
s_regMapX86[X86_REG_ZMM9] = RegsX86::xmm9;
|
||||
s_regMapX86[X86_REG_XMM10] = RegsX86::xmm10;
|
||||
s_regMapX86[X86_REG_YMM10] = RegsX86::xmm10;
|
||||
s_regMapX86[X86_REG_ZMM10] = RegsX86::xmm10;
|
||||
s_regMapX86[X86_REG_XMM11] = RegsX86::xmm11;
|
||||
s_regMapX86[X86_REG_YMM11] = RegsX86::xmm11;
|
||||
s_regMapX86[X86_REG_ZMM11] = RegsX86::xmm11;
|
||||
s_regMapX86[X86_REG_XMM12] = RegsX86::xmm12;
|
||||
s_regMapX86[X86_REG_YMM12] = RegsX86::xmm12;
|
||||
s_regMapX86[X86_REG_ZMM12] = RegsX86::xmm12;
|
||||
s_regMapX86[X86_REG_XMM13] = RegsX86::xmm13;
|
||||
s_regMapX86[X86_REG_YMM13] = RegsX86::xmm13;
|
||||
s_regMapX86[X86_REG_ZMM13] = RegsX86::xmm13;
|
||||
s_regMapX86[X86_REG_XMM14] = RegsX86::xmm14;
|
||||
s_regMapX86[X86_REG_YMM14] = RegsX86::xmm14;
|
||||
s_regMapX86[X86_REG_ZMM14] = RegsX86::xmm14;
|
||||
s_regMapX86[X86_REG_XMM15] = RegsX86::xmm15;
|
||||
s_regMapX86[X86_REG_YMM15] = RegsX86::xmm15;
|
||||
s_regMapX86[X86_REG_ZMM15] = RegsX86::xmm15;
|
||||
s_regMapX86[X86_REG_XMM16] = RegsX86::xmm16;
|
||||
s_regMapX86[X86_REG_YMM16] = RegsX86::xmm16;
|
||||
s_regMapX86[X86_REG_ZMM16] = RegsX86::xmm16;
|
||||
s_regMapX86[X86_REG_XMM17] = RegsX86::xmm17;
|
||||
s_regMapX86[X86_REG_YMM17] = RegsX86::xmm17;
|
||||
s_regMapX86[X86_REG_ZMM17] = RegsX86::xmm17;
|
||||
s_regMapX86[X86_REG_XMM18] = RegsX86::xmm18;
|
||||
s_regMapX86[X86_REG_YMM18] = RegsX86::xmm18;
|
||||
s_regMapX86[X86_REG_ZMM18] = RegsX86::xmm18;
|
||||
s_regMapX86[X86_REG_XMM19] = RegsX86::xmm19;
|
||||
s_regMapX86[X86_REG_YMM19] = RegsX86::xmm19;
|
||||
s_regMapX86[X86_REG_ZMM19] = RegsX86::xmm19;
|
||||
s_regMapX86[X86_REG_XMM20] = RegsX86::xmm20;
|
||||
s_regMapX86[X86_REG_YMM20] = RegsX86::xmm20;
|
||||
s_regMapX86[X86_REG_ZMM20] = RegsX86::xmm20;
|
||||
s_regMapX86[X86_REG_XMM21] = RegsX86::xmm21;
|
||||
s_regMapX86[X86_REG_YMM21] = RegsX86::xmm21;
|
||||
s_regMapX86[X86_REG_ZMM21] = RegsX86::xmm21;
|
||||
s_regMapX86[X86_REG_XMM22] = RegsX86::xmm22;
|
||||
s_regMapX86[X86_REG_YMM22] = RegsX86::xmm22;
|
||||
s_regMapX86[X86_REG_ZMM22] = RegsX86::xmm22;
|
||||
s_regMapX86[X86_REG_XMM23] = RegsX86::xmm23;
|
||||
s_regMapX86[X86_REG_YMM23] = RegsX86::xmm23;
|
||||
s_regMapX86[X86_REG_ZMM23] = RegsX86::xmm23;
|
||||
s_regMapX86[X86_REG_XMM24] = RegsX86::xmm24;
|
||||
s_regMapX86[X86_REG_YMM24] = RegsX86::xmm24;
|
||||
s_regMapX86[X86_REG_ZMM24] = RegsX86::xmm24;
|
||||
s_regMapX86[X86_REG_XMM25] = RegsX86::xmm25;
|
||||
s_regMapX86[X86_REG_YMM25] = RegsX86::xmm25;
|
||||
s_regMapX86[X86_REG_ZMM25] = RegsX86::xmm25;
|
||||
s_regMapX86[X86_REG_XMM26] = RegsX86::xmm26;
|
||||
s_regMapX86[X86_REG_YMM26] = RegsX86::xmm26;
|
||||
s_regMapX86[X86_REG_ZMM26] = RegsX86::xmm26;
|
||||
s_regMapX86[X86_REG_XMM27] = RegsX86::xmm27;
|
||||
s_regMapX86[X86_REG_YMM27] = RegsX86::xmm27;
|
||||
s_regMapX86[X86_REG_ZMM27] = RegsX86::xmm27;
|
||||
s_regMapX86[X86_REG_XMM28] = RegsX86::xmm28;
|
||||
s_regMapX86[X86_REG_YMM28] = RegsX86::xmm28;
|
||||
s_regMapX86[X86_REG_ZMM28] = RegsX86::xmm28;
|
||||
s_regMapX86[X86_REG_XMM29] = RegsX86::xmm29;
|
||||
s_regMapX86[X86_REG_YMM29] = RegsX86::xmm29;
|
||||
s_regMapX86[X86_REG_ZMM29] = RegsX86::xmm29;
|
||||
s_regMapX86[X86_REG_XMM30] = RegsX86::xmm30;
|
||||
s_regMapX86[X86_REG_YMM30] = RegsX86::xmm30;
|
||||
s_regMapX86[X86_REG_ZMM30] = RegsX86::xmm30;
|
||||
s_regMapX86[X86_REG_XMM31] = RegsX86::xmm31;
|
||||
s_regMapX86[X86_REG_YMM31] = RegsX86::xmm31;
|
||||
s_regMapX86[X86_REG_ZMM31] = RegsX86::xmm31;
|
||||
s_regMapX86[X86_REG_K0] = RegsX86::k0;
|
||||
s_regMapX86[X86_REG_K1] = RegsX86::k1;
|
||||
s_regMapX86[X86_REG_K2] = RegsX86::k2;
|
||||
s_regMapX86[X86_REG_K3] = RegsX86::k3;
|
||||
s_regMapX86[X86_REG_K4] = RegsX86::k4;
|
||||
s_regMapX86[X86_REG_K5] = RegsX86::k5;
|
||||
s_regMapX86[X86_REG_K6] = RegsX86::k6;
|
||||
s_regMapX86[X86_REG_K7] = RegsX86::k7;
|
||||
}
|
||||
};
|
||||
|
||||
static bool IsJumpConditionalX86( const char* op )
|
||||
{
|
||||
static constexpr const char* branchX86[] = {
|
||||
"je", "jne", "jg", "jge", "ja", "jae", "jl", "jle", "jb", "jbe", "jo", "jno", "jz", "jnz", "js", "jns", "jcxz", "jecxz", "jrcxz", "loop", "loope",
|
||||
"loopne", "loopnz", "loopz", "jnle", "jnl", "jnge", "jng", "jnbe", "jnb", "jnae", "jna", "jc", "jnc", "jp", "jpe", "jnp", "jpo", nullptr
|
||||
};
|
||||
auto ptr = branchX86;
|
||||
while( *ptr ) if( strcmp( *ptr++, op ) == 0 ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
DisasmData Disassemble( uint64_t symAddr, const Worker& worker )
|
||||
{
|
||||
DisasmData data = {};
|
||||
data.disasmFail = -1;
|
||||
|
||||
if( symAddr == 0 ) return data;
|
||||
data.cpuArch = worker.GetCpuArch();
|
||||
if( data.cpuArch == CpuArchUnknown ) return data;
|
||||
uint32_t len;
|
||||
auto code = worker.GetSymbolCode( symAddr, len );
|
||||
if( !code ) return data;
|
||||
data.codeLen = len;
|
||||
|
||||
static InitRegMapX86 regMapInit;
|
||||
|
||||
csh handle;
|
||||
cs_err rval = CS_ERR_ARCH;
|
||||
switch( data.cpuArch )
|
||||
{
|
||||
case CpuArchX86:
|
||||
rval = cs_open( CS_ARCH_X86, CS_MODE_32, &handle );
|
||||
break;
|
||||
case CpuArchX64:
|
||||
rval = cs_open( CS_ARCH_X86, CS_MODE_64, &handle );
|
||||
break;
|
||||
case CpuArchArm32:
|
||||
rval = cs_open( CS_ARCH_ARM, CS_MODE_ARM, &handle );
|
||||
break;
|
||||
case CpuArchArm64:
|
||||
rval = cs_open( CS_ARCH_AARCH64, CS_MODE_ARM, &handle );
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
if( rval != CS_ERR_OK ) return data;
|
||||
|
||||
Tokenizer tokenizer;
|
||||
|
||||
cs_option( handle, CS_OPT_DETAIL, CS_OPT_ON );
|
||||
cs_option( handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL );
|
||||
cs_insn* insn;
|
||||
size_t cnt = cs_disasm( handle, (const uint8_t*)code, len, symAddr, 0, &insn );
|
||||
if( cnt > 0 )
|
||||
{
|
||||
if( insn[cnt-1].address - symAddr + insn[cnt-1].size < len ) data.disasmFail = insn[cnt-1].address - symAddr;
|
||||
int bytesMax = 0;
|
||||
int mLenMax = 0;
|
||||
int oLenMax = 0;
|
||||
data.lines.reserve( cnt );
|
||||
for( size_t i=0; i<cnt; i++ )
|
||||
{
|
||||
const auto& op = insn[i];
|
||||
const auto& detail = *op.detail;
|
||||
bool hasJump = false;
|
||||
bool jumpConditional = false;
|
||||
AsmOpType opType = AsmOpType::None;
|
||||
for( auto j=0; j<detail.groups_count; j++ )
|
||||
{
|
||||
if( detail.groups[j] == CS_GRP_JUMP || detail.groups[j] == CS_GRP_CALL || detail.groups[j] == CS_GRP_RET )
|
||||
{
|
||||
hasJump = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for( auto j=0; j<detail.groups_count; j++ )
|
||||
{
|
||||
if( detail.groups[j] == CS_GRP_JUMP && opType < AsmOpType::Jump ) opType = AsmOpType::Jump;
|
||||
else if( detail.groups[j] == CS_GRP_BRANCH_RELATIVE && opType < AsmOpType::Branch ) opType = AsmOpType::Branch;
|
||||
else if( detail.groups[j] == CS_GRP_CALL && opType < AsmOpType::Call ) opType = AsmOpType::Call;
|
||||
else if( detail.groups[j] == CS_GRP_RET && opType < AsmOpType::Ret ) opType = AsmOpType::Ret;
|
||||
else if( detail.groups[j] == CS_GRP_PRIVILEGE && opType < AsmOpType::Privileged )
|
||||
{
|
||||
opType = AsmOpType::Privileged;
|
||||
break;
|
||||
}
|
||||
}
|
||||
uint64_t jumpAddr = 0;
|
||||
if( hasJump )
|
||||
{
|
||||
switch( data.cpuArch )
|
||||
{
|
||||
case CpuArchX86:
|
||||
case CpuArchX64:
|
||||
if( detail.x86.op_count == 1 && detail.x86.operands[0].type == X86_OP_IMM )
|
||||
{
|
||||
jumpAddr = (uint64_t)detail.x86.operands[0].imm;
|
||||
}
|
||||
jumpConditional = IsJumpConditionalX86( op.mnemonic );
|
||||
break;
|
||||
case CpuArchArm32:
|
||||
if( detail.arm.op_count == 1 && detail.arm.operands[0].type == ARM_OP_IMM )
|
||||
{
|
||||
jumpAddr = (uint64_t)detail.arm.operands[0].imm;
|
||||
}
|
||||
break;
|
||||
case CpuArchArm64:
|
||||
if( detail.aarch64.op_count == 1 && detail.aarch64.operands[0].type == AARCH64_OP_IMM )
|
||||
{
|
||||
jumpAddr = (uint64_t)detail.aarch64.operands[0].imm;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
if( jumpAddr >= symAddr && jumpAddr < symAddr + len )
|
||||
{
|
||||
auto fit = std::lower_bound( insn, insn+cnt, jumpAddr, []( const auto& l, const auto& r ) { return l.address < r; } );
|
||||
if( fit != insn+cnt && fit->address == jumpAddr )
|
||||
{
|
||||
const auto min = std::min( jumpAddr, op.address );
|
||||
const auto max = std::max( jumpAddr, op.address );
|
||||
auto it = data.jumpTable.find( jumpAddr );
|
||||
if( it == data.jumpTable.end() )
|
||||
{
|
||||
data.jumpTable.emplace( jumpAddr, AsmJumpData { min, max, 0, { op.address } } );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( it->second.min > min ) it->second.min = min;
|
||||
else if( it->second.max < max ) it->second.max = max;
|
||||
it->second.source.emplace_back( op.address );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jumpAddr = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
data.jumpOut.emplace( op.address );
|
||||
}
|
||||
}
|
||||
std::vector<AsmOpParams> params;
|
||||
switch( data.cpuArch )
|
||||
{
|
||||
case CpuArchX86:
|
||||
case CpuArchX64:
|
||||
for( uint8_t i=0; i<detail.x86.op_count; i++ )
|
||||
{
|
||||
uint8_t type = 0;
|
||||
switch( detail.x86.operands[i].type )
|
||||
{
|
||||
case X86_OP_IMM:
|
||||
type = 0;
|
||||
break;
|
||||
case X86_OP_REG:
|
||||
type = 1;
|
||||
break;
|
||||
case X86_OP_MEM:
|
||||
type = 2;
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
params.emplace_back( AsmOpParams { type, uint16_t( detail.x86.operands[i].size * 8 ) } );
|
||||
}
|
||||
break;
|
||||
case CpuArchArm32:
|
||||
for( uint8_t i=0; i<detail.arm.op_count; i++ )
|
||||
{
|
||||
uint8_t type = 0;
|
||||
switch( detail.arm.operands[i].type )
|
||||
{
|
||||
case ARM_OP_IMM:
|
||||
type = 0;
|
||||
break;
|
||||
case ARM_OP_REG:
|
||||
type = 1;
|
||||
break;
|
||||
case ARM_OP_MEM:
|
||||
type = 2;
|
||||
break;
|
||||
default:
|
||||
type = 255;
|
||||
break;
|
||||
}
|
||||
params.emplace_back( AsmOpParams { type, 0 } );
|
||||
}
|
||||
break;
|
||||
case CpuArchArm64:
|
||||
for( uint8_t i=0; i<detail.aarch64.op_count; i++ )
|
||||
{
|
||||
uint8_t type = 0;
|
||||
switch( detail.aarch64.operands[i].type )
|
||||
{
|
||||
case AARCH64_OP_IMM:
|
||||
type = 0;
|
||||
break;
|
||||
case AARCH64_OP_REG:
|
||||
type = 1;
|
||||
break;
|
||||
case AARCH64_OP_MEM:
|
||||
type = 2;
|
||||
break;
|
||||
default:
|
||||
type = 255;
|
||||
break;
|
||||
}
|
||||
params.emplace_back( AsmOpParams { type, 0 } );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
}
|
||||
AsmLeaData leaData = AsmLeaData::none;
|
||||
if( ( data.cpuArch == CpuArchX64 || data.cpuArch == CpuArchX86 ) && op.id == X86_INS_LEA )
|
||||
{
|
||||
assert( op.detail->x86.op_count == 2 );
|
||||
assert( op.detail->x86.operands[1].type == X86_OP_MEM );
|
||||
auto& mem = op.detail->x86.operands[1].mem;
|
||||
if( mem.base == X86_REG_INVALID )
|
||||
{
|
||||
if( mem.index == X86_REG_INVALID )
|
||||
{
|
||||
leaData = AsmLeaData::d;
|
||||
}
|
||||
else
|
||||
{
|
||||
leaData = mem.disp == 0 ? AsmLeaData::i : AsmLeaData::id;
|
||||
}
|
||||
}
|
||||
else if( mem.base == X86_REG_RIP )
|
||||
{
|
||||
leaData = mem.disp == 0 ? AsmLeaData::r : AsmLeaData::rd;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( mem.index == X86_REG_INVALID )
|
||||
{
|
||||
leaData = mem.disp == 0 ? AsmLeaData::b : AsmLeaData::bd;
|
||||
}
|
||||
else
|
||||
{
|
||||
leaData = mem.disp == 0 ? AsmLeaData::bi : AsmLeaData::bid;
|
||||
}
|
||||
}
|
||||
}
|
||||
data.lines.emplace_back( AsmLine { op.address, jumpAddr, op.mnemonic, op.op_str, (uint8_t)op.size, leaData, opType, jumpConditional, std::move( params ) } );
|
||||
const auto& operands = data.lines.back().operands;
|
||||
data.lines.back().opTokens = tokenizer.TokenizeAsm( operands.c_str(), operands.c_str() + operands.size() );
|
||||
|
||||
#if CS_API_MAJOR >= 4
|
||||
auto& entry = data.lines.back();
|
||||
cs_regs read, write;
|
||||
uint8_t rcnt, wcnt;
|
||||
cs_regs_access( handle, &op, read, &rcnt, write, &wcnt );
|
||||
int idx;
|
||||
switch( data.cpuArch )
|
||||
{
|
||||
case CpuArchX86:
|
||||
case CpuArchX64:
|
||||
assert( rcnt < sizeof( entry.readX86 ) );
|
||||
assert( wcnt < sizeof( entry.writeX86 ) );
|
||||
idx = 0;
|
||||
for( int i=0; i<rcnt; i++ )
|
||||
{
|
||||
if( s_regMapX86[read[i]] != RegsX86::invalid ) entry.readX86[idx++] = s_regMapX86[read[i]];
|
||||
entry.readX86[idx] = RegsX86::invalid;
|
||||
}
|
||||
idx = 0;
|
||||
for( int i=0; i<wcnt; i++ )
|
||||
{
|
||||
if( s_regMapX86[write[i]] != RegsX86::invalid ) entry.writeX86[idx++] = s_regMapX86[write[i]];
|
||||
entry.writeX86[idx] = RegsX86::invalid;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto mLen = (int)strlen( op.mnemonic );
|
||||
if( mLen > mLenMax ) mLenMax = mLen;
|
||||
const auto oLen = (int)strlen( op.op_str );
|
||||
if( oLen > oLenMax ) oLenMax = oLen;
|
||||
if( op.size > bytesMax ) bytesMax = op.size;
|
||||
|
||||
uint32_t mLineMax = 0;
|
||||
uint32_t srcline;
|
||||
const auto srcidx = worker.GetLocationForAddress( op.address, srcline );
|
||||
if( srcidx.Active() )
|
||||
{
|
||||
mLineMax = srcline;
|
||||
const auto idx = srcidx.Idx();
|
||||
auto sit = data.sourceFiles.find( idx );
|
||||
if( sit == data.sourceFiles.end() ) data.sourceFiles.emplace( idx, srcline );
|
||||
}
|
||||
char tmp[16];
|
||||
sprintf( tmp, "%" PRIu32, mLineMax );
|
||||
data.maxLine = std::max( data.maxLine, strlen( tmp ) + 1 );
|
||||
}
|
||||
cs_free( insn, cnt );
|
||||
data.maxMnemonicLen = mLenMax + 1;
|
||||
data.maxOperandLen = oLenMax + 1;
|
||||
data.maxAsmBytes = bytesMax;
|
||||
if( !data.jumpTable.empty() )
|
||||
{
|
||||
struct JumpRange
|
||||
{
|
||||
uint64_t target;
|
||||
uint64_t len;
|
||||
};
|
||||
std::vector<JumpRange> jumpRange;
|
||||
jumpRange.reserve( data.jumpTable.size() );
|
||||
for( auto& v : data.jumpTable )
|
||||
{
|
||||
pdqsort_branchless( v.second.source.begin(), v.second.source.end() );
|
||||
jumpRange.emplace_back( JumpRange { v.first, v.second.max - v.second.min } );
|
||||
}
|
||||
pdqsort_branchless( jumpRange.begin(), jumpRange.end(), []( const auto& l, const auto& r ) { return l.len < r.len; } );
|
||||
std::vector<std::vector<std::pair<uint64_t, uint64_t>>> levelRanges;
|
||||
for( auto& v : jumpRange )
|
||||
{
|
||||
auto it = data.jumpTable.find( v.target );
|
||||
assert( it != data.jumpTable.end() );
|
||||
size_t level = 0;
|
||||
for(;;)
|
||||
{
|
||||
assert( levelRanges.size() >= level );
|
||||
if( levelRanges.size() == level )
|
||||
{
|
||||
it->second.level = level;
|
||||
levelRanges.push_back( { { it->second.min, it->second.max } } );
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool validFit = true;
|
||||
auto& lr = levelRanges[level];
|
||||
for( auto& range : lr )
|
||||
{
|
||||
assert( !( it->second.min >= range.first && it->second.max <= range.second ) );
|
||||
if( it->second.min <= range.second && it->second.max >= range.first )
|
||||
{
|
||||
validFit = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( validFit )
|
||||
{
|
||||
it->second.level = level;
|
||||
lr.emplace_back( it->second.min, it->second.max );
|
||||
break;
|
||||
}
|
||||
level++;
|
||||
}
|
||||
}
|
||||
if( level > data.maxJumpLevel ) data.maxJumpLevel = level;
|
||||
}
|
||||
|
||||
uint32_t locNum = 0;
|
||||
for( auto& v : data.lines )
|
||||
{
|
||||
if( data.jumpTable.contains( v.addr ) )
|
||||
{
|
||||
data.locMap.emplace( v.addr, locNum++ );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cs_close( &handle );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::vector<std::string>& sources, uint64_t symAddr, const AddrStatData& as, const unordered_flat_map<uint64_t, uint32_t>& locMap )
|
||||
{
|
||||
std::string line;
|
||||
|
||||
uint32_t srcline;
|
||||
const auto srcidx = worker.GetLocationForAddress( opcode.addr, srcline );
|
||||
if( srcidx.Active() )
|
||||
{
|
||||
size_t idx;
|
||||
const auto file = worker.GetString( srcidx );
|
||||
auto it = std::ranges::find( sources, file );
|
||||
if( it == sources.end() )
|
||||
{
|
||||
idx = sources.size();
|
||||
sources.emplace_back( file );
|
||||
}
|
||||
else
|
||||
{
|
||||
idx = std::distance( sources.begin(), it );
|
||||
}
|
||||
|
||||
line = std::to_string( idx ) + ":" + std::to_string( srcline ) + ":";
|
||||
}
|
||||
else
|
||||
{
|
||||
line = "::";
|
||||
}
|
||||
|
||||
line += "+" + std::to_string( opcode.addr - symAddr ) + ":";
|
||||
|
||||
const auto totalCost = as.ipTotalAsm.local + as.ipTotalAsm.ext;
|
||||
if( totalCost != 0 )
|
||||
{
|
||||
char buf[32];
|
||||
auto it = as.ipCountAsm.find( opcode.addr );
|
||||
if( it != as.ipCountAsm.end() )
|
||||
{
|
||||
auto& stat = it->second;
|
||||
if( stat.local != 0 )
|
||||
{
|
||||
snprintf( buf, sizeof(buf), "%.4f%%:", 100.0f * stat.local / totalCost );
|
||||
line += buf;
|
||||
}
|
||||
else
|
||||
{
|
||||
line += ":";
|
||||
}
|
||||
if( stat.ext != 0 )
|
||||
{
|
||||
snprintf( buf, sizeof(buf), "%.4f%%:", 100.0f * stat.ext / totalCost );
|
||||
line += buf;
|
||||
}
|
||||
else
|
||||
{
|
||||
line += ":";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line += "::";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line += "::";
|
||||
}
|
||||
|
||||
line += opcode.mnemonic;
|
||||
|
||||
const char* jumpName = nullptr;
|
||||
bool hasJump = false;
|
||||
if( opcode.jumpAddr != 0 )
|
||||
{
|
||||
auto lit = locMap.find( opcode.jumpAddr );
|
||||
if( lit != locMap.end() )
|
||||
{
|
||||
line += " .L" + std::to_string( lit->second );
|
||||
hasJump = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t jumpOffset;
|
||||
uint64_t jumpBase = worker.GetSymbolForAddress( opcode.jumpAddr, jumpOffset );
|
||||
if( jumpBase && jumpBase != symAddr )
|
||||
{
|
||||
auto jumpSym = worker.GetSymbolData( jumpBase );
|
||||
if( jumpSym )
|
||||
{
|
||||
if( worker.HasInlineSymbolAddresses() )
|
||||
{
|
||||
const auto jumpAddr = worker.GetInlineSymbolForAddress( opcode.jumpAddr );
|
||||
if( jumpAddr != 0 )
|
||||
{
|
||||
const auto symData = worker.GetSymbolData( jumpAddr );
|
||||
if( symData ) jumpName = worker.GetString( symData->name );
|
||||
}
|
||||
}
|
||||
if( !jumpName ) jumpName = worker.GetString( jumpSym->name );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( !hasJump && !opcode.operands.empty() ) line += " " + opcode.operands;
|
||||
|
||||
std::string label;
|
||||
auto it = locMap.find( opcode.addr );
|
||||
if( it != locMap.end() ) label = ".L" + std::to_string( it->second );
|
||||
|
||||
if( jumpName || !label.empty() )
|
||||
{
|
||||
line += ";";
|
||||
if( !label.empty() ) line += " label: " + label;
|
||||
if( jumpName ) line += " destination: " + std::string( jumpName );
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& view )
|
||||
{
|
||||
auto sym = worker.GetSymbolData( symAddr );
|
||||
if( !sym ) return nlohmann::json { { "error", "Symbol not found" } };
|
||||
if( sym->isInline ) return nlohmann::json { { "error", "Symbol is inline" } };
|
||||
|
||||
auto data = Disassemble( symAddr, worker );
|
||||
if( data.lines.empty() ) return nlohmann::json { { "error", "Disassembly failed" } };
|
||||
|
||||
const bool limitView = view.m_statRange.active;
|
||||
AddrStatData as;
|
||||
GatherIpStats( symAddr, as, worker, limitView, view, nullptr, false );
|
||||
auto iptr = worker.GetInlineSymbolList( symAddr, data.codeLen );
|
||||
if( iptr )
|
||||
{
|
||||
const auto symEnd = symAddr + data.codeLen;
|
||||
while( *iptr < symEnd )
|
||||
{
|
||||
GatherIpStats( *iptr, as, worker, limitView, view, nullptr, false );
|
||||
iptr++;
|
||||
}
|
||||
}
|
||||
GatherAdditionalIpStats( symAddr, as, worker, limitView, view, nullptr, false );
|
||||
|
||||
char tmp[32];
|
||||
sprintf( tmp, "0x%" PRIx64, symAddr );
|
||||
|
||||
nlohmann::json json = {
|
||||
{ "address", tmp },
|
||||
{ "files", nlohmann::json::object() },
|
||||
{ "hint", "Code lines format is: fileIdx:line:offset:cost:callCost:assembly. To decode file names, access files[fileIdx]." },
|
||||
{ "symbol", worker.GetString( sym->name ) }
|
||||
};
|
||||
|
||||
std::vector<std::string> sources;
|
||||
std::string code;
|
||||
|
||||
for( auto& v: data.lines ) code += FormatDisassemblyLine( v, worker, sources, symAddr, as, data.locMap ) + "\n";
|
||||
json["code"] = code;
|
||||
for( size_t i = 0; i < sources.size(); ++i ) json["files"][std::to_string(i)] = sources[i];
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
|
||||
{
|
||||
if( limitView )
|
||||
{
|
||||
auto vec = worker.GetSamplesForSymbol( baseAddr );
|
||||
if( !vec ) return;
|
||||
auto it = std::lower_bound( vec->begin(), vec->end(), view.m_statRange.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
if( it == vec->end() ) return;
|
||||
auto end = std::lower_bound( it, vec->end(), view.m_statRange.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
as.ipTotalAsm.local += end - it;
|
||||
while( it != end )
|
||||
{
|
||||
if( filename )
|
||||
{
|
||||
auto frame = worker.GetCallstackFrame( it->ip );
|
||||
if( frame )
|
||||
{
|
||||
const auto end = propagateInlines ? frame->size : 1;
|
||||
for( uint8_t i=0; i<end; i++ )
|
||||
{
|
||||
const auto line = frame->data[i].line;
|
||||
if( line != 0 )
|
||||
{
|
||||
auto ffn = worker.GetString( frame->data[i].file );
|
||||
if( strcmp( ffn, filename ) == 0 )
|
||||
{
|
||||
auto sit = as.ipCountSrc.find( line );
|
||||
if( sit == as.ipCountSrc.end() )
|
||||
{
|
||||
as.ipCountSrc.emplace( line, AddrStat { 1, 0 } );
|
||||
if( as.ipMaxSrc.local < 1 ) as.ipMaxSrc.local = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto sum = sit->second.local + 1;
|
||||
sit->second.local = sum;
|
||||
if( as.ipMaxSrc.local < sum ) as.ipMaxSrc.local = sum;
|
||||
}
|
||||
as.ipTotalSrc.local++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto addr = worker.GetCanonicalPointer( it->ip );
|
||||
auto sit = as.ipCountAsm.find( addr );
|
||||
if( sit == as.ipCountAsm.end() )
|
||||
{
|
||||
as.ipCountAsm.emplace( addr, AddrStat{ 1, 0 } );
|
||||
if( as.ipMaxAsm.local < 1 ) as.ipMaxAsm.local = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto sum = sit->second.local + 1;
|
||||
sit->second.local = sum;
|
||||
if( as.ipMaxAsm.local < sum ) as.ipMaxAsm.local = sum;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ipmap = worker.GetSymbolInstructionPointers( baseAddr );
|
||||
if( !ipmap ) return;
|
||||
for( auto& ip : *ipmap )
|
||||
{
|
||||
auto addr = worker.GetCanonicalPointer( ip.first );
|
||||
assert( as.ipCountAsm.find( addr ) == as.ipCountAsm.end() );
|
||||
as.ipCountAsm.emplace( addr, AddrStat { ip.second, 0 } );
|
||||
as.ipTotalAsm.local += ip.second;
|
||||
if( as.ipMaxAsm.local < ip.second ) as.ipMaxAsm.local = ip.second;
|
||||
|
||||
if( filename )
|
||||
{
|
||||
auto frame = worker.GetCallstackFrame( ip.first );
|
||||
if( frame )
|
||||
{
|
||||
const auto end = propagateInlines ? frame->size : 1;
|
||||
for( uint8_t i=0; i<end; i++ )
|
||||
{
|
||||
const auto line = frame->data[i].line;
|
||||
if( line != 0 )
|
||||
{
|
||||
auto ffn = worker.GetString( frame->data[i].file );
|
||||
if( strcmp( ffn, filename ) == 0 )
|
||||
{
|
||||
auto it = as.ipCountSrc.find( line );
|
||||
if( it == as.ipCountSrc.end() )
|
||||
{
|
||||
as.ipCountSrc.emplace( line, AddrStat{ ip.second, 0 } );
|
||||
if( as.ipMaxSrc.local < ip.second ) as.ipMaxSrc.local = ip.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto sum = it->second.local + ip.second;
|
||||
it->second.local = sum;
|
||||
if( as.ipMaxSrc.local < sum ) as.ipMaxSrc.local = sum;
|
||||
}
|
||||
as.ipTotalSrc.local += ip.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
|
||||
{
|
||||
if( !worker.AreSymbolSamplesReady() ) return;
|
||||
auto sym = worker.GetSymbolData( baseAddr );
|
||||
if( !sym ) return;
|
||||
|
||||
if( limitView )
|
||||
{
|
||||
for( uint64_t ip = baseAddr; ip < baseAddr + sym->size.Val(); ip++ )
|
||||
{
|
||||
auto cp = worker.GetChildSamples( ip );
|
||||
if( !cp ) continue;
|
||||
auto it = std::lower_bound( cp->begin(), cp->end(), view.m_statRange.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
if( it == cp->end() ) continue;
|
||||
auto end = std::lower_bound( it, cp->end(), view.m_statRange.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
const auto ccnt = uint64_t( end - it );
|
||||
auto eit = as.ipCountAsm.find( ip );
|
||||
if( eit == as.ipCountAsm.end() )
|
||||
{
|
||||
as.ipCountAsm.emplace( ip, AddrStat { 0, ccnt } );
|
||||
}
|
||||
else
|
||||
{
|
||||
eit->second.ext += ccnt;
|
||||
}
|
||||
as.ipTotalAsm.ext += ccnt;
|
||||
if( as.ipMaxAsm.ext < ccnt ) as.ipMaxAsm.ext = ccnt;
|
||||
|
||||
if( filename )
|
||||
{
|
||||
auto frame = worker.GetCallstackFrame( worker.PackPointer( ip ) );
|
||||
if( frame )
|
||||
{
|
||||
const auto end = propagateInlines ? frame->size : 1;
|
||||
for( uint8_t i=0; i<end; i++ )
|
||||
{
|
||||
const auto line = frame->data[i].line;
|
||||
if( line != 0 )
|
||||
{
|
||||
auto ffn = worker.GetString( frame->data[i].file );
|
||||
if( strcmp( ffn, filename ) == 0 )
|
||||
{
|
||||
auto sit = as.ipCountSrc.find( line );
|
||||
if( sit == as.ipCountSrc.end() )
|
||||
{
|
||||
as.ipCountSrc.emplace( line, AddrStat{ 0, ccnt } );
|
||||
if( as.ipMaxSrc.ext < ccnt ) as.ipMaxSrc.ext = ccnt;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto csum = sit->second.ext + ccnt;
|
||||
sit->second.ext = csum;
|
||||
if( as.ipMaxSrc.ext < csum ) as.ipMaxSrc.ext = csum;
|
||||
}
|
||||
as.ipTotalSrc.ext += ccnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( uint64_t ip = baseAddr; ip < baseAddr + sym->size.Val(); ip++ )
|
||||
{
|
||||
auto cp = worker.GetChildSamples( ip );
|
||||
if( !cp ) continue;
|
||||
const auto ccnt = cp->size();
|
||||
auto eit = as.ipCountAsm.find( ip );
|
||||
if( eit == as.ipCountAsm.end() )
|
||||
{
|
||||
as.ipCountAsm.emplace( ip, AddrStat { 0, ccnt } );
|
||||
}
|
||||
else
|
||||
{
|
||||
eit->second.ext += ccnt;
|
||||
}
|
||||
as.ipTotalAsm.ext += ccnt;
|
||||
if( as.ipMaxAsm.ext < ccnt ) as.ipMaxAsm.ext = ccnt;
|
||||
|
||||
if( filename )
|
||||
{
|
||||
auto frame = worker.GetCallstackFrame( worker.PackPointer( ip ) );
|
||||
if( frame )
|
||||
{
|
||||
const auto end = propagateInlines ? frame->size : 1;
|
||||
for( uint8_t i=0; i<end; i++ )
|
||||
{
|
||||
const auto line = frame->data[i].line;
|
||||
if( line != 0 )
|
||||
{
|
||||
auto ffn = worker.GetString( frame->data[i].file );
|
||||
if( strcmp( ffn, filename ) == 0 )
|
||||
{
|
||||
auto sit = as.ipCountSrc.find( line );
|
||||
if( sit == as.ipCountSrc.end() )
|
||||
{
|
||||
as.ipCountSrc.emplace( line, AddrStat{ 0, ccnt } );
|
||||
if( as.ipMaxSrc.ext < ccnt ) as.ipMaxSrc.ext = ccnt;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto csum = sit->second.ext + ccnt;
|
||||
sit->second.ext = csum;
|
||||
if( as.ipMaxSrc.ext < csum ) as.ipMaxSrc.ext = csum;
|
||||
}
|
||||
as.ipTotalSrc.ext += ccnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
145
profiler/src/profiler/TracyDisassembly.hpp
Normal file
145
profiler/src/profiler/TracyDisassembly.hpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#ifndef __TRACYDISASSEMBLY_HPP__
|
||||
#define __TRACYDISASSEMBLY_HPP__
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "tracy_robin_hood.h"
|
||||
#include "TracyProtocol.hpp"
|
||||
#include "TracySourceTokenizer.hpp"
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
class View;
|
||||
class Worker;
|
||||
|
||||
enum class RegsX86 : uint8_t
|
||||
{
|
||||
invalid, flags,
|
||||
rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15,
|
||||
mm0, mm1, mm2, mm3, mm4, mm5, mm6, mm7,
|
||||
xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9,
|
||||
xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, xmm16, xmm17, xmm18, xmm19,
|
||||
xmm20, xmm21, xmm22, xmm23, xmm24, xmm25, xmm26, xmm27, xmm28, xmm29,
|
||||
xmm30, xmm31, k0, k1, k2, k3, k4, k5, k6, k7,
|
||||
NUMBER_OF_ENTRIES
|
||||
};
|
||||
|
||||
enum class AsmLeaData : uint8_t
|
||||
{
|
||||
none,
|
||||
b,
|
||||
bd,
|
||||
bi,
|
||||
bid,
|
||||
d,
|
||||
i,
|
||||
id,
|
||||
r,
|
||||
rd
|
||||
};
|
||||
|
||||
enum class AsmOpType : uint8_t
|
||||
{
|
||||
None,
|
||||
Jump,
|
||||
Branch,
|
||||
Call,
|
||||
Ret,
|
||||
Privileged
|
||||
};
|
||||
|
||||
struct AsmOpParams
|
||||
{
|
||||
uint8_t type;
|
||||
uint16_t width;
|
||||
};
|
||||
|
||||
struct AsmJumpData
|
||||
{
|
||||
uint64_t min;
|
||||
uint64_t max;
|
||||
size_t level;
|
||||
std::vector<uint64_t> source;
|
||||
};
|
||||
|
||||
struct AsmLine
|
||||
{
|
||||
uint64_t addr;
|
||||
uint64_t jumpAddr;
|
||||
std::string mnemonic;
|
||||
std::string operands;
|
||||
uint8_t len;
|
||||
AsmLeaData leaData;
|
||||
AsmOpType opType;
|
||||
bool jumpConditional;
|
||||
std::vector<AsmOpParams> params;
|
||||
std::vector<Tokenizer::AsmToken> opTokens;
|
||||
union
|
||||
{
|
||||
RegsX86 readX86[12];
|
||||
};
|
||||
union
|
||||
{
|
||||
RegsX86 writeX86[20];
|
||||
};
|
||||
uint16_t regData[20];
|
||||
};
|
||||
|
||||
struct DisasmData
|
||||
{
|
||||
std::vector<AsmLine> lines;
|
||||
unordered_flat_map<uint64_t, uint32_t> locMap;
|
||||
unordered_flat_map<uint64_t, AsmJumpData> jumpTable;
|
||||
unordered_flat_set<uint64_t> jumpOut;
|
||||
unordered_flat_map<uint32_t, uint32_t> sourceFiles;
|
||||
|
||||
size_t maxJumpLevel;
|
||||
int32_t disasmFail;
|
||||
uint32_t codeLen;
|
||||
size_t maxLine;
|
||||
int maxMnemonicLen;
|
||||
int maxOperandLen;
|
||||
uint8_t maxAsmBytes;
|
||||
|
||||
CpuArchitecture cpuArch;
|
||||
};
|
||||
|
||||
struct AddrStat
|
||||
{
|
||||
uint64_t local;
|
||||
uint64_t ext;
|
||||
|
||||
AddrStat& operator+=( const AddrStat& other )
|
||||
{
|
||||
local += other.local;
|
||||
ext += other.ext;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct AddrStatData
|
||||
{
|
||||
AddrStat ipTotalSrc = {};
|
||||
AddrStat ipTotalAsm = {};
|
||||
AddrStat ipMaxSrc = {};
|
||||
AddrStat ipMaxAsm = {};
|
||||
AddrStat hwMaxSrc = {};
|
||||
AddrStat hwMaxAsm = {};
|
||||
unordered_flat_map<uint64_t, AddrStat> ipCountSrc, ipCountAsm;
|
||||
unordered_flat_map<uint64_t, AddrStat> hwCountSrc, hwCountAsm;
|
||||
};
|
||||
|
||||
DisasmData Disassemble( uint64_t symAddr, const Worker& worker );
|
||||
std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::vector<std::string>& sources, uint64_t symAddr, const AddrStatData& as, const unordered_flat_map<uint64_t, uint32_t>& locMap );
|
||||
nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& view );
|
||||
|
||||
void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
|
||||
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "TracyPrint.hpp"
|
||||
#include "TracyImGui.hpp"
|
||||
@@ -127,25 +128,54 @@ void PrintSource( const std::vector<Tokenizer::Line>& lines )
|
||||
}
|
||||
}
|
||||
|
||||
bool PrintTextWrapped( const char* text, const char* end )
|
||||
bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bool underline )
|
||||
{
|
||||
bool hovered = false;
|
||||
|
||||
if( !end ) end = text + strlen( text );
|
||||
|
||||
auto firstWord = text;
|
||||
while( firstWord < end && *firstWord == ' ' ) firstWord++;
|
||||
while( firstWord < end && *firstWord != ' ' && *firstWord != '\n' ) firstWord++;
|
||||
|
||||
auto fontSize = ImGui::GetFontSize();
|
||||
const auto fontSize = ImGui::GetFontSize();
|
||||
const auto fontSize05 = round( fontSize * 0.5f );
|
||||
const auto scale = GetScale();
|
||||
const auto color = ImGui::ColorConvertFloat4ToU32( ImGui::GetStyle().Colors[ImGuiCol_Text] );
|
||||
|
||||
auto left = ImGui::GetContentRegionAvail().x;
|
||||
auto fwLen = ImGui::CalcTextSize( text, firstWord ).x;
|
||||
if( fwLen > left )
|
||||
{
|
||||
const auto textPrev = text;
|
||||
while( text < firstWord && *text == ' ' ) text++;
|
||||
|
||||
const auto prev = left;
|
||||
ImGui::NewLine();
|
||||
left = ImGui::GetContentRegionAvail().x;
|
||||
if( left == prev )
|
||||
{
|
||||
ImGui::SameLine( 0, 0 ); // undo NewLine
|
||||
text = textPrev;
|
||||
}
|
||||
}
|
||||
|
||||
auto endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
|
||||
ImGui::TextUnformatted( text, endLine );
|
||||
if( strikethrough || underline )
|
||||
{
|
||||
auto y1 = ImGui::GetCursorScreenPos().y + fontSize05;
|
||||
auto y2 = ImGui::GetCursorScreenPos().y + fontSize;
|
||||
auto x0 = ImGui::GetCursorScreenPos().x - scale;
|
||||
ImGui::TextUnformatted( text, endLine );
|
||||
ImGui::SameLine( 0, 0 );
|
||||
auto x1 = ImGui::GetCursorScreenPos().x + scale;
|
||||
ImGui::NewLine();
|
||||
if( strikethrough ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y1 ), ImVec2( x1, y1 ), color, scale );
|
||||
if( underline ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y2 ), ImVec2( x1, y2 ), color, scale );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextUnformatted( text, endLine );
|
||||
}
|
||||
if( !hovered ) hovered = ImGui::IsItemHovered();
|
||||
|
||||
left = ImGui::GetContentRegionAvail().x;
|
||||
@@ -155,11 +185,46 @@ bool PrintTextWrapped( const char* text, const char* end )
|
||||
if( *text == ' ' ) text++;
|
||||
endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
|
||||
if( text == endLine ) endLine++;
|
||||
ImGui::TextUnformatted( text, endLine );
|
||||
if( strikethrough || underline )
|
||||
{
|
||||
auto y1 = ImGui::GetCursorScreenPos().y + fontSize05;
|
||||
auto y2 = ImGui::GetCursorScreenPos().y + fontSize;
|
||||
auto x0 = ImGui::GetCursorScreenPos().x - scale;
|
||||
ImGui::TextUnformatted( text, endLine );
|
||||
ImGui::SameLine( 0, 0 );
|
||||
auto x1 = ImGui::GetCursorScreenPos().x + scale;
|
||||
ImGui::NewLine();
|
||||
if( strikethrough ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y1 ), ImVec2( x1, y1 ), color, scale );
|
||||
if( underline ) ImGui::GetWindowDrawList()->AddLine( ImVec2( x0, y2 ), ImVec2( x1, y2 ), color, scale );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextUnformatted( text, endLine );
|
||||
}
|
||||
if( !hovered ) hovered = ImGui::IsItemHovered();
|
||||
}
|
||||
|
||||
return hovered;
|
||||
}
|
||||
|
||||
bool DragHeightSplitter( const char* id, float& height, float minHeight, float maxHeight, float thickness )
|
||||
{
|
||||
ImGui::InvisibleButton( id, ImVec2( -1, thickness * 1.5f ) );
|
||||
const bool active = ImGui::IsItemActive();
|
||||
if( active ) height = std::clamp( height + ImGui::GetIO().MouseDelta.y, minHeight, maxHeight );
|
||||
if( ImGui::IsItemHovered() || active ) ImGui::SetMouseCursor( ImGuiMouseCursor_ResizeNS );
|
||||
|
||||
auto color = ImGui::GetColorU32( ImGuiCol_Separator );
|
||||
if( active ) color = ImGui::GetColorU32( ImGuiCol_SeparatorActive );
|
||||
else if( ImGui::IsItemHovered() ) color = ImGui::GetColorU32( ImGuiCol_SeparatorHovered );
|
||||
|
||||
auto draw = ImGui::GetWindowDrawList();
|
||||
const auto p0 = ImGui::GetItemRectMin();
|
||||
const auto p1 = ImGui::GetItemRectMax();
|
||||
const float y = ( p0.y + p1.y ) * 0.5f;
|
||||
draw->AddLine( ImVec2( p0.x, y ), ImVec2( p1.x, y ), color, thickness );
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include "../public/common/TracyForceInline.hpp"
|
||||
#include "IconsFontAwesome6.h"
|
||||
#include "IconsFontAwesome7.h"
|
||||
#include "TracySourceTokenizer.hpp"
|
||||
|
||||
ImTextureID GetProfilerIconTexture();
|
||||
|
||||
#if !IMGUI_DEFINE_MATH_OPERATORS
|
||||
static inline ImVec2 operator+( const ImVec2& l, const ImVec2& r ) { return ImVec2( l.x + r.x, l.y + r.y ); }
|
||||
static inline ImVec2 operator-( const ImVec2& l, const ImVec2& r ) { return ImVec2( l.x - r.x, l.y - r.y ); }
|
||||
@@ -32,7 +34,8 @@ void DrawZigZag( ImDrawList* draw, const ImVec2& wpos, double start, double end,
|
||||
void DrawStripedRect( ImDrawList* draw, const ImVec2& wpos, double x0, double y0, double x1, double y1, double sw, uint32_t color, bool fix_stripes_in_screen_space, bool inverted );
|
||||
void DrawHistogramMinMaxLabel( ImDrawList* draw, int64_t tmin, int64_t tmax, ImVec2 wpos, float w, float ty );
|
||||
void PrintSource( const std::vector<Tokenizer::Line>& lines );
|
||||
bool PrintTextWrapped( const char* text, const char* end = nullptr );
|
||||
bool PrintTextWrapped( const char* text, const char* end, bool strikethrough, bool underline );
|
||||
bool DragHeightSplitter( const char* id, float& height, float minHeight, float maxHeight, float thickness );
|
||||
|
||||
|
||||
static constexpr const uint32_t SyntaxColors[] = {
|
||||
@@ -119,7 +122,7 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
|
||||
ImGui::TextUnformatted( value );
|
||||
}
|
||||
|
||||
[[maybe_unused]] static inline void DrawWaitingDots( double time )
|
||||
[[maybe_unused]] static inline void DrawWaitingDotsCentered( double time )
|
||||
{
|
||||
s_wasActive = true;
|
||||
ImGui::TextUnformatted( "" );
|
||||
@@ -133,6 +136,19 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
|
||||
draw->AddCircleFilled( wpos + ImVec2( w * 0.5f + ty, h ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f - 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
|
||||
}
|
||||
|
||||
[[maybe_unused]] static inline void DrawWaitingDots( double time, bool windowPos = true, bool small = false )
|
||||
{
|
||||
s_wasActive = true;
|
||||
const auto pos = ( windowPos ? ImGui::GetWindowPos() : ImVec2( 0, 0 ) ) + ImGui::GetCursorPos();
|
||||
auto draw = ImGui::GetWindowDrawList();
|
||||
const auto ty = ImGui::GetTextLineHeight();
|
||||
const auto yOffset = ty * ( small ? 0.5f : 0.675f );
|
||||
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 0 * ty, yOffset ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f + 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
|
||||
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 1 * ty, yOffset ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
|
||||
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 2 * ty, yOffset ), ty * ( 0.15f + 0.2f * ( pow( cos( time * 3.5f - 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
|
||||
ImGui::Dummy( ImVec2( ty * 3, ty ) );
|
||||
}
|
||||
|
||||
[[maybe_unused]] static inline bool SmallCheckbox( const char* label, bool* var )
|
||||
{
|
||||
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
|
||||
@@ -263,13 +279,13 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
|
||||
[[maybe_unused]] static tracy_force_inline void DrawLine( ImDrawList* draw, const ImVec2& v1, const ImVec2& v2, uint32_t col, float thickness = 1.0f )
|
||||
{
|
||||
const ImVec2 data[2] = { v1, v2 };
|
||||
draw->AddPolyline( data, 2, col, 0, thickness );
|
||||
draw->AddPolyline( data, 2, col, thickness );
|
||||
}
|
||||
|
||||
[[maybe_unused]] static tracy_force_inline void DrawLine( ImDrawList* draw, const ImVec2& v1, const ImVec2& v2, const ImVec2& v3, uint32_t col, float thickness = 1.0f )
|
||||
{
|
||||
const ImVec2 data[3] = { v1, v2, v3 };
|
||||
draw->AddPolyline( data, 3, col, 0, thickness );
|
||||
draw->AddPolyline( data, 3, col, thickness );
|
||||
}
|
||||
|
||||
[[maybe_unused]] static tracy_force_inline void TooltipIfHovered( const char* text )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,14 +20,23 @@ class TracyLlmApi;
|
||||
class TracyLlmChat;
|
||||
class TracyLlmTools;
|
||||
class TracyManualData;
|
||||
class View;
|
||||
class Worker;
|
||||
|
||||
struct LlmSkill
|
||||
{
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
class TracyLlm
|
||||
{
|
||||
enum class Task
|
||||
{
|
||||
Connect,
|
||||
SendMessage,
|
||||
FastMessage,
|
||||
Tokenize
|
||||
};
|
||||
|
||||
@@ -37,64 +46,87 @@ class TracyLlm
|
||||
std::function<void()> callback;
|
||||
std::function<void(nlohmann::json)> callback2;
|
||||
std::string param;
|
||||
nlohmann::json param2;
|
||||
bool stop;
|
||||
};
|
||||
|
||||
public:
|
||||
TracyLlm( Worker& worker, const TracyManualData& manual );
|
||||
TracyLlm( Worker& worker, View& view, const TracyManualData& manual );
|
||||
~TracyLlm();
|
||||
|
||||
[[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_lock ); return m_busy; }
|
||||
[[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_jobsLock ); return m_busy; }
|
||||
|
||||
void Draw();
|
||||
void AddAttachment( std::string&& str, const char* role );
|
||||
void AddMessage( std::string&& str, const char* role );
|
||||
bool QueueSendMessage();
|
||||
|
||||
bool m_show = false;
|
||||
|
||||
void AddAttachmentLocking( std::string&& str, const char* role );
|
||||
void AddMessageLocking( std::string&& str, const char* role );
|
||||
bool QueueSendMessageLocking();
|
||||
bool QueueFastMessageLocking( const nlohmann::json& req, std::function<void(nlohmann::json)> callback );
|
||||
|
||||
private:
|
||||
void AddMessage( std::string&& str, const char* role );
|
||||
bool QueueSendMessage();
|
||||
bool QueueFastMessage( const nlohmann::json& req, std::function<void(nlohmann::json)> callback );
|
||||
|
||||
void WorkerThread();
|
||||
|
||||
void UpdateModels();
|
||||
void ResetChat();
|
||||
void UpdateSystemPrompt();
|
||||
|
||||
void QueueConnect();
|
||||
|
||||
void AddMessageBlocking( std::string&& str, const char* role, std::unique_lock<std::mutex>& lock );
|
||||
// Will block, cannot enter with a taken lock
|
||||
void AddMessageBlocking( std::string&& str, const char* role );
|
||||
void AddMessageBlocking( nlohmann::json&& json );
|
||||
|
||||
void ManageContext( std::unique_lock<std::mutex>& lock );
|
||||
void SendMessage( std::unique_lock<std::mutex>& lock );
|
||||
void ManageContext();
|
||||
void SendMessage();
|
||||
|
||||
void AppendResponse( const char* name, const nlohmann::json& delta );
|
||||
bool OnResponse( const nlohmann::json& json );
|
||||
|
||||
void AddSkill( std::string&& name, std::string&& description, const std::shared_ptr<EmbedData>& content );
|
||||
|
||||
std::unique_ptr<TracyLlmApi> m_api;
|
||||
std::unique_ptr<TracyLlmChat> m_chatUi;
|
||||
std::unique_ptr<TracyLlmTools> m_tools;
|
||||
|
||||
int m_modelIdx;
|
||||
int m_fastIdx;
|
||||
int m_embedIdx;
|
||||
|
||||
std::atomic<bool> m_exit;
|
||||
std::condition_variable m_cv;
|
||||
std::thread m_thread;
|
||||
|
||||
mutable std::mutex m_lock;
|
||||
mutable std::mutex m_jobsLock;
|
||||
std::vector<std::shared_ptr<WorkItem>> m_jobs;
|
||||
std::shared_ptr<WorkItem> m_currentJob;
|
||||
|
||||
bool m_busy = false;
|
||||
bool m_focusInput = false;
|
||||
int m_chatId = 0;
|
||||
std::atomic<int> m_chatId {0};
|
||||
int m_usedCtx = 0;
|
||||
float m_temperature = 1.0f;
|
||||
bool m_setTemperature = false;
|
||||
bool m_allThinkingRegions = false;
|
||||
|
||||
char* m_input;
|
||||
char* m_apiInput;
|
||||
std::mutex m_chatLock;
|
||||
std::vector<nlohmann::json> m_chat;
|
||||
std::string m_summary;
|
||||
std::string m_suggestion;
|
||||
|
||||
std::vector<LlmSkill> m_skills;
|
||||
std::shared_ptr<EmbedData> m_systemPrompt;
|
||||
std::shared_ptr<EmbedData> m_systemReminder;
|
||||
nlohmann::json m_toolsJson;
|
||||
|
||||
Worker& m_worker;
|
||||
View& m_view;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ void TracyLlmApi::SetupCurl( void* curl )
|
||||
curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1L );
|
||||
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
|
||||
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
|
||||
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 300 );
|
||||
curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, 5 );
|
||||
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 1200 );
|
||||
curl_easy_setopt( curl, CURLOPT_USERAGENT, "Tracy Profiler" );
|
||||
}
|
||||
|
||||
@@ -74,20 +75,6 @@ bool TracyLlmApi::Connect( const char* url )
|
||||
m_models.back().quant = json2["quantization"].get_ref<const std::string&>();
|
||||
if( json2.contains( "loaded_context_length" ) ) m_models.back().contextSize = json2["loaded_context_length"].get<int>();
|
||||
}
|
||||
else if( ( m_type == Type::Unknown || m_type == Type::Ollama ) && PostRequest( m_url + "/api/show", "{\"name\":\"" + id + "\"}", buf2 ) == 200 )
|
||||
{
|
||||
m_type = Type::Ollama;
|
||||
auto json2 = nlohmann::json::parse( buf2 );
|
||||
m_models.back().quant = json2["details"]["quantization_level"].get_ref<const std::string&>();
|
||||
for( auto& cap : json2["capabilities"] )
|
||||
{
|
||||
if( cap.get_ref<const std::string&>() == "embedding" )
|
||||
{
|
||||
m_models.back().embeddings = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( m_type == Type::Unknown )
|
||||
{
|
||||
m_type = Type::Other;
|
||||
@@ -262,6 +249,32 @@ int TracyLlmApi::Tokenize( const std::string& text, int modelIdx )
|
||||
return -1;
|
||||
}
|
||||
|
||||
nlohmann::json TracyLlmApi::SendMessage( const nlohmann::json& chat, int modelIdx )
|
||||
{
|
||||
assert( m_curl );
|
||||
|
||||
nlohmann::json req = {
|
||||
{ "model", m_models[modelIdx].name },
|
||||
{ "messages", chat },
|
||||
{ "chat_template_kwargs", {
|
||||
{ "enable_thinking", false }
|
||||
} }
|
||||
};
|
||||
|
||||
auto data = req.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace );
|
||||
std::string buf;
|
||||
auto res = PostRequest( m_url + "/v1/chat/completions", data, buf, true );
|
||||
|
||||
try
|
||||
{
|
||||
return nlohmann::json::parse( buf );
|
||||
}
|
||||
catch( const std::exception& )
|
||||
{
|
||||
return { { "response", buf } };
|
||||
}
|
||||
}
|
||||
|
||||
int64_t TracyLlmApi::GetRequest( const std::string& url, std::string& response )
|
||||
{
|
||||
assert( m_curl );
|
||||
|
||||
@@ -23,7 +23,6 @@ class TracyLlmApi
|
||||
enum class Type
|
||||
{
|
||||
Unknown,
|
||||
Ollama,
|
||||
LmStudio,
|
||||
LlamaSwap,
|
||||
Other
|
||||
@@ -36,6 +35,7 @@ public:
|
||||
bool ChatCompletion( const nlohmann::json& req, const std::function<bool(const nlohmann::json&)>& callback, int modelIdx );
|
||||
bool Embeddings( const nlohmann::json& req, nlohmann::json& response, bool separateConnection = false );
|
||||
[[nodiscard]] int Tokenize( const std::string& text, int modelIdx );
|
||||
[[nodiscard]] nlohmann::json SendMessage( const nlohmann::json& chat, int modelIdx );
|
||||
|
||||
[[nodiscard]] bool IsConnected() const { return m_curl != nullptr; }
|
||||
[[nodiscard]] const std::vector<LlmModel>& GetModels() const { return m_models; }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user