Skip to content

Commit 39c06bb

Browse files
garazdawimichalmuskala
authored andcommitted
Add process_info binary_full that provides more details
In particular, this includes the full original binary and all the references. Gathering this information requires full heap traversal, so is fairly expensive - that's why we keep both the existing `binary` and this new API. Example: ``` 1> A = <<0:(1024*8)>>. 2> <<B:550/bitstring,D:550/bits,E:550/bits,C/bitstring>> = A. 3> erlang:process_info(self(),[binary_full]). [{binary_full,[{126812357133856,1024,1, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...>>, [{1100,1650},{550,1100},{1650,8192},{0,550},{0,8192}]}]}] ``` Co-authored-by: Lukas Backström <[email protected]>
1 parent 0696dd4 commit 39c06bb

File tree

6 files changed

+242
-3
lines changed

6 files changed

+242
-3
lines changed

erts/emulator/beam/atom.names

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ atom bif_return_trap
136136
atom binary
137137
atom binary_copy_trap
138138
atom binary_find_trap
139+
atom binary_full
139140
atom binary_longest_prefix_trap
140141
atom binary_longest_suffix_trap
141142
atom binary_to_list_continue

erts/emulator/beam/erl_bif_info.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ collect_one_suspend_monitor(ErtsMonitor *mon, void *vsmicp, Sint reds)
796796
#define ERTS_PI_IX_DICTIONARY_LOOKUP 38
797797
#define ERTS_PI_IX_LABEL 39
798798
#define ERTS_PI_IX_PRIORITY_MESSAGES 40
799+
#define ERTS_PI_IX_BINARY_FULL 41
799800

800801
#define ERTS_PI_UNRESERVE(RS, SZ) \
801802
(ASSERT((RS) >= (SZ)), (RS) -= (SZ))
@@ -849,7 +850,8 @@ static ErtsProcessInfoArgs pi_args[] = {
849850
{am_async_dist, 0, 0, ERTS_PROC_LOCK_MAIN},
850851
{am_dictionary, 3, ERTS_PI_FLAG_FORCE_SIG_SEND|ERTS_PI_FLAG_KEY_TUPLE2, ERTS_PROC_LOCK_MAIN},
851852
{am_label, 0, ERTS_PI_FLAG_FORCE_SIG_SEND, ERTS_PROC_LOCK_MAIN},
852-
{am_priority_messages, 0, 0, ERTS_PROC_LOCK_MAIN}
853+
{am_priority_messages, 0, 0, ERTS_PROC_LOCK_MAIN},
854+
{am_binary_full, 0, ERTS_PI_FLAG_NEED_MSGQ|ERTS_PI_FLAG_FORCE_SIG_SEND, ERTS_PROC_LOCK_MAIN}
853855
};
854856

855857
#define ERTS_PI_ARGS ((int) (sizeof(pi_args)/sizeof(pi_args[0])))
@@ -948,6 +950,8 @@ pi_arg2ix(Eterm arg, Eterm *extrap)
948950
return ERTS_PI_IX_TRACE;
949951
case am_binary:
950952
return ERTS_PI_IX_BINARY;
953+
case am_binary_full:
954+
return ERTS_PI_IX_BINARY_FULL;
951955
case am_sequential_trace_token:
952956
return ERTS_PI_IX_SEQUENTIAL_TRACE_TOKEN;
953957
case am_catchlevel:
@@ -2156,6 +2160,10 @@ process_info_aux(Process *c_p,
21562160
break;
21572161
}
21582162

2163+
case ERTS_PI_IX_BINARY_FULL:
2164+
res = erts_gather_binaries(hfact, rp);
2165+
break;
2166+
21592167
case ERTS_PI_IX_SEQUENTIAL_TRACE_TOKEN: {
21602168
Uint sz = size_object(rp->seq_trace_token);
21612169
hp = erts_produce_heap(hfact, sz, reserve_size);

erts/emulator/beam/erl_gc.c

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3839,6 +3839,173 @@ erts_max_heap_size(Eterm arg, Uint *max_heap_size, Uint *max_heap_flags)
38393839
return 1;
38403840
}
38413841

3842+
typedef struct debug_binary {
3843+
BinRef *bin_ref;
3844+
ErtsDynamicWStack ws;
3845+
} DebugBinary;
3846+
3847+
static void gather_binaries(DebugBinary *bins, Uint count, Eterm *start, Eterm *stop) {
3848+
Eterm* tp = start;
3849+
while (tp < stop) {
3850+
Eterm val = *tp++;
3851+
3852+
switch (primary_tag(val)) {
3853+
case TAG_PRIMARY_IMMED1:
3854+
case TAG_PRIMARY_LIST:
3855+
case TAG_PRIMARY_BOXED:
3856+
break;
3857+
case TAG_PRIMARY_HEADER:
3858+
if (header_is_transparent(val)) {
3859+
continue;
3860+
}
3861+
switch (thing_subtag(val)) {
3862+
case SUB_BITS_SUBTAG:
3863+
{
3864+
const ErlSubBits *sb = (ErlSubBits*)(tp-1);
3865+
const BinRef *underlying = (BinRef*)boxed_val(sb->orig);
3866+
if (thing_subtag(underlying->thing_word) != HEAP_BITS_SUBTAG) {
3867+
for (Uint i = 0; i < count; i++) {
3868+
DebugBinary* b = &bins[i];
3869+
if (b->bin_ref == underlying) {
3870+
WSTACK_PUSH2(b->ws.ws, sb->start, sb->end);
3871+
break;
3872+
}
3873+
}
3874+
}
3875+
}
3876+
ERTS_FALLTHROUGH();
3877+
default:
3878+
{
3879+
tp += header_arity(val);
3880+
}
3881+
break;
3882+
}
3883+
break;
3884+
}
3885+
}
3886+
}
3887+
3888+
Eterm
3889+
erts_gather_binaries(ErtsHeapFactory *hfact, Process *rp) {
3890+
#define PSTACK_TYPE DebugBinary
3891+
PSTACK_DECLARE(binaries, 16);
3892+
3893+
union erl_off_heap_ptr u;
3894+
Eterm res = NIL;
3895+
Eterm tuple;
3896+
union erts_tmp_aligned_offheap tmp;
3897+
Uint binaries_count;
3898+
DebugBinary* binariesp;
3899+
3900+
ErlHeapFragment* bp;
3901+
ErtsMessage* mp;
3902+
Eterm *htop, *heap;
3903+
Uint sz = 0;
3904+
Eterm *hp;
3905+
3906+
for (u.hdr = MSO(rp).first; u.hdr; u.hdr = u.hdr->next) {
3907+
erts_align_offheap(&u, &tmp);
3908+
if (u.hdr->thing_word == HEADER_BIN_REF) {
3909+
DebugBinary* bin = PSTACK_PUSH(binaries);
3910+
bin->bin_ref = u.br;
3911+
WSTACK_INIT(&bin->ws, ERTS_ALC_T_ESTACK);
3912+
}
3913+
}
3914+
3915+
for (u.hdr = rp->wrt_bins; u.hdr; u.hdr = u.hdr->next) {
3916+
erts_align_offheap(&u, &tmp);
3917+
if (u.hdr->thing_word == HEADER_BIN_REF) {
3918+
DebugBinary* bin = PSTACK_PUSH(binaries);
3919+
bin->bin_ref = u.br;
3920+
WSTACK_INIT(&bin->ws, ERTS_ALC_T_ESTACK);
3921+
}
3922+
}
3923+
binariesp = (DebugBinary*)binaries.pstart;
3924+
binaries_count = PSTACK_COUNT(binaries);
3925+
3926+
if (rp->abandoned_heap) {
3927+
heap = get_orig_heap(rp, &htop, NULL);
3928+
gather_binaries(binariesp, binaries_count, heap, htop);
3929+
}
3930+
3931+
if (OLD_HEAP(rp))
3932+
gather_binaries(binariesp, binaries_count, OLD_HEAP(rp), OLD_HTOP(rp) /*OLD_HEND(p)*/);
3933+
3934+
gather_binaries(binariesp, binaries_count, HEAP_START(rp), HEAP_TOP(rp));
3935+
3936+
mp = rp->msg_frag;
3937+
bp = rp->mbuf;
3938+
3939+
if (bp)
3940+
goto search_heap_frags;
3941+
3942+
while (mp) {
3943+
3944+
bp = erts_message_to_heap_frag(mp);
3945+
mp = mp->next;
3946+
3947+
search_heap_frags:
3948+
3949+
while (bp) {
3950+
gather_binaries(binariesp, binaries_count,
3951+
bp->mem, bp->mem + bp->used_size);
3952+
bp = bp->next;
3953+
}
3954+
}
3955+
3956+
for (Uint i = 0; i < binaries_count; i++) {
3957+
DebugBinary b = binariesp[i];
3958+
sz += 2 /* cons */ + 6 /* tuple (ptr, sz, refc, binary, subs) */;
3959+
erts_bld_uword(NULL, &sz, (UWord) b.bin_ref->val);
3960+
erts_bld_uint(NULL, &sz, b.bin_ref->val->orig_size);
3961+
sz += ERL_REFC_BITS_SIZE;
3962+
for (UWord *bits = b.ws.ws.wstart; bits < b.ws.ws.wsp; bits += 2) {
3963+
sz += 2 /* cons */ + 3 /* tuple*/;
3964+
erts_bld_uword(NULL, &sz, bits[0]);
3965+
erts_bld_uword(NULL, &sz, bits[1]);
3966+
}
3967+
}
3968+
3969+
hp = erts_produce_heap(hfact, sz, 2);
3970+
3971+
for (Uint i = 0; i < binaries_count; i++) {
3972+
DebugBinary b = binariesp[i];
3973+
Eterm bitslist = NIL;
3974+
Eterm val = erts_bld_uword(&hp, NULL, (UWord)b.bin_ref->val);
3975+
Eterm orig_size = erts_bld_uint(&hp, NULL, b.bin_ref->val->orig_size);
3976+
Eterm bitstring;
3977+
for (UWord *bits = b.ws.ws.wstart; bits < b.ws.ws.wsp; bits += 2) {
3978+
Eterm offset = erts_bld_uword(&hp, NULL, bits[0]);
3979+
Eterm size = erts_bld_uword(&hp, NULL, bits[1]);
3980+
Eterm tuple = TUPLE2(hp, offset, size);
3981+
hp += 3;
3982+
bitslist = CONS(hp, tuple, bitslist);
3983+
hp += 2;
3984+
}
3985+
erts_refc_inc(&b.bin_ref->val->intern.refc, 1);
3986+
bitstring = erts_wrap_refc_bitstring(
3987+
&hfact->off_heap->first,
3988+
&hfact->off_heap->overhead,
3989+
&hp, b.bin_ref->val, (byte*)b.bin_ref->val->orig_bytes,
3990+
0, b.bin_ref->val->orig_size * 8);
3991+
WSTACK_DESTROY(b.ws.ws);
3992+
tuple = TUPLE5(hp, val,
3993+
orig_size,
3994+
/* We subtract the bump we did above when copying the binary */
3995+
make_small(erts_refc_read(&b.bin_ref->val->intern.refc, 1) - 1),
3996+
bitstring, bitslist);
3997+
hp += 6;
3998+
res = CONS(hp, tuple, res);
3999+
hp += 2;
4000+
4001+
}
4002+
4003+
PSTACK_DESTROY(binaries);
4004+
#undef PSTACK_TYPE
4005+
4006+
return res;
4007+
}
4008+
38424009
#if defined(DEBUG) && defined(ERLANG_FRAME_POINTERS)
38434010
void erts_validate_stack(Process *p, Eterm *frame_ptr, Eterm *stack_top) {
38444011
Eterm *stack_bottom = HEAP_END(p);

erts/emulator/beam/erl_gc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ int erts_max_heap_size(Eterm, Uint *, Uint *);
190190
void erts_deallocate_young_generation(Process *c_p);
191191
void erts_copy_one_frag(Eterm** hpp, ErlOffHeap* off_heap,
192192
ErlHeapFragment *bp, Eterm *refs, int nrefs);
193+
Eterm erts_gather_binaries(ErtsHeapFactory *hfact, Process *p);
193194
#if defined(DEBUG) || defined(ERTS_OFFHEAP_DEBUG)
194195
int erts_dbg_within_proc(Eterm *ptr, Process *p, Eterm* real_htop);
195196
#endif

erts/emulator/test/binary_SUITE.erl

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
t2b_system_limit/1,
8181
term_to_iovec/1,
8282
is_binary_test/1,
83-
local_ext/1]).
83+
local_ext/1,
84+
process_info/1]).
8485

8586
%% Internal exports.
8687
-export([sleeper/0,trapping_loop/4]).
@@ -110,7 +111,7 @@ all() ->
110111
robustness, otp_8180, trapping, large,
111112
error_after_yield, cmp_old_impl,
112113
is_binary_test,
113-
local_ext].
114+
local_ext, process_info].
114115

115116
groups() ->
116117
[
@@ -2527,3 +2528,40 @@ call_local_fail(Port, [Lext1, Lext3 | Rest]) ->
25272528
ok
25282529
end,
25292530
call_local_fail(Port, Rest).
2531+
2532+
process_info(_Config) ->
2533+
Parent = self(),
2534+
WaitGo = fun() -> receive go -> ok end end,
2535+
Pid = spawn(fun() ->
2536+
WaitGo(),
2537+
A = <<0:(1024*8)>>,
2538+
<<B:550/bitstring,D:550/bits,E:550/bits,C/bitstring>> = A,
2539+
State0 = {A, B, C, D, E},
2540+
Parent ! go,
2541+
(fun Loop(State) ->
2542+
receive
2543+
{new_state, State1} -> Loop(State1);
2544+
{gc, From} -> erlang:garbage_collect(), From ! go, Loop(State);
2545+
{get_state, From} -> From ! State, Loop(State)
2546+
end
2547+
end)(State0)
2548+
end),
2549+
[{binary_full, []}, {binary, []}] = process_info(Pid, [binary_full, binary]),
2550+
Pid ! go,
2551+
WaitGo(),
2552+
[{binary_full, FullInfo}, {binary, Info}] = process_info(Pid, [binary_full, binary]),
2553+
[{Id, Size, Count}] = Info,
2554+
[{Id, Size, Count, Bin, Refs}] = FullInfo,
2555+
true = (lists:sort(Refs) =:=
2556+
lists:sort([{0,550},{0,8192},{550,1100},{1100,1650},{1650,8192},{1650,8192}])),
2557+
Pid ! {stuck_in_queue, Bin},
2558+
Pid ! {new_state, {}},
2559+
Pid ! {gc, self()},
2560+
WaitGo(),
2561+
erlang:garbage_collect(),
2562+
{binary_full,[{Id,Size,2,Bin,[{0,8192}]}]} = process_info(Pid, binary_full),
2563+
NewBin = <<0:(1000*8)>>,
2564+
Pid ! {new_state, NewBin},
2565+
{binary_full, Info3} = process_info(Pid, binary_full),
2566+
{value, {Id, Size, 3, Bin, [{0,8192}]}, [NewBinInfo]} = lists:keytake(Id, 1, Info3),
2567+
{_, 1000, 2, NewBin, [{0,8000}]} = NewBinInfo.

erts/preloaded/src/erlang.erl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8176,6 +8176,7 @@ process_flag(_Flag, _Value) ->
81768176
async_dist |
81778177
backtrace |
81788178
binary |
8179+
binary_full |
81798180
catchlevel |
81808181
current_function |
81818182
current_location |
@@ -8218,6 +8219,11 @@ process_flag(_Flag, _Value) ->
82188219
{binary, BinInfo :: [{non_neg_integer(),
82198220
non_neg_integer(),
82208221
non_neg_integer()}]} |
8222+
{binary_full, FullBinInfo :: [{non_neg_integer(),
8223+
non_neg_integer(),
8224+
non_neg_integer(),
8225+
bitstring(),
8226+
[{non_neg_integer(), non_neg_integer()}]}]} |
82218227
{catchlevel, CatchLevel :: non_neg_integer()} |
82228228
{current_function,
82238229
{Module :: module(), Function :: atom(), Arity :: arity()} | undefined} |
@@ -8319,6 +8325,24 @@ Valid `InfoTuple`s with corresponding `Item`s:
83198325
[`message_queue_data`](#process_flag_message_queue_data) process
83208326
flag the message queue may be stored on the heap.
83218327

8328+
- **`{binary_full, FullBinInfo}`** - `FullBinInfo` is a list containing
8329+
comprehensive information about binaries on the heap of this process.
8330+
This `InfoTuple` can be changed or removed without prior notice. In the
8331+
current implementation `FullBinInfo` is a list of tuples. The tuples begin
8332+
the same way as the `BinInfo` tuples with `BinaryId`, `BinarySize`,
8333+
`BinaryRefcCount`, followed by the binary itself and a list of bit ranges
8334+
for each reference held by the process.
8335+
8336+
> #### Warning {: .warning }
8337+
>
8338+
> The message will contain the binary itself, meaning the calling process will
8339+
> hold a new reference to this binary preventing it from being freed, even if the
8340+
> target process released all references. It is recommended to immediately call
8341+
> `erlang:garbage_collect/0` from the caller process as soon as it finishes handling
8342+
> the result of this call to release those extra references.
8343+
8344+
Since: OTP 29
8345+
83228346
- **`{catchlevel, CatchLevel}`** - `CatchLevel` is the number of currently
83238347
active catches in this process. This `InfoTuple` can be changed or removed
83248348
without prior notice.

0 commit comments

Comments
 (0)