Android Hprof Dump Data Source

Overview

A Perfetto data source that triggers a Java heap dump (.hprof) on a target Android process and embeds the raw hprof binary in the trace as a packet. Optionally also extracts bitmap images as PNGs.

This runs in system_server (AOSP frameworks/base) and uses the existing ActivityManagerService.dumpHeap() mechanism.

What it does

  1. Receives trace config with target process pid/cmdline
  2. Calls AMS.dumpHeap(process, managed=true, dumpBitmaps, path, fd, cb)
  3. Target process runs Debug.dumpHprofData().hprof file
  4. Optionally target process runs Bitmap.dumpAll("png") → PNG files
  5. On completion callback: reads the files, writes trace packets
  6. Cleans up temp files

Proto

Config (new, in Perfetto repo)

// protos/perfetto/config/profiling/hprof_dump_config.proto
message HprofDumpConfig {
  optional uint64 pid = 1;
  optional string process_cmdline = 2;
  optional bool run_gc = 3;          // GC before dump, default true
  optional bool dump_bitmaps = 4;    // also extract bitmaps as PNG
  optional string bitmap_format = 5; // "png" (default) or "webp"
}

Trace output (new, in Perfetto repo)

// protos/perfetto/trace/profiling/hprof_dump.proto
message HprofDump {
  // Process ID of the dumped process.
  optional int32 pid = 1;

  // Raw .hprof binary data. Can be split across multiple packets
  // if the dump is large (use continued flag on TracePacket).
  optional bytes hprof_data = 2;
}

Add to TracePacket:

HprofDump hprof_dump = <next_field>;

For bitmaps, reuse the VideoFrame proto or add:

message HprofBitmap {
  optional int32 pid = 1;
  optional string filename = 2;   // e.g., "bitmap_0.png"
  optional bytes png_image = 3;
}

Trace processor changes

Option A: Route raw bytes to ArtHprofParser

Add a module that handles TracePacket.hprof_dump:

  1. Extracts the hprof_data bytes
  2. Feeds them to ArtHprofParser::Parse() as TraceBlobView chunks
  3. ArtHprofParser populates the existing heap_graph_* tables

This reuses all existing hprof parsing -- no new tables needed.

Option B: Store raw bytes as BLOB

Store the raw hprof bytes in a BLOB vector (like VideoFrame) and expose via hprof_dump_data(id) SQL function. This lets the UI download the raw .hprof file.

Recommended: both. Parse into heap_graph tables AND store raw bytes so the user can also download the original file.

System server data source (AOSP)

HprofDumpDataSource.java

public class HprofDumpDataSource extends PerfettoDataSource {
    static { INSTANCE.register("android.hprof_dump"); }

    @Override
    protected void onStart(int instanceIndex, byte[] config) {
        // 1. Parse HprofDumpConfig from config bytes
        // 2. Find target process via AMS
        // 3. Create temp dir: /data/local/tmp/hprof_<sessionId>/
        // 4. Create ParcelFileDescriptor for output
        // 5. Call AMS.dumpHeap(process, managed=true,
        //      dumpBitmaps=config.dump_bitmaps ? "png" : null,
        //      path, fd, finishCallback)
        // 6. In finishCallback (runs when dump complete):
        //    a. Read .hprof file bytes
        //    b. Write as TracePacket { hprof_dump { pid, hprof_data } }
        //       Split into multiple packets if >4MB
        //    c. If bitmap dump enabled:
        //       Read each PNG file
        //       Write as TracePacket { hprof_bitmap { pid, filename, png } }
        //    d. commitPacket()
        //    e. Delete temp files
    }
}

Splitting large hprof files

A heap dump can be 50-200MB. This won't fit in a single trace packet. Use the continued flag on TracePacket to split across multiple packets on the same sequence:

byte[] hprofBytes = Files.readAllBytes(hprofPath);
int chunkSize = 4 * 1024 * 1024; // 4MB chunks
for (int offset = 0; offset < hprofBytes.length; offset += chunkSize) {
    int len = Math.min(chunkSize, hprofBytes.length - offset);
    ProtoWriter w = ctx.getWriter();
    // Write timestamp, sequence_id, etc.
    int dump = w.beginNested(HPROF_DUMP_FIELD);
    w.writeVarInt(1, pid);
    w.writeBytes(2, hprofBytes, offset, len);
    w.endNested(dump);
    ctx.commitPacket();
}

Shmem buffer sizing

Hprof dumps are large. Set shmem_size_hint_kb = 8192 (8MB). Use PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT.

Threading

The dump is async -- AMS.dumpHeap() returns immediately, the target process does the work, then calls the finish callback. The data source should defer stop until the callback fires (like LayerDataSource's HandleStopAsynchronously()).

Trace config example

buffers { size_kb: 262144 }  # 256MB for large hprof
data_sources {
  config {
    name: "android.hprof_dump"
    hprof_dump_config {
      process_cmdline: "com.example.app"
      run_gc: true
      dump_bitmaps: true
    }
  }
}
duration_ms: 60000

Testing

On device:

# Existing mechanism (works today):
adb shell am dumpheap -b png com.example.app /data/local/tmp/dump.hprof

# With Perfetto (after this data source is built):
adb shell perfetto -c - --txt <<EOF
buffers { size_kb: 262144 }
data_sources { config { name: "android.hprof_dump"
  hprof_dump_config { process_cmdline: "com.example.app" run_gc: true }
} }
duration_ms: 60000
EOF

Load the trace in Perfetto UI → heap graph tables populated from the embedded hprof data, alongside any other concurrent trace data.

Perfetto repo changes needed

  1. Config proto: HprofDumpConfig
  2. Trace proto: HprofDump (raw bytes), HprofBitmap (PNG bytes)
  3. Trace processor module: routes hprof_dump bytes to ArtHprofParser
  4. Optional: BLOB storage + SQL function for raw hprof download
  5. Optional: UI plugin for bitmap gallery view