From 573a5abd9d6b1668b49376b489b187780c7125c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20H=C3=B6gberg?= Date: Tue, 27 Mar 2018 13:14:40 +0200 Subject: erts: Rewrite memory instrumentation This commit replaces the old memory instrumentation with a new implementation that scans carriers instead of wrapping erts_alloc/erts_free. The old implementation could not extract information without halting the emulator, had considerable runtime overhead, and the memory maps it produced were noisy and lacked critical information. Since the new implementation walks through existing data structures there's no longer a need to start the emulator with special flags to get information about carrier utilization/fragmentation. Memory fragmentation is also easier to diagnose as it's presented on a per-carrier basis which eliminates the need to account for "holes" between mmap segments. To help track allocations, each allocation can now be tagged with what it is and who allocated it at the cost of one extra word per allocation. This is controlled on a per-allocator basis with the +Matags option, and is enabled by default for binary_alloc and driver_alloc (which is also used by NIFs). --- lib/tools/doc/src/instrument.xml | 527 ++++++++++++--------------------------- 1 file changed, 165 insertions(+), 362 deletions(-) (limited to 'lib/tools/doc/src/instrument.xml') diff --git a/lib/tools/doc/src/instrument.xml b/lib/tools/doc/src/instrument.xml index bb6f9b6100..9fd9332373 100644 --- a/lib/tools/doc/src/instrument.xml +++ b/lib/tools/doc/src/instrument.xml @@ -4,7 +4,7 @@
- 19982016 + 19982018 Ericsson AB. All Rights Reserved. @@ -41,387 +41,190 @@

Note that this whole module is experimental, and the representations used as well as the functionality is likely to change in the future.

-

The instrument module interface was slightly changed in - Erlang/OTP R9C.

-

To start an Erlang runtime system with instrumentation, use the - +Mi* set of command-line arguments to the erl command (see - the erts_alloc(3) and erl(1) man pages).

-

The basic object of study in the case of memory allocation is a memory - allocation map. A memory allocation map contains a list of descriptors - for each allocated memory block. Currently, a descriptor is a 4-tuple

-
-        {TypeNo, Address, Size, PidDesc}    
-

where TypeNo is the memory block type number, Address - is its place in memory, and Size is its size, in bytes. - PidDesc is either a tuple {X,Y,Z} identifying the - process which was executing when the block was allocated, or - undefined if no process was executing. The pid tuple - {X,Y,Z} can be transformed into a real pid by usage of the - c:pid/3 function.

-

Various details about memory allocation:

-

Memory blocks are allocated both on the heap segment and on other memory - segments. This can cause the instrumentation functionality to report - very large holes. Currently the instrumentation functionality doesn't - provide any support for distinguishing between holes between memory - segments, and holes between allocated blocks inside memory segments. - The current size of the process cannot be obtained from within Erlang, - but can be seen with one of the system statistics tools, e.g., - ps or top. The Solaris utility pmap can be - useful. It reports currently mapped memory segments.

-

Overhead for instrumentation: When the emulator has been started with - the "+Mim true" - flag, each block is preceded by a 24 bytes large - header on a 32-bit machine and a 48 bytes large header on a 64-bit - machine. When the emulator has been started with the - "+Mis true" - flag, each block is preceded by an 8 bytes large header. These are the header - sizes used by the Erlang 5.3/OTP R9C emulator. Other versions of the - emulator may use other header sizes. The function - block_header_size/1 - can be used for retrieving the header size used for a specific memory - allocation map. The time overhead for managing the instrumentation - data is small.

-

Sizes presented by the instrumentation functionality are (by the - emulator) requested sizes, i.e. neither instrumentation headers nor - headers used by allocators are included.

+ + + + +

A histogram of block sizes where each interval's upper bound is + twice as high as the one before it.

+

The upper bound of the first interval is provided by the function + that returned the histogram, and the last interval has no upper + bound.

+
+
+ + + +

A summary of allocated block sizes (including their headers) grouped + by their Origin and Type.

+

Origin is generally which NIF or driver that + allocated the blocks, or 'system' if it could not be determined.

+

Type is the allocation category that the blocks + belong to, e.g. db_term, message or binary.

+

If one or more carriers could not be scanned in full without harming + the responsiveness of the system, UnscannedSize + is the number of bytes that had to be skipped.

+
+
+ + + +

AllocatorType is the type of the allocator that + employs this carrier.

+

TotalSize is the total size of the carrier, + including its header.

+

AllocatedSize is the combined size of the + carrier's allocated blocks, including their headers.

+

AllocatedCount is the number of allocated + blocks in the carrier.

+

InPool is whether the carrier is in the + migration pool.

+

FreeBlocks is a histogram of the free block + sizes in the carrier.

+

If the carrier could not be scanned in full without harming the + responsiveness of the system, UnscannedSize is + the number of bytes that had to be skipped.

+
+
+
+ - allocator_descr(MemoryData, TypeNo) -> AllocDescr | invalid_type | "unknown" - Returns a allocator description - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - TypeNo = int() - AllocDescr = atom() | string() - - -

Returns the allocator description of the allocator that - manages memory blocks of type number TypeNo used in - MemoryData. - Valid TypeNos are in the range returned by - type_no_range/1 on - this specific memory allocation map. If TypeNo is an - invalid integer, invalid_type is returned.

-
-
- - block_header_size(MemoryData) -> int() - Returns the memory block header size used by the emulator that generated the memory allocation map - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - - - -

Returns the memory block header size used by the - emulator that generated the memory allocation map. The block - header size may differ between different emulators.

-
-
- - class_descr(MemoryData, TypeNo) -> ClassDescr | invalid_type | "unknown" - Returns a allocator description - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - TypeNo = int() - ClassDescr = atom() | string() - - -

Returns the class description of the class that - the type number TypeNo used in MemoryData belongs - to. - Valid TypeNos are in the range returned by - type_no_range/1 on - this specific memory allocation map. If TypeNo is an - invalid integer, invalid_type is returned.

-
-
- - descr(MemoryData) -> DescrMemoryData - Replace type numbers in memory allocation map with type descriptions - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - DescrMemoryData = {term(), DescrAllocList} - DescrAllocList = [DescrDesc] - DescrDesc = {TypeDescr, int(), int(), DescrPidDesc} - TypeDescr = atom() | string() - DescrPidDesc = pid() | undefined - - -

Returns a memory allocation map where the type numbers (first - element of Desc) have been replaced by type descriptions, - and pid tuples (fourth element of Desc) have been - replaced by real pids.

-
-
- - holes(MemoryData) -> ok - Print out the sizes of unused memory blocks - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - - -

Prints out the size of each hole (i.e., the space between - allocated blocks) on the terminal. NOTE: Really large holes - are probably holes between memory segments. - The memory allocation map has to be sorted (see - sort/1).

-
-
- - mem_limits(MemoryData) -> {Low, High} - Return lowest and highest memory address used - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - Low = High = int() - - -

Returns a tuple {Low, High} indicating - the lowest and highest address used. - The memory allocation map has to be sorted (see - sort/1).

-
-
- - memory_data() -> MemoryData | false - Return the current memory allocation map - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - - -

Returns MemoryData (a the memory allocation map) - if the emulator has been started with the "+Mim true" - command-line argument; otherwise, false. NOTE:memory_data/0 blocks execution of other processes while - the data is collected. The time it takes to collect the data can - be substantial.

-
-
- - memory_status(StatusType) -> [StatusInfo] | false - Return current memory allocation status - - StatusType = total | allocators | classes | types - StatusInfo = {About, [Info]} - About = atom() - Info = {InfoName, Current, MaxSinceLast, MaxEver} - InfoName = sizes|blocks - Current = int() - MaxSinceLast = int() - MaxEver = int() - - -

Returns a list of StatusInfo if the emulator has been - started with the "+Mis true" or "+Mim true" - command-line argument; otherwise, false.

-

See the - read_memory_status/1 - function for a description of the StatusInfo term.

-
-
- - read_memory_data(File) -> MemoryData | {error, Reason} - Read memory allocation map - - File = string() - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - + + Return a summary of all allocations in the system. - -

Reads a memory allocation map from the file File and - returns it. The file is assumed to have been created by - store_memory_data/1. The error codes are the same as for - file:consult/1.

+

Shorthand for + allocations(#{}).

+ - read_memory_status(File) -> MemoryStatus | {error, Reason} - Read memory allocation status from a file - - File = string() - MemoryStatus = [{StatusType, [StatusInfo]}] - StatusType = total | allocators | classes | types - StatusInfo = {About, [Info]} - About = atom() - Info = {InfoName, Current, MaxSinceLast, MaxEver} - InfoName = sizes|blocks - Current = int() - MaxSinceLast = int() - MaxEver = int() - - - -

Reads memory allocation status from the file File and - returns it. The file is assumed to have been created by - store_memory_status/1. The error codes are the same as - for file:consult/1.

-

When StatusType is allocators, About is - the allocator that the information is about. When - StatusType is types, About is - the memory block type that the information is about. Memory - block types are not described other than by their name and may - vary between emulators. When StatusType is classes, - About is the memory block type class that information is - presented about. Memory block types are classified after their - use. Currently the following classes exist:

+ + Return a summary of all allocations filtered by allocator type + and scheduler id. + +

Returns a summary of all tagged allocations in the system, + optionally filtered by allocator type and scheduler id.

+

Only binaries and allocations made by NIFs and drivers are tagged by + default, but this can be configured an a per-allocator basis with the + +M<S>atags + emulator option.

+

If tagged allocations are not enabled on any of the specified + allocator types, the call will fail with + {error, not_enabled}.

+

The following options can be used:

- process_data - Erlang process specific data. - binary_data - Erlang binaries. - atom_data - Erlang atoms. - code_data - Erlang code. - system_data - Other data used by the system + allocator_types + +

The allocator types that will be searched. Defaults to all + alloc_util allocators.

+
+ scheduler_ids + +

The scheduler ids whose allocator instances will be searched. A + scheduler id of 0 will refer to the global instance that is not + tied to any particular scheduler. Defaults to all schedulers and + the global instance.

+
+ histogram_start + +

The upper bound of the first interval in the allocated block + size histograms. Defaults to 128.

+
+ histogram_width + +

The number of intervals in the allocated block size histograms. + Defaults to 18.

+
-

When InfoName is sizes, Current, - MaxSinceLast, and MaxEver are, respectively, current - size, maximum size since last call to - store_memory_status/1 or memory_status/1 with the - specific StatusType, and maximum size since the emulator - was started. When InfoName is blocks, Current, - MaxSinceLast, and MaxEver are, respectively, current - number of blocks, maximum number of blocks since last call to - store_memory_status/1 or memory_status/1 with the - specific StatusType, and maximum number of blocks since the - emulator was started.

-

NOTE:A memory block is accounted for at - "the first level" allocator. E.g. fix_alloc allocates its - memory pools via ll_alloc. When a fix_alloc block - is allocated, neither the block nor the pool in which it resides - are accounted for as memory allocated via ll_alloc even - though it is.

-
-
- - sort(MemoryData) -> MemoryData - Sort the memory allocation list - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - - - -

Sorts a memory allocation map so that the addresses are in - ascending order.

-
-
- - store_memory_data(File) -> true|false - Store the current memory allocation map on a file - - File = string() - - -

Stores the current memory allocation map on the file - File. Returns true if the emulator has been - started with the "+Mim true" command-line argument, and - the map was successfully stored; otherwise, false. The - contents of the file can later be read using - read_memory_data/1. - NOTE:store_memory_data/0 blocks execution of - other processes while the data is collected. The time it takes - to collect the data can be substantial.

-
-
- - store_memory_status(File) -> true|false - Store the current memory allocation status on a file - - File = string() - - -

Stores the current memory status on the file - File. Returns true if the emulator has been - started with the "+Mis true", or "+Mim true" - command-line arguments, and the data was successfully stored; - otherwise, false. The contents of the file can later be - read using - read_memory_status/1.

-
-
- - sum_blocks(MemoryData) -> int() - Return the total amount of memory used - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - - -

Returns the total size of the memory blocks in the list.

+

Example:

+ instrument:allocations(#{ histogram_start => 128, histogram_width => 15 }). +{ok,{128,0, + #{udp_inet => + #{driver_event_state => {0,0,0,0,0,0,0,0,0,1,0,0,0,0,0}}, + system => + #{heap => {0,0,0,0,20,4,2,2,2,3,0,1,0,0,1}, + db_term => {271,3,1,52,80,1,0,0,0,0,0,0,0,0,0}, + code => {0,0,0,5,3,6,11,22,19,20,10,2,1,0,0}, + binary => {18,0,0,0,7,0,0,1,0,0,0,0,0,0,0}, + message => {0,40,78,2,2,0,0,0,0,0,0,0,0,0,0}, + ... } + spawn_forker => + #{driver_select_data_state => + {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, + ram_file_drv => #{drv_binary => {0,0,0,0,0,0,1,0,0,0,0,0,0,0,0}}, + prim_file => + #{process_specific_data => {2,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + nif_trap_export_entry => {0,4,0,0,0,0,0,0,0,0,0,0,0,0,0}, + monitor_extended => {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0}, + drv_binary => {0,0,0,0,0,0,1,0,3,5,0,0,0,1,0}, + binary => {0,4,0,0,0,0,0,0,0,0,0,0,0,0,0}}, + prim_buffer => + #{nif_internal => {0,4,0,0,0,0,0,0,0,0,0,0,0,0,0}, + binary => {0,4,0,0,0,0,0,0,0,0,0,0,0,0,0}}}}} + ]]>
+ - type_descr(MemoryData, TypeNo) -> TypeDescr | invalid_type - Returns a type description - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - TypeNo = int() - TypeDescr = atom() | string() - + + Return a list of all carriers in the system. -

Returns the type description of a type number used in - MemoryData. - Valid TypeNos are in the range returned by - type_no_range/1 on - this specific memory allocation map. If TypeNo is an - invalid integer, invalid_type is returned.

+

Shorthand for + carriers(#{}).

+ - type_no_range(MemoryData) -> {Min, Max} - Returns the memory block type numbers - - MemoryData = {term(), AllocList} - AllocList = [Desc] - Desc = {int(), int(), int(), PidDesc} - PidDesc = {int(), int(), int()} | undefined - Min = int() - Max = int() - + + Return a list of all carriers filtered by allocator type and + scheduler id. - -

Returns the memory block type number range used in - MemoryData. When the memory allocation map was generated - by an Erlang 5.3/OTP R9C or newer emulator, all integers T - that satisfy Min <= T <= Max are - valid type numbers. When the memory allocation map was generated - by a pre Erlang 5.3/OTP R9C emulator, all integers in the - range are not valid type numbers.

+

Returns a summary of all carriers in the system, optionally filtered + by allocator type and scheduler id.

+

If the specified allocator types are not enabled, the call will fail + with {error, not_enabled}.

+

The following options can be used:

+ + allocator_types + +

The allocator types that will be searched. Defaults to all + alloc_util allocators.

+
+ scheduler_ids + +

The scheduler ids whose allocator instances will be searched. A + scheduler id of 0 will refer to the global instance that is not + tied to any particular scheduler. Defaults to all schedulers and + the global instance.

+
+ histogram_start + +

The upper bound of the first interval in the free block size + histograms. Defaults to 512.

+
+ histogram_width + +

The number of intervals in the free block size histograms. + Defaults to 14.

+
+
+

Example:

+ instrument:carriers(#{ histogram_start => 512, histogram_width => 8 }). +{ok,{512, + [{ll_alloc,1048576,0,1048344,71,false,{0,0,0,0,0,0,0,0}}, + {binary_alloc,1048576,0,324640,13,false,{3,0,0,1,0,0,0,2}}, + {eheap_alloc,2097152,0,1037200,45,false,{2,1,1,3,4,3,2,2}}, + {fix_alloc,32768,0,29544,82,false,{22,0,0,0,0,0,0,0}}, + {...}|...]}} + ]]>
+
-- cgit v1.2.3