thag_profiler 1.0.1

A lightweight, cross-platform Rust code profiling toolkit with zero overhead when disabled
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
# thag_profiler

An accurate lightweight cross-platform profiling library for Rust applications, offering time and/or memory profiling with minimal boilerplate and your choice of color schemes.

An independent offshoot of the `thag(_rs)` script runner and REPL.

Why choose `thag_profiler`?:

 - quick and easy to set up and run

 - clear and accurate interactive flamegraphs

 - time and memory profiling

 - detailed profiling option for memory hotspots

 - any mix of synchronous and asynchronous code

 - pure Rust with no platform dependencies

---

[![Rust-themed flamechart](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamechart_rust_time.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamechart_rust_time.svg)<br>
*Time profile in `inferno` "Rust" color scheme. Click on image for interactive version with clickable bars and search.*

---

Instrumentation:

- `#[enable_profiling]` attribute for your lowest-level profiled function, typically `main`

- `#[profiled]` attribute for other functions

- `profile!` ... `end!` macro pairs for code sections.

  Each of these items offers a range of options for any combination of time, memory summary and memory detail profiling.

Instrumentation tools:

- `thag_instrument` command to add the attributes to every function and method of a .rs file

- `thag_uninstrument` command to remove them.

Output analysis:

- `thag_profile` to select, filter and display:

  - interactive flamegraphs, flamecharts, comparisons

  - function statistics for time profiles

  - allocation statistics and size distribution for memory profiles

---

[![Memory flamegraph](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamegraph_mem_20250624-141421.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamegraph_mem_20250624-141421.svg)<br>
*Filtered memory profile in <code>inferno</code> "memory" color scheme with `count_words` function profiled in detail. Click on image for interactive version with clickable bars and search.*

---

## 🎬 Demo: Profiled from scratch in 1 minute

### Part 1: Instrument, profile and start analysis (1 min 0 sec)

[![asciicast](https://asciinema.org/a/YP6mW01gYgW9EgZ5c01hL0vhc.svg)](https://asciinema.org/a/YP6mW01gYgW9EgZ5c01hL0vhc)

*Click to watch: The demo shows building and running an instrumented program, then generating an interactive flamegraph*

*Tip: View in full-screen mode for best legibility*

Use ***`f`*** to toggle full-screen mode.

<details>
<summary>📋 **Detailed steps shown in video**</summary>

1. **Instrument** - Auto-add profiling with `thag_instrument`

2. **View changes** - See the diff in `vimdiff`

3. **Run** - Execute the instrumented program as normal

    We run with `thag` because it's a stand-alone `thag script`
4. **Analyze 1: select** - Select analysis type and data with `thag_profile`

    We could have chosen memory and/or a sequential flamechart instead.
5. **Analyze 2: generate** - Select flamegraph color scheme, generate and open in browser

</details>

- Since the lightweight `asciinema` tool can't see the browser, we exit at this point and return to view it interactively in Part 2.

### Part 2: View and interact with the flamegraph (21 sec)

<details>
<summary>📋 **Try this**</summary>

1. **Hover to see details** and see the following displayed for each one as a tooltip and also in the bottom area:

    a. function name, preceded by `async` if applicable

    b. elapsed time in microseconds

    c. percentage of total elapsed time that this bar accounts for.

2. **Search for functions** Find the `count_words` function:

      - Click on the `Search` link in the top right corner, enter `count` in the dialog that pops up, and press Enter. Bars with matching text will display in pink. Notice that the `count_words` function appears in two different call stacks.

3. **Expand bars and undo** Click on one of the pink bars and see how it expands to the full width of the screen, with its call stack below it.

    Click on the `main` or `all` bar at the bottom to restore the full view.

    Click on the other pink bar and see it do the same, showing its slightly different call stack below it.

4. **Reset view** To remove the pink highlighting, click on the `Reset` link.

</details>

[![Flamegraph from thag_profiling](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/thag_profile_demo.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/thag_profile_demo.svg)<br>
*Interactive flamegraph showing execution time across nested function calls. Click image for interactive version with clickable bars and search.*

---

## Features

- **Zero-cost abstraction**: No runtime overhead when `thag_profiler`'s profiling features are turned off.

- **Execution time profiling**: Low-overhead profiling to highlight hotspots. Profiler overhead is stripped out for accuracy even at small scale.

- **Accurate memory profiling**: Memory allocations are accurately tracked at line number level and ring-fenced from profiler code so that the latter can't distort the measurements. Allocations may be summarized by function or section, or broken out in detail where desired.

- **Function and section profiling**: Profiling can be applied to any number of specific non-overlapping code sections, down to single instructions.

- **Async support**: Seamlessly works with `tokio` or other async code.

- **Automatic instrumentation**: Tools to quickly bulk add and remove profiling annotations to/from source code without losing comments or formatting.

- **Interactive flamegraphs and flamecharts**: Visualize performance bottlenecks with `inferno` flamegraphs and flamecharts, and easily do before-and-after comparisons using `inferno` differential flamegraphs.

- **Graph filtering**: Filter out dead or unwanted sections of flamegraphs and flamecharts for a better view.

- **Proc macro based**: All instrumentation is provided via proc macros that provide a simple flexible interface, precise control, ring-fencing of profiler code from user code and zero-cost abstractions when profiling features are turned off.

- **Development or release build profiling**: Although `thag_profiler` is focused on the development cycle, it supports profiling release builds, subject to enabling debug information and to any limitations imposed by the `backtrace` crate.

- **One-line full detailed transitive memory profiling option**: Track all memory allocations and deallocations for your project and dependencies with a single `#[enable_profiling(runtime)]` and the `THAG_PROFILER` environment variable.

- **Intelligent output naming**: Output files are named with the source program, timestamp and profile type for quick and easy selection.

- **Cross-platform**: Works on macOS, Linux and Windows.

## Installation

Add `thag_profiler` to your `Cargo.toml`:

For inactive instrumentation, no features are needed:

```toml
[dependencies]
thag_profiler = "0.1.0"
```

To activate time profiling alone, you need the `time_profiling` feature:

```toml
thag_profiler = { version = "0.1.0", features = ["time_profiling"] }
```

For comprehensive profiling (memory and optionally time), you need the `full_profiling` feature:

```toml
thag_profiler = { version = "0.1.0", features = ["full_profiling"] }
```

Install the profiling tools:

```bash
# Install all tools
cargo install thag_profiler --features=tools

# Or install tools individually
cargo install thag_profiler --features=instrument_tool --bin thag_instrument
cargo install thag_profiler --features=instrument_tool --bin thag_uninstrument
cargo install thag_profiler --features=analyze_tool --bin thag_profile
```

## Getting Started

### 1. Instrument Your Code for Profiling

You can instrument your code permanently or semi-permanently, and activate profiling via a feature only as and when needed.

#### a. Automatically instrument your code:

Replace `2021` below with your project's Rust edition:

```bash
thag_instrument 2021 < path/to/your/file.rs > path/to/your/instrumented_file.rs
```

* Ensure your original source is backed up or committed before instrumenting.

* Replace `2021` with your project's Rust edition.

* Do NOT redirect the output to your source file.

* Compare generated code with the original to ensure correctness before overwriting any original code with instrumented code.

Repeat for all modules you want to profile.

####     ... AND / OR ...

#### b. Manually add profiling annotations:

**Imports**

```rust
use thag_profiler::{enable_profiling, profile, profiled};
```

**Enable profiling**

NB: exactly **one function** in your app should be annotated with `#[enable_profiling]`. Normally this is `fn main`, but it is possible to annotate a different function instead, to narrow the scope of profiling to a section of particular interest.

```rust
// Enable profiling for the program.
// To disable it while keeping the instrumentation, you can either
// disable the profiling features in the `thag_profiler` dependency
// or simply specify `#[enable_profiling(no)]`.
#[enable_profiling]
fn main() -> u64 {
    // Function code...
    42
}
```

**Instrument a function**

```rust
// Instrument a function
#[profiled]
fn expensive_calculation() -> u64 {
    // Function code...
    42
}
```

**Profile a section**

```rust
// Profile a specific section with `profile!` and matching `end!`
#[profiled] // Optional
fn complex_operation() {
    // Some code...

    profile!(expensive_part);
    // Expensive operation
    ...
    end!(expensive_part);

    // More code...
}
```

**Profile a section of an async function**

For a section in a profiled async function, it's best to add `async_fn` as a second argument, as explained in the _Best Practices_ section of this document.

```rust
// Profile a specific section of an async function
#[profiled] // Optional
async fn complex_async_operation() {
    // Some code...

    profile!(expensive_part, async_fn);
    // Expensive operation
    ...
    end!(expensive_part);

    // More code...
}
```

**Profile to the end of a function with `unbounded`**

```rust
// Profile the remainder of a function
#[profiled] // Optional
fn complex_operation() {
    // Some code...

    // Must be scoped to end of function
    profile!(rest_of_function, unbounded);
    // All code to end of function will be profiled
}

// 🚫 INCORRECT:
#[profiled] // Optional
fn complex_operation() {
    // Some code...

    {
        // ⚠️ Unbounded keyword misused here
        profile!(rest_of_block, unbounded); // 🚫
    }   // ⚠️ Profile will be dropped here unknown to allocation tracker

    // ⚠️ The following section profiling may not work correctly due to the above
    profile!(another_section);
    // Expensive operation
    ...
    end!(another_section);
}
```

### 2. Enable the Profiling Feature

In order to activate profiling, the desired `thag_profiler` feature - `time_profiling` or `full_profiling` - must be enabled at build time in one of two ways:

1. Via a `features` keyword on the `thag_profiler` dependency in the manifest (Cargo.toml or `thag` toml block).

2. Via a command-line `--features` option.

#### In regular Cargo projects

  **1. Cargo.toml only**:

  Either expose it directly in the dependency:

```toml
[dependencies]
thag_profiler = { version = "0.1", features = ["full_profiling"] }
```

  or via a feature of your own project, with a default:

```toml
[dependencies]
thag_profiler = "0.1.0"

[features]
my_profiling = ["thag_profiler/time_profiling"]
default = [my_profiling]
```

  **OR**

  **2. Cargo.toml and command line**:

  Either via a feature of your own project as above, but without specifying a default:

```toml
[dependencies]
thag_profiler = "0.1.0"

[features]
my_profiling = ["thag_profiler/time_profiling"]
```

then run with:

```bash
cargo run --features my_profiling
```

  OR directly via the command line:

```toml
[dependencies]
thag_profiler = "0.1.0"
```

then run with:

```bash
cargo run --features thag_profiler/time_profiling
```


#### In scripts run with the `thag(_rs)` script runner

  This section applies only to the `thag(_rs)` script runner [thag](../README.md), of which `thag_profiler` is an offshoot. You can safely ignore it if you're not using `thag(_rs)`.

  Snippets may be profiled provided they have a function to be annotated with `#[enable_profiling]`, since the generated `fn main` will be unannotated. For an example see `demo/factorial_ibig_product_instr.rs`.

  When using `thag_profiler` in `thag` scripts, for a start you have the same two options as above, except for using a `toml` block in place of a `Cargo.toml`. You also have a third option using only dependency inference and configuration:

  **1. Manifest (toml block) only**:

  Either expose it directly in the dependency:

```rust
/*[toml]
[dependencies]
thag_profiler = { version = "0.1", features = ["time_profiling"] }
*/
```

  or via a feature of your own script, with a default:

```toml
/*[toml]
[dependencies]
thag_profiler = "0.1.0"

[features]
my_profiling = ["thag_profiler/full_profiling"]
default = [my_profiling]
*/
```

  **OR**

  **2. Manifest (toml block) and command line**:

  Sample script configuration:

  Either via a feature of your own script as above, but without specifying a default:

```toml
/*[toml]
[dependencies]
thag_profiler = "0.1.0"

[features]
# For time profiling only
my_profiling = ["thag_profiler/time_profiling"]

# OR for comprehensive profiling (time + memory)
my_profiling = ["thag_profiler/full_profiling"]
*/
```

  then run with:

```bash
cargo run bank/mem_prof.rs --features=my_profiling
```

  OR directly via the command line:

```rust
/*[toml]
[dependencies]
thag_profiler = "0.1.0"
*/
```

  then run with:

```bash
cargo run bank/mem_prof.rs --features thag_profiler/full_profiling
```

  **OR**

  **3. Dependency inference and/or default feature configuration**:

  The *dependency* may be omitted and will be inferred either from imports (`use thag_profiler::{enable_profiling, ...};`) or if you use the qualified forms of the macros, for example #`[thag_profiler::enable_profiling]`.

  The *feature* may be configured as as a default in `~/.config/thag_rs/config.toml`, which you can conveniently edit via `thag -C`.

```toml
[dependencies.feature_overrides.thag_profiler]
required_features = ["full_profiling"]
```

  Alternatively you can specify it in a toml block in your script, even in combination with dependency inference:

```toml
/*[toml]
[features]
default = ["thag_profiler/full_profiling"]
*/
```

  As the examples show, you may pair any dependency option with any feature option, as long as `thag` is able to pick up both the `thag_profiler` dependency itself and the desired `thag_profiler` feature.


### 3. Run Your Application

Ensure that the `thag_profiler` feature you need is enabled by one of the methods discussed above. If using `cargo run` from the command line, and the desired feature is not specified by the manifest, you need to do so via the `--features` argument of `cargo run`.

If your app is annotated with `#[enable_profiling(runtime)]`, specify the environment variable `THAG_PROFILER=[profile_type][,[output_dir][,[debug_level][,memory_detail]]]`*.

*In other words, there are 4 parameters, all optional. Comma separators are required as placeholders but trailing commas may be dropped.

If using `thag` to run a script annotated with `#[enable_profiling(runtime)]` and features not specified in a toml block or configured:

`THAG_PROFILER=both,,announce thag --features=thag_profiler/full_profiling demo/document_pipeline_profile.rs -ft`

If using `thag` to run a script annotated with `#[enable_profiling]` and features specified in a toml block or configured via `thag -C`:

`thag demo/document_pipeline_profile.rs -ft`

### 4. Analyze Results

When you run your application with profiling enabled, `thag_profiler` generates folded stack files in the current working directory, unless that location is overridden by the second argument of a `THAG_PROFILER` environment variable used in conjunction with `#[enable_profiling(runtime)]`.

Use the included analysis tool to visualize the results:

```bash
thag_profile <output_dir>
```

By default:

```bash
thag_profile .
```

This opens an interactive menu to explore your profiling data and display various flamegraphs, flamecharts or simple statistics.

`thag_profile` uses two extremely helpful third-party crates:

  - `inquire` to help you select output for analysis and optionally filter out any unwanted functions.

  - `inferno` to display the results in your browser as interactive flamegraphs and flamecharts.


For time profiles you can also choose to display function statistics (inclusive or exclusive of children according to the `.folded` file naming convention), and ranked by total duration or call frequency.

Flamegraphs are not offered for `*-inclusive.folded` files, since the bar lengths and displayed values would show multiple counting of descendants (double counting at every level of the stack), so these files are only useful for the statistics report.

For memory profiles you can also choose to display memory statistics and an allocation size analysis.


## Detailed Usage

### Manually Instrumenting Code for Profiling

While the `thag_instrument` tool should very quickly provide good default instrumentation, this section describes how to fine-tune the profiling to suit your needs.

In addition to enabling the appropriate `thag_profiler` feature, you must enable profiling in your code by adding the `#[enable_profiling]` attribute to the top-level function to be profiled, which is preferably but not necessarily the `main` function.

If using this attribute to annotate any function other than `main`, you need to take extra care. Annotating more than one function with `#[enable_profiling]` is not supported and behavior is undefined in such a case. The same applies to annotating an async function or a descendant of an async function with `#[enable_profiling]`, if this could cause overlap in their execution. It is safer to do the conventional thing and annotate the function in question with `#[profiled]` and the `main` function with `#[enable_profiling]`.

**NB:** The `#[enable_profiling]` attribute also profiles the function it annotates, so the `#[profiled]` attribute need not and should not be specified on the same function.

**#[enable_profiling] arguments**

The following optional arguments are available:

- `both`: Specifies both time and memory profiling.

- `memory`: Specifies memory profiling only.

- `time`: Specifies time profiling only.

- `no`: Disables profiling as a convenient alternative to disabling the profiling features of the `thag_profiler` dependency. Unlike disabling the features, this only provides zero-cost abstraction for the current function. However, at runtime the profile instantiation code generated by the other macros immediately returns `None` instead of `Some(Profile)` when profiling is disabled, so the overhead is still very slight.

- `yes`: (default) Enables profiling according to the feature specified for the `thag_profiler` dependency, which must be either `full_profiling` or `time_profiling`.

- `runtime`: Specifies that a detailed specification is to be provided at runtime via the `THAG_PROFILER` environment variable. This is the only option that allows you to influence profiling at runtime. This includes switching profiling off, thus trading the efficiency of zero-cost abstraction for the flexibility of runtime configuration. That being said, the overhead is still very small, for the reasons stated under the `no` option above.

- `function(...)`: Configures profiling options specific to the current function. Within the parentheses, you can specify any of the arguments that would be accepted by the `#[profiled]` attribute: `time`, `mem_summary`, `mem_detail`, `both`, `global`, `test`

Examples:

Basic memory profiling:

```rust
#[enable_profiling(memory)]
fn main() {
...
}
```

Enable memory profiling for the program, together with detailed memory profiling for the function itself.
  Detailed memory profiling picks up all descendant functions as a matter of course, but you may
still choose to annotate any of them with #[profiled] for time profiling:

```rust
#[enable_profiling(memory, function(mem_detail))]
fn process_data() {
...
}
```

Runtime global profiling with function-specific time and detailed memory profiling:

```rust
#[enable_profiling(runtime, function(time, mem_detail))]
fn main() {
...
}
```

**Format of the `THAG_PROFILER` environment variable to be used with `#[enable_profiling(runtime)]`**

The `THAG_PROFILER` environment variable has 4 optional positional comma-separated arguments.

If `#[enable_profiling(runtime)]` is
specified but either the environment variable or its first argument is missing, no profiling is done.

Format:

`THAG_PROFILER=[profile_type][,[output_dir][,[debug_level][,memory_detail]]]`

*All parameters are optional. Commas are required as placeholders, but trailing commas may be dropped.*

| Parameter      | Description |
|----------------|-------------|
| `profile_type` | `both`, `memory`, `time`, `none` (default) |
| `output_dir` *(optional)* | Output dir for `.folded` files. Default: current directory |
| `debug_level` *(optional)* | `none` (default), `announce`, or `quiet`. Logs to `temp_dir()/thag_profiler/...`. Requires `debug_logging` feature |
| `memory_detail` *(optional)* | `true` = detailed `.folded`; `false` (default) = minimal |

Examples:

```bash
THAG_PROFILER=both,$TMPDIR,announce,true cargo run
```

  ...specifies both memory and time profiling, `.folded` files to $TMPDIR, debug log path to be written to user program output, and full memory detail allocation and deallocation tracking (caution: may be slow) into separate `.folded` files.


```bash
THAG_PROFILER=time cargo run
```

  ...specifies time profiling only, `.folded` files to current directory, no debug log, no detailed memory files as not applicable to time profiling.


```bash
THAG_PROFILER=memory,,quiet thag demo/document_pipeline_profile_minimal.rs  -ft
```

  ...runs `thag` demo script document_pipeline_profile_minimal.rs with forced rebuild (-f) and timings (-t), memory profiling only, internal debug logging without announcing the log file path, and no detailed output `.folded` files. Note that debug logging requires the `debug_logging` feature of the `thag_profiler` dependency to be enabled, otherwise the `debug_log!` macro compiles to `()` to provide a zero-cost abstraction.


The function annotated with `#[enable_profiling]` is taken to be the root of the profiling callstack.

```rust
#[enable_profiling]
fn main() {
    // Your program...
}
```

### Function Profiling with `#[profiled]`

Add the `#[profiled]` attribute to any function you want to profile:

```rust
use thag_profiler::profiled;
```

Regular functions:

```rust
#[profiled]
fn expensive_calculation() -> u64 {
    // Function code...
}
```

Works with async functions too:

```rust
#[profiled]
async fn fetch_data() -> Result<String, Error> {
    // Async operations...
}
```

Methods in implementations:

```rust
impl MyStruct {
    #[profiled]
    fn process(&self, data: &[u8]) {
        // Method code...
    }
}
```

#### Attribute Options

The `#[profiled]` attribute macro accepts several arguments that configure how profiling is performed.

##### Usage

```rust
#[profiled]
fn my_function() { ... }
```

With arguments:

```rust
#[profiled(time, mem_detail)]
fn my_memory_intensive_function() { ... }
```

##### Available Arguments

| Argument | Description | Feature Required |
|----------|-------------|------------------|
| `time` | Enables time/performance profiling | `time_profiling` |
| `mem_summary` | Enables basic memory profiling | `full_profiling` |
| `mem_detail` | Enables detailed memory profiling | `full_profiling` |
| `both` | Shorthand to enable both time and memory profiling | `full_profiling` |
| `global` | Uses the global profile type setting | `Either` |
| `test` | Special flag for testing - enables clone of profile for test access | `Either` |

##### Notes

- **Argument Order**: The order of arguments doesn't matter - `#[profiled(time, mem_detail)]` is equivalent to `#[profiled(mem_detail, time)]`.

- **Default Behavior**: If no arguments are provided, the macro defaults to using the global profile type setting.

- **Feature Flags**: Memory-related profiling options require the `full_profiling` feature to be enabled. If only `time_profiling` is enabled, all memory profiling arguments are ignored.

- **Combined Profiling**: Using both `time` and any memory option (for example, `mem_detail` or `mem_summary`) is equivalent to using `both`.

##### Examples

Basic time profiling:

```rust
#[profiled(time)]
fn time_sensitive_function() { ... }
```

Detailed memory profiling:

```rust
#[profiled(mem_detail)]
fn memory_intensive_function() { ... }
```

Both time and memory profiling:

```rust
#[profiled(both)]
fn complex_function() { ... }
```

Or equivalently:

```rust
#[profiled(time, mem_summary)]
fn complex_function() { ... }
```

Use the global profile type:

```rust
#[profiled(global)]
fn standard_function() { ... }
```

Default - equivalent to global:

```rust
#[profiled]
fn simple_function() { ... }
```

Special case for tests:

```rust
#[profiled(time, test)]
async fn function_for_testing() { ... }
```

##### Testing Async Functions

For testing async functions with the `#[profiled]` attribute, use one of these approaches:

1. Add the `test` argument: `#[profiled(time, test)]`

2. Add a `_test` suffix to your function name: `async fn my_function_test()`

Both methods allow accessing the profile variable inside async function bodies during tests.

The `#[profiled]` attribute accepts various flags to control profiling behavior:

```rust
// Override the profile type for a specific function
#[profiled(both)]
fn allocating_function() { /* ... */ }
```

#### Order of attributes

If `#[enable_profiling]` is used in conjunction with either `#[tokio::main]` or `#[async_std::main]`, then `#[enable_profiling]` must appear _after_ the other attribute.

#### Interaction with features

If only the `time_profiling` feature is enabled, any memory profiling-specific arguments are invalid because they depend on the `full_profiling` feature. In this case they are ignored rather than raise an error.

For example:

```Rust
#[cfg(feature = "time_profiling")]
#[profiled(mem_detail)] // 🚫 Argument clashes with feature
```

### Code Section Profiling with `profile!` and `end!`

Section profiling with the `profile!` and `end!` macros allows you to profile hotspots within a function, down to single instructions.

#### Rules

1. **Global constraints take precedence**: Section profiling modes are overridden by the program defaults set by `#[enable_profiling]`.

2. **Limited integration with functions**: Profiled sections will have ancestors in the callstack, but no children. A function called from within a profiled section appears in flamegraphs, not as a child of the section but as a child of the parent function and a sibling of the section. This is because profiling hierarchies depend on built-in Rust backtraces, and sections are not a Rust feature but a `thag_profiler` artifact grafted on top of their parent function, and the complexity and overhead of transforming each backtrace to accommodate any sections is not considered worthwhile.

 By the same token, there is no point in using `profile!..end!` purely to wrap a function for memory profiling, as memory allocations are only attributed once and as narrowly as possible, so they show up in the function and not in the section.

3. **No section nesting or overlaps**: Section profiles should not overlap or be nested in code. This is not checked, but memory allocations that fall within the scope of more than one section are attributed to only one of those sections rather than being double-counted.

#### Format

```Rust
profile!(name[, flag1[, flag2[, ...]]]);
```

Parameters

- **name**: A valid Rust identifier to be expanded to the variable name of the `Profile` for the section
- **flags**: Optional comma-separated identifiers that control profiling behavior

#### Available Flags

| Flag | Description |
|------|-------------|
| `time` | Enable time profiling for this section |
| `mem_summary` | Enable basic memory allocation tracking |
| `mem_detail` | Enable detailed memory allocation tracking |
| `async_fn` | Mark that this profile is for an async function |
| `unbounded` | This is equivalent to an `end!` macro at the end of the function |

#### Profile Types

The macro automatically determines the type of profiling based on the flags provided:

- **Time only**: When only the `time` flag is present
- **Memory only**: When `mem_summary` or `mem_detail` is present without `time`
- **Both**: When `time` is combined with either `mem_summary` or `mem_detail`

#### Examples

Basic time profiling:

```rust
profile!(calculate_result, time);
// calculation code here
...
end!(calculate_result);
```

Memory usage summary:

```rust
profile!(load_data, mem_summary);
// data loading code here
...
end!(load_data);
```

Detailed memory tracking:

```rust
profile!(process_image, mem_detail);
// image processing code here
...
end!(process_image);
```

Both time and memory profiling:

```rust
profile!(generate_report, time, mem_detail);
// report generation code here
...
end!(generate_report);
```

Async function profiling:

```rust
profile!(fetch_data, time, async_fn);
// data fetching code here
...
end!(fetch_data);
```

Unbounded memory profile (must NOT be manually ended):

```rust
fn multi_part_function() {
    // other code here
    ...
    profile!(long_running_task, mem_summary, unbounded);
    // long running section to end of function
    ...
} // end of function
```

#### Notes

The macro captures source location information automatically for accurate profiling results.

Section profiling requires either:

  **1. Recommended:** an `end!(<identifier>)` macro to drop the   profile outside of user code and to mark the end of the section so that memory allocations can be
  accurately attributed to the correct section by line number.   This macro invocation must not be outside the normal Rust scope   of the `profile!` macro.

  The identifier must be identical to the one used in the  matching `profile!` macro call, as it is used to match up  the two.

or:

  **2.** An `unbounded` argument to allow the profile to be dropped at the end of the _function_ and to assist memory profiling.

  This is not preferred because:

  a. The profile inevitably gets dropped in user code, leaving it up to the allocation tracker to identify and filter out its allocations in the first place. This is not as clean and precise   as using the `end!` mechanism to ring-fence the profiler code,  and thus creates more overhead and greater exposure to any   potential loopholes in the filtering algorithm.

  b. It has limited applicability and is open to misuse. It may only be used to profile the remainder of a function. For more  limited scopes you must use an `end!` macro.

  The 'unbounded` option may be dropped in future.

### Conditional Profiling

You can conditionally enable profiling based on build configuration:

**1. Function profiling examples**

Only profile function when a feature is enabled:

```rust
#[cfg_attr(feature = "my_profile_feature", profiled)]
fn expensive_calculation() { /* ... */ }
```

Only profile function in debug builds:

```rust
#[cfg_attr(debug_assertions, profiled)]
fn complex_operation() { /* ... */ }
```

**2. Section profiling example**

Only profile section in debug builds:

```rust
fn process_data(data: &[u8]) {
    #[cfg(debug_assertions)]
    profile!(process_data);

    // Your code here...

    #[cfg(debug_assertions)]
    end!(process_data);

    ...
}
```

## In more depth

### Time Profiling

Time profiling measures the wall-clock time between profile creation and destruction, which closely approximates the time spent in the user code. It is async-compatible, has minimal overhead and is suitable for most performance investigations. It produces a regular `*.folded` file suitable for flamegraphs, flamecharts and function statistics *exclusive* of time spent in calls to other profiled functions, as well as an `*-inclusive.folded` file suitable only for function statistics *inclusive* of time spent in calls to other profiled functions. The analyzer handles these files accordingly.

### Memory Profiling

`thag_profiler` memory profiling attempts to provide a practical and convenient solution to memory profiling that is compatible with async operation.

Memory profiling (available via the `full_profiling` feature) accurately tracks every heap allocation (and for global detailed profiling, deallocation) requested by profiled user code, including reallocations, using a global memory allocator in conjunction with attribute macros to exclude `thag_profiler`'s own code from interfering with the analysis. It uses the official Rust `backtrace` crate to identify the source of the allocation or deallocation request.

**Note**: `thag_profiler` provides comprehensive memory tracking that includes allocator metadata, alignment overhead, and intermediate allocations during container growth. This may result in higher allocation counts compared to tools that only track payload data, but gives you the complete picture of your program's actual memory footprint.

#### One-Line Exhaustive Detailed Memory Profiling

A handy trick is that simply by annotating the `main` function of your project with `#[enable_profiling(runtime)]` and running it with `THAG_PROFILER=memory,,,true` you can get a fully detailed memory profile showing allocations and one showing deallocations. This also applies transitively to all the dependencies of your project!

Caution: this may be prohibitively slow, depending on your project, although the example below only took a few seconds.

##### A simple example of detailed memory profiling

Here we try the above trick on a `thag(_rs)` script that uses `syn` to print out an AST for a Rust source file. The code is available in the `thag_rs` project.

The script is `demo/syn_dump_syntax_profile_syn.rs` and we run it with `thag` to print out the `syn` AST for another, simple script called demo/hello_main.rs. Below is the execution showing the AST printout, followed by the detailed memory allocation profile for the run from `thag_profile`:

```zsh
donf@MacBook-Air thag_rs % THAG_PROFILER=both,,announce,true thag demo/syn_dump_syntax_profile_syn.rs --timings -- demo/hello_main.rs

Skipping unnecessary generation step.  Use --force (-f) to override.
Skipping unnecessary cargo build step. Use --force (-f) to override.
──────────────────────────────────────────────────────────────────────
Thag Profiler debug log: /var/folders/rx/mng2ds0s6y53v12znz5jhpk80000gn/T/thag_profiler/syn_dump_syntax_profile_syn-20250521-105801-debug.log
is_profiling_enabled()? true, get_global_profile_type(): Both
File {
    shebang: None,
    attrs: [],
    items: [
        Item::Fn {
            attrs: [
                Attribute {
                    pound_token: Pound,
                    style: AttrStyle::Outer,
                    bracket_token: Bracket,
                    meta: Meta::NameValue {
                        path: Path {
                            leading_colon: None,
                            segments: [
                                PathSegment {
                                    ident: Ident {
                                        sym: doc,
                                        span: bytes(1..60),
                                    },
                                    arguments: PathArguments::None,
                                },
                            ],
                        },
                        eq_token: Eq,
                        value: Expr::Lit {
                            attrs: [],
                            lit: Lit::Str {
                                token: " Hello World as a program (posh Winnie-the-Pooh version)",
                            },
                        },
                    },
                },
            ],
            vis: Visibility::Inherited,
            sig: Signature {
                constness: None,
                asyncness: None,
                unsafety: None,
                abi: None,
                fn_token: Fn,
                ident: Ident {
                    sym: main,
                    span: bytes(129..133),
                },
                generics: Generics {
                    lt_token: None,
                    params: [],
                    gt_token: None,
                    where_clause: None,
                },
                paren_token: Paren,
                inputs: [],
                variadic: None,
                output: ReturnType::Default,
            },
            block: Block {
                brace_token: Brace,
                stmts: [
                    Stmt::Local {
                        attrs: [],
                        let_token: Let,
                        pat: Pat::Ident {
                            attrs: [],
                            by_ref: None,
                            mutability: None,
                            ident: Ident {
                                sym: other,
                                span: bytes(146..151),
                            },
                            subpat: None,
                        },
                        init: Some(
                            LocalInit {
                                eq_token: Eq,
                                expr: Expr::Lit {
                                    attrs: [],
                                    lit: Lit::Str {
                                        token: "World 🌍",
                                    },
                                },
                                diverge: None,
                            },
                        ),
                        semi_token: Semi,
                    },
                    Stmt::Macro {
                        attrs: [],
                        mac: Macro {
                            path: Path {
                                leading_colon: None,
                                segments: [
                                    PathSegment {
                                        ident: Ident {
                                            sym: println,
                                            span: bytes(169..176),
                                        },
                                        arguments: PathArguments::None,
                                    },
                                ],
                            },
                            bang_token: Not,
                            delimiter: MacroDelimiter::Paren(
                                Paren,
                            ),
                            tokens: TokenStream [
                                Literal {
                                    lit: "Hello, {other}!",
                                    span: bytes(178..195),
                                },
                            ],
                        },
                        semi_token: Some(
                            Semi,
                        ),
                    },
                ],
            },
        },
    ],
}
──────────────────────────────────────────────────────────────────────
Completed run in 4.44s
thag_rs completed processing script syn_dump_syntax_profile_syn.rs in 4.76s
donf@MacBook-Air thag_rs %
```

[![Global detailed memory flamechart: syn](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamechart_20250521-100000.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamechart_20250521-100000.svg)<br>
*Global detailed memory profile in <code>inferno</code> "Rust" color scheme showing `syn` crate functions. Click on image for interactive version with clickable bars and search.*

##### A project example of detailed memory profiling

Here is `thag` itself in REPL mode, profiled in the same way:

[![Global detailed memory flamechart: thag](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamegraph_detail_thag.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamegraph_detail_thag.svg)<br>
*Detailed memory allocation profile in <code>inferno</code> "orange" color scheme showing all dependencies. Click on image for interactive version with clickable bars and search.*


#### Before-and-After (Differential) Memory Profiling

The `thag_profile` tool supports `inferno`'s differential profiling feature for both time and memory profiles. Simply select this option and the "before" and "after" .folded files.

[![Differential memory flamegraph](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamegraph_mem_diff.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamegraph_mem_diff.svg)<br>
*Differential memory profile showing reduced allocations in blue. Click on image for interactive version with clickable bars and search.*


#### Memory Profiling Limitations and Considerations

- **Performance Impact**: `thag_profiler` memory profiling introduces significant overhead compared to time profiling. Expect your application to run appreciably more slowly when memory profiling is enabled. It's strongly recommended to use memory profiling selectively for occasional health checks and targeted investigations rather than leave it enabled indefinitely.

- **Comparison with Other Memory Profilers**: `thag_profiler` aims to provide comprehensive memory tracking and may show higher allocation counts than some other profiling tools because it captures:

 - Allocator metadata and alignment overhead
 - Intermediate allocations during Vec/HashMap growth and  reallocation
 - Function-level aggregation of all allocations within profiled  scopes

 This comprehensive approach provides a more accurate picture of actual memory usage that your program experiences in production, including overhead that other tools might filter out.

- **Mitigating Performance Impact with Optional Tracking Threshold Size**: Detailed memory profiling in particular is obviously the slowest profiling option and may be prohibitively slow for some applications.

  To mitigate this, `thag_profiler` provides a `SIZE_TRACKING_THRESHOLD=<bytes>` environment variable allowing you to track only individual allocations that exceed the specified threshold size (default value 0). This is obviously at the cost of accuracy, particularly if your app mainly does allocations below the threshold. To get a good idea of a suitable threshold value, you can first do _detailed_ memory profiling (cancel if you need to once you see significant detailed output being generated in the output directory). Then in the `thag_profile` tool, select the detailed output, then select `Show Allocation Size Distribution`. This needs to be the detailed allocations `.folded` file, because the normal memory profiling shows aggregated values per function rather than the detailed values being tracked.

  The catch-22 with overriding the default zero threshold is that if there are enough small allocations below the threshold to make a significant difference in profiling speed, those small allocations may themselves be worth investigating. So rather than screen them out, you may well get a better outcome by first identifying hotspots using summary profiling, and then doing very focused detailed profiling only on these, avoiding setting a threshold if at all possible.

  For accuracy it's recommended *not* to use the `SIZE_TRACKING_THRESHOLD` override if you don't have to.

- **Custom Global Allocator**: Memory profiling (the optional `full_profiling` feature) uses a custom global allocator to track memory allocations.

  1. This is incompatible with specifying your own global allocator in your application (`#[global_allocator]`), as Rust only allows one global allocator.

  2. **Note about thread-local storage (TLS)**: The profiler is designed to be compatible with code that uses thread-local storage. If you encounter the error "fatal runtime error: the global allocator may not use TLS with destructors", please report it as a bug.

     In older versions of `async_std` (pre-1.10), there were known interactions that could cause this error in certain usage patterns. If you experience issues with a particular async runtime, consider trying alternatives like `smol` or `tokio`.

- **Thread-Safety Considerations**: Memory profiling uses global state protected by mutexes. While this works for most cases, extremely high-concurrency apps may experience contention.

- **Potential Allocator Race Conditions in Async Environments**: Unfortunately, Rust only allows a single global allocator (which `thag_profiler` refers to as the dispatcher), so profiler code must share this with user code. `thag_profiler` uses a thread-local variable to indicate to the dispatcher to use the system allocator for profiler code in place of the default "tracking" allocator.

 The dispatcher delegates memory requests from user code to the tracking allocator, which records the point of origin in user code (the "allocation site") and the request type (allocation, deallocation or reallocation) and size, before in turn delegating the request to the Rust system allocator to honour the request. The dispatcher delegates requests from profiler code directly to the system allocator.

 Thread-local storage is not a panacea, as `tokio` work stealing can cause a suspended task to resume on a different thread. This means that in an async environment, particularly under stress, the thread-local boolean variable that reflects the current allocator could end up out of sync with the workload it was intended to interact with, either causing user allocations to be processed through the system allocator and thus not be recorded, or causing profiler code allocations to be processed through the tracking allocator. The latter not only risks profiler code allocations being incorrectly recorded as user allocations, but risks causing the whole app to hang due to infinite recursion through the tracking allocator.

 Should it be suspected that allocations are being processed through the wrong allocator, leading to inaccuracies, some mitigations you may consider are:

 1. To use detailed profiling to reveal unexpected or missing allocations.

 2. To compare results with another tool such as `DHAT`.

 3. To reduce the number of profiled functions and sections, and if possible the number of threads, to the practical minimum in the final stages of profiling, in order to minimise contention.

 4. To check for consistency in the processing of the same functions and sections over time under different load conditions  and with different amounts of profiled code competing for the dispatcher.

 5. If possible, to copy the relevant sections of code to a profiled `thag` script so as to study the allocations in isolation.

 Fortunately, this potential issue has not shown up in unit testing, and results so far have been very consistent, so contention does not seem to be a significant issue in practice. However the potential for race conditions is a caveat when memory profiling in async environments.

- **Complete Allocation Tracking**: All allocations, including those from libraries and dependencies, are tracked and included in profiling data. This provides a comprehensive view of memory usage
   across your entire application stack, revealing hidden costs from dependencies like async runtimes.

 Detailed memory profiling allows you to drill down into these allocations as well as the resulting deallocations.

- **Peak memory allocation on flamegraphs and flamecharts**: The peak bytes allocated total shown in the graph headers is simply calculated at the available granularity and may therefore be a gross over-estimate. For a normal summary memory graph this is not very accurate as in the absence of profiled child functions it assumes that memory allocated over the life of a profiled function is only deallocated at the end of the function. The global detailed flamegraph, which shows the relative lifespan of each allocation in the call stack, should give an accurate figure which may be much lower.

### Profiling release builds

Although `thag_profiler` is focused on the development cycle, you may wish to do a profiling exercise on a release build. Since `thag_profiler` relies on the `backtrace` crate, see the entry for that crate on `docs.rs` for the backtrace considerations that may affect you. Specifying the following in your `Cargo.toml` or in the toml block of a `thag` script should generally work:

```toml
[profile.release]
debug = true
strip = false
```

You also need to provide the features information at build time as described in a previous section.

If you build your app with `#[enable_profiling(runtime)]`, then at runtime you need to provide the `THAG_PROFILER` environment variable specifying the runtime parameters.

### Windows Memory Profiling

For memory profiling on Windows, your application requires:

1. Debug information in the executable, which can be enabled with:

   ```toml
   [profile.release]
   debug = true
   strip = false
   ```

2. PDB files generated by the build must be distributed alongside the executable.
   These files contain the debug information needed for accurate profiling.

### Async Compatibility

`thag_profiler` supports profiling async code with some considerations:

- **Basic Time Profiling**: Works well with the async runtimes tested including tokio and smol.

- **Memory Profiling with Async**: Memory profiling in async contexts is more complex:

  - Works with `tokio` and `smol` for most common patterns.

  - Has a (small?) degree of exposure to race conditions, as discussed above.

  - Examples of using `thag_profiler` in async contexts may be found at:

      - `https://github.com/durbanlegend/thag_rs/demo/document_pipeline_profile.rs` (tokio)

      - `https://github.com/durbanlegend/thag_rs/demo/smol_chat_server_profile.rs` (smol)

      - `https://github.com/durbanlegend/thag_rs/demo/flume_async_profile.rs` (async-std)

  - For best results in async code, use explicit section profiling with `profile!(<section_name>, async)`

### Implementation Details

`thag_profiler` uses several internal mechanisms to track profiling data:

- **Tracking allocations by line number of origin**: Memory profiling attributes allocations to the correct point of origin ("allocation site") by matching the file, function and line number of the allocation site with the file, function and line number ranges of the profiles generated by the `thag_profiler` macros.

- **Thread safety**: The profiler uses atomic operations and mutex-protected shared state to coordinate profiling across threads, with one caveat already mentioned relating to dispatching of memory allocation.

- **Guard objects**: TaskGuard objects help manage the lifetime of profiling tasks and ensure proper cleanup when tasks complete.

- **Profiler code ring-fencing**: The profiler carefully isolates its own allocations and operations from user code through the use of a dual-allocator system. This ensures that profiling overhead
  doesn't contaminate the results, providing clean separation between the measurement apparatus and the code being measured.

Note that deallocations are not reported for normal memory profiling, as they invite a fruitless attempt to identify memory leaks by matching them up by function against the allocations, whereas the deallocations are often done by a parent function. However, deallocations are reported for detailed memory profiling in order to give a complete picture, so this is a better tool for identifying memory leaks, although still not a walk in the park.

#### Profiling data recording

The recording takes two forms:

a. For summary memory profiling, the allocations are attributed to a profile (a `Profile` instance representing an active execution of a function or code section) in a mutex-protected registry, by matching the allocation site against the registered active profiles. The allocation size is passed to the profile, which accumulates it. When the function completes execution the profile goes out of scope and is automatically dropped, and its `drop` trait method writes out the accumulated total to the `-memory.folded` file.

Time profiling also uses these same profiles, but in a simpler way: all that's needed is to record the duration between the activation and dropping of the profile.

In both cases, since the metrics for the function are measured over the lifetime of its profile instance, `thag_profiler` takes care to ensure that the profile lifetime coincides as closely as possible with the lifetime of the function, in order to ensure accurate measurement.

b. For detailed memory profiling, allocations and deallocations alike are not accumulated or even tracked back to a `Profile`, but immediately written with a lightly tidied-up stack to the `-memory_detail.folded` and `-memory_detail_dealloc.folded` files respectively.

Being the default, the tracking allocator is automatically used for user code and must not be used for profiler code.

To avoid getting caught up in the default allocator and causing recursion or polluting the user allocation data with its own allocations, all of the profiler's own code that runs during memory profiling execution is passed directly to the untracked System allocator in a closure or function via a `with_sys_alloc()` function (`pub fn with_sys_alloc<T, F: FnOnce() -> T>(f: F) -> T`).

### Profile Output

Profiles generate "folded" stack traces in the output directory by default:

- `your_program-<yyyymmdd>-<hhmmss>.folded`: Time profiling data for graphs and stats reporting

- `your_program-<yyyymmdd>-<hhmmss>-inclusive.folded`: Time profiling data for stats reporting

- `your_program-<yyyymmdd>-<hhmmss>-memory.folded`: Summary memory allocation data

- `your_program-<yyyymmdd>-<hhmmss>-memory_detail.folded`: Detailed memory allocation data (if enabled in 4th argument of THAG_PROFILER with #[enable_profiling(runtime)])

- `your_program-<yyyymmdd>-<hhmmss>-memory_detail_dealloc.folded`: Detailed memory deallocation data (if enabled in 4th argument of THAG_PROFILER with #[enable_profiling(runtime)])

These files can be visualized with the included `thag_profile` or with tools like [inferno-flamegraph](https://github.com/jonhoo/inferno) or the beautiful [speedscope](https://www.speedscope.app/).

`thag_profile` is recommended because it offers:

 1. Correct handling of units.

 2. File grouping and sorting for ease of selection and comparison.

 3. Built-in file comparisons using `inferno` differential flamegraphs.

 4. Filtering out of dead space in flamegraphs.

    a. Irrelevant substacks

    b. Dead parts of functions (as illustrated below)

### Filtering Flamegraphs and Flamecharts

[![Unfiltered memory flamegraph](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamegraph_unfiltered.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamegraph_unfiltered.svg)<br>
*Unfiltered profile showing wasted space.  <code>inferno</code> "yellow" color scheme. Click on image for interactive version with clickable bars and search.*

---

[![Filtered memory flamegraph](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamegraph_filtered.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/memory_flamegraph_filtered.svg)<br>
*The same .folded file, but with the dead section of `main` filtered out for a clearer view.  <code>inferno</code> "aqua" color scheme. Click on image for interactive version with clickable bars and search.*

### Profiling Tools

`thag_profiler` includes three command-line tools for working with profiles.

***Important notice:***

By using the tools, you agree to the license terms and take full responsibility for any consequences. Please take care to back up and protect your code before instrumenting or removing instrumentation.
Take care not to overwrite your code when using the instrumenting tools unless you have a backup.

[License reminder](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/dont_make_me_tap_the_sign.jpg)


#### Instrumentation: thag_instrument and thag_uninstrument

Automatically add or remove profiling attributes to/from code, outputting to a different file.

These tools aim to be lossless, i.e. preserving comments and formatting intact. For this purpose
they rely on `rust-analyzer`'s `ra_ap_syntax` and `ra-ap-rustc_lexer` crates rather than `syn` and `quote`.

Input is from `stdin` and output is to `stdout`.

***NB:*** always direct output to a different file.

Replace `2021` with your project's Rust edition (2015, 2018, 2021, 2024) as required by the `rust-analyzer` crates:

***thag_instrument:*** Add profiling attributes to code

```bash
thag_instrument 2021 < path/to/your/file.rs > path/to/your/instrumented_file.rs
```

***thag_uninstrument:*** Remove profiling attributes from code

```bash
thag_uninstrument 2021 < path/to/your/instrumented_file.rs > path/to/your/de-instrumented_file.rs
```

* Ensure your original source is safely backed up or committed before instrumenting.

* Replace `2021` with your project's Rust edition.

* Do NOT redirect the output back to your source file in the same command.

* In the case of `thag_uninstrument`, you may need to remove the relevant imports manually.
`thag_uninstrument` may leave the occasional trailing space and one or two blank lines at the very top of the file.

* Compare the original and instrumented files to ensure correctness, especially if
you're using a custom edition.

  For example Comparing before and after with `vimdiff`:

    ```
    vimdiff demo/factorial_ibig_product.rs demo/factorial_ibig_product_profile.rs
    ```

    ![vimdiff](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/vimdiff_profile_instrument.png)

    If you're profiling a project source file, at this point you'd want to replace the uninstrumented code with the instrumented version.


Repeat for all modules you want to profile.

#### Analysis: thag_profile

Interactive analysis of profiling results:

```bash
thag_profile <dirname>
```
![Main menu](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/thag_profile_main.png)

## Profile Analysis Features

The analyzer provides:

**1. Statistical Reports**: Shows function calls, total time, average time, memory allocations by function, and memory allocation size analysis.

**2. Interactive Flamegraphs and Flamecharts**: Visual representation of performance data, both cumulative and detailed

**3. Differential Analysis**: Before/after comparisons using `inferno` differential flamegraphs module.

**4. Grouping, selection and ordering of output for analysis**: Groups files by program name and displays them with most-recent first.

**5. Graph filtering**: Filter out irrelevant sections of flamegraphs and flamecharts for a better view.

### Flamegraphs and Flamecharts

Cumulative flamegraphs and detailed flamecharts provide an intuitive interactive visualization of your profiling data. The wider a function appears, the more time (or allocated / deallocated memory) it represents relative to the total for the execution.

Flamegraphs and flamecharts are interactive SVGs that allow you to:

- Zoom in on specific functions

- Hover over functions to see detailed information

- Search for specific functions

- Compare before/after optimizations

`thag_profiler` uses the `inferno` crate to generate flamegraphs and flamecharts.
The analysis tool allows you to choose which `inferno` color scheme to use and remembers your last choice for each type (time and memory).

### Flamegraphs vs. Flamecharts

`thag_profiler` can generate both flamegraphs and flamecharts:

#### Flamegraphs

[![Example flamegraph](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamegraph_time_20250302-080709.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamegraph_time_20250302-080709.svg)<br>
*Example flamegraph in <code>inferno</code> "purple" color scheme. Click on image for interactive version with clickable bars and search.*

**Flamegraphs** aggregate all executions of a function into one, making them ideal for identifying which functions consume the most resources overall. Use flamegraphs when you want to identify your application's hottest functions regardless of when they occur. Flamegraphs organize functions alphabetically, so unlike flamecharts there is no significance to the horizontal sequence of items - it is only the width and the parent-child relationships that are important.

#### Flamecharts

[![Example flamechart](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamechart_time_20250519-155436.png)](https://durbanlegend.github.io/thag_rs/thag_profiler/assets/flamechart_time_20250519-155436.svg)<br>
*Example flamechart of same data in <code>inferno</code> "green" color scheme. Click on image for interactive version with clickable bars and search.*

**Flamecharts** organize functions chronologically, showing the sequence of operations over time. They're particularly valuable for:

  - Understanding the progression of your application's execution

  - Identifying patterns in memory allocation/deallocation

  - Seeing how different phases of your application behave

Note that `inferno` still aggregates consecutive entries with the same key into a single bar. So if a profiled function or section `f` is called repeatedly in a loop in a synchronous program, these executions are shown as one. In an async environment, the consecutive entries for `f` may be arbitrarily interleaved with entries written by unrelated asynchronous profiles being dropped, for example `f,f,f,a,f,f,b,c,f`. This causes `inferno` to group the consecutive entries for `f` in the arbitrary consecutive groups thus created.

In other words, while `thag_profiler` should always attribute an allocation event to the right function or section, or in a worst case to an ancestor at least, be aware that in an async environment it may attribute the event to the wrong *execution* of that function. We could consider working around this, for example by giving each execution a unique subscript, but in the async scenario when multiple instances of `f` are running concurrently, `thag_profiler`'s memory tracker has no sure way to tell them apart anyway, and must resort to arbitrarily attributing the allocation to the most recently instantiated active one of them, falling back to the closest matching ancestor as a last resort.

For time profiling, flamecharts show when each function executed relative to others. For summary memory profiling, they are less significant because all allocations for a function are shown as at the end of execution of the function, because it is at this point that the `Profile` object generated for that execution of the function is dropped, causing its `drop` method to write the function's accumulated allocations to the `-memory.folded` file.

For detailed memory profiling, flamecharts are again more significant. They show when the allocations (for `-memory_detail.folded` and deallocations (for `-memory_detail_dealloc.folded`) actually occurred, as they are recorded immediately the allocation or deallocation requests are received and identified by the global allocator.

In general, choose flamegraphs for a high-level view of resource usage and flamecharts for detailed analysis of execution flow.

## Best Practices

**1. Profile representative workloads**: Make sure your test cases represent real-world usage

**2. Focus on hot paths**: Look for the widest blocks in your flamechart - these are your performance bottlenecks

**3. Compare before/after**: Always compare profiles before and after optimization

**4. Watch for memory bloat**: Use memory profiling to identify excessive allocations

**5. Verify changes**: Always verify automated changes with a diff tool

**6. Don't run with option `both` for serious time profiling**: The memory profiling overhead tends to distort the relative execution times of the functions and sections

**7. Section profiling in async functions**: For accurate callstack representation in async contexts, use the `async_fn` parameter when manually creating profile sections within async functions

**8. Profiling efficiency**: Every execution of an actively profiled function requires a Profile struct to be set up at the beginning and torn down at the end after logging the captured data for that function execution. The Profile setup and teardown time amount to about 1ms (based on an M1 MacBook). This time is stripped out of the results, but still has an appreciable effect on runtime when profiling very lightweight functions that are called many thousands, millions or billions of times during execution. If such a function is called from a loop, consider profiling the loop as a whole (with a section profile or by factoring it out to a separate profiled function) rather that incurring the extra overhead of profiling the inner function to record each individual function call.

**9. Understand how differential profiling graphs work in `inferno`**: `thag_profiler` delegates these entirely to `inferno`, which does not report straight comparisons but normalises the "before" profile count to match the "after" one in an attempt to compensate for inferred changes in external variables such as machine loading, even though they may in fact be almost entirely due to the changes being compared. This seems to have been optional in the Brendan Gregg's original flamegraph toolkit but not in `inferno`. I don't have strong views on the merits of this implementation, but it may cause small but worthwhile changes to literally pale into insignificance beside the most substantial ones, and not show up as improvements until hovered over.

```rust
async fn fetch_data() {
    // Tell the profiler this section is within an async function
    profile!(database_query, async_fn);

    // Async operations...
    let result = query_database().await;

    end!(database_query);
}
```

This ensures that the profile correctly associates the section with its async parent function in the profiling output. Without this parameter, the section will appear a second time in the flamegraph without its async identifier, as there is no way to link the two automatically.

## Troubleshooting

### Common Issues

**1. Missing profile output**: Ensure that the `time_profiling` or `full_profiling` feature of the `thag_profiler` dependency is enabled.

Ensure profiling is enabled and you have write permissions in the current directory.

Ensure your code is compiled with the `debug` option while profiling. For example in release mode:

```toml
[profile.release]
debug = true
strip = false
```

Ensure that unbounded section profiles do not go out of scope before the end of the current _function_.

Ensure that bounded section profiles do not go out of scope before the `end!` macro.

**2. Inaccurate profile output**: Ensure you have no nested or overlapping profile sections. A high degree of contention between threads could also lead to memory allocations not being reported or reported against an ancestor function (that is, one higher up the callstack).

**3. Test failures**: Profiled tests must use serialization.

**4. Performance impact**: Memory profiling adds significant overhead. Consider using SIZE_TRACKING_THRESHOLD=n as discussed above to ignore small allocations of integer `n` bytes or smaller.

**5. File redirect issues**: Never redirect output from the instrumentation tools back to the input file.

### Inspecting Profile Files

The folded stack files are human-readable:

```bash
head your_executable-<yyyymmdd>-<hhmmss>.folded
```

## Verification against `dhat` crate

The `bash` script `scripts/compare_profilers.sh` can be used to verify the accuracy of `thag_profiler` memory profiling against the established `dhat` crate.

```bash
scripts/compare_profilers.sh
```

You need to have `jq` installed to summarize the `dhat` results.

Notes:

1. `thag_profiler` does not offer a `PowerShell` version of `scripts/compare_profilers.sh`. Short of rolling your own or getting AI to convert it, you may instead find the following suggestion useful:

   Install `Git for Windows` and run the `Git Bash` app.

   Running `scripts/compare_profilers.sh` within the `Git Bash`  window will still run the Windows version of  `thag`, as opposed to using `WSL`.

2. `dhat` itself provides no guarantees. See the warning on its [docs.rs page](https://docs.rs/dhat/latest/dhat/).

3. `dhat` and `thag_profiler` both use the `backtrace` crate to identify memory allocation sites, so to this extent they are not giving completely mutually independent assessments of memory usage.

4. To dig deeper than the summary results and investigate the extra allocation reported (at time of writing) by `thag_profiler` for the `allocate_hashmap` function, you can run a detailed `thag_profiler` analysis like this:

```bash
THAG_PROFILER=memory,,announce,true thag --features full_profiling demo/thag_profile_benchmark.rs -f
```

Then run `thag_profile .` and choose `Memory Profile - Single` and the most recent `thag_profile_benchmark-<yyyymmdd>-<hhmmss>-memory_detail.folded`. Drill down to the affected functions by clicking on each one in turn and compare it to the various occurrences of the same function in the `DHAT` viewer. This currently shows that `thag_profiler` is correctly reflecting 103 kB for the inserts (`std::collections::hash::map::HashMap<K,V,S>::insert
`) which `dhat` is not. There are some other minor discrepancies, but these are due to the DHAT viewer suppressing them as insignificant, and they can be tracked down in the `dhat-heap.json` file.

## Documentation

This library provides two levels of documentation to serve different audiences:

### Public API Documentation (Default)

Generate clean, user-focused documentation that hides implementation details:

```bash
cargo doc --package thag_profiler --features document-features,full_profiling,debug_logging --no-deps
```

This shows only the essential public API that most users need:
- Core profiling attributes (`#[profiled]`, `#[enable_profiling]`)
- Main configuration types (``ProfileType`)
- Essential functions (`disable_profiling`, `is_profiling_enabled`)
- Memory profiling utilities (when `full_profiling` feature is enabled)

### Internal Documentation (Development)

Generate comprehensive documentation including implementation details:

```bash
cargo doc --package thag_profiler --features document-features,full_profiling,debug_logging,internal_docs --no-deps
```

For the most comprehensive documentation including private items:

```bash
cargo doc --package thag_profiler --features document-features,full_profiling,debug_logging,internal_docs --no-deps --document-private-items
```

This includes everything from the public API plus:
- Internal utility functions
- Debug logging infrastructure
- Advanced configuration functions
- Implementation macros and helpers
- Development and debugging tools
- Private functions and modules (when using `--document-private-items`)

### Implementation

The library provides two convenient ways to mark internal items:

1. **Using the `#[internal_doc]` macro** (recommended):
   ```rust
   use thag_profiler::internal_doc;

   #[internal_doc]
   pub fn internal_utility() {
       // Hidden from public API docs
   }
   ```

2. **Using the manual attribute**:
   ```rust
   #[cfg_attr(not(feature = "internal_docs"), doc(hidden))]
   pub fn internal_utility() {
       // Hidden from public API docs
   }
   ```

### Rationale

Many internal functions need to be `pub` for use across modules and by generated macros, but they're not intended for direct use by library consumers. The `internal_docs` feature flag allows:

- **Library users** get clean, focused documentation showing only what they need
- **Contributors/maintainers** can generate complete documentation for development
- **docs.rs** can show comprehensive documentation with all features enabled

Use the provided `doc_examples.sh` script to see all documentation modes in action.

## Terminology

### Ancestor and descendant functions

An **ancestor function** of a function `f` means any function that may directly or indirectly call function `f` during execution.

A **descendant function** of a function `f` means any function that may be called directly or indirectly by function `f` during execution.

### Manifest

In a normal project, the manifest is the Cargo.toml file. In a Rust script to be run with `thag(_rs)`, the manifest is a "toml block" consisting of a `/**[toml!] ... */` comment embedded at the top of the script and containing entries in Cargo.toml format. `thag` uses the toml block contents in conjunction with any additional dependencies inferred from the Rust code to generate a Cargo.toml for the script.

## License

SPDX-License-Identifier: Apache-2.0 OR MIT

Licensed under either of

    Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)

or

    MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

as you prefer.

## Contributing

Contributions will be considered (under MIT/Apache 2 license) if they align with the aims of the project.

Rust code should pass clippy::pedantic checks.