Repository: jlfwong/speedscope
Pull request #502 (2025-03-16 18:28:22)

Add support for FreeBSD's pmcstat callgraph format
This eliminates the need for stackcollapse-pmc.pl and its dependency on Perl to import FreeBSD callgraphs.  Tracing can be done completely with tools included in the base system.  For example:

```
# kldload hwpmc
# pmcstat -S instructions -O sample.out sleep 1
# pmcstat -R sample.out -G sample.pmcstat.graph
```

To get `npm run jest` to run on FreeBSD, I changed the shebang in scripts/test-setup.sh from /bin/bash to /bin/sh as FreeBSD does not have /bin/bash. The script uses no bash-specific features. This is done in a separate commit.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/72752788/badge)](https://coveralls.io/builds/72752788)

coverage: 43.984% (+0.3%) from 43.731%
when pulling **899184b0e67d240475ec5c215dbd4a56f2cd3e9e on ryan-moeller:pmcstat**
into **1c254dcb3e2b4f6d921340d20e972d9d27b788f4 on jlfwong:main**.


Patch:
diff --git a/sample/profiles/pmcstat/simple-with-invalids.txt b/sample/profiles/pmcstat/simple-with-invalids.txt
new file mode 100644
index 0000000..835f78f
--- /dev/null
+++ b/sample/profiles/pmcstat/simple-with-invalids.txt
@@ -0,0 +1,38 @@
+@ inst_retired.any [35705 samples]
+
+# comment ok
+// ignored
+
+20.86%  [7447]     zfs_lz4_compress @ /boot/kernel/zfs.ko
+ 100.0%  [7447]      zio_compress_data
+  100.0%  [7447]       zio_write_compress
+   100.0%  [7447]        zio_execute
+    100.0%  [7447]         taskqueue_run_locked @ /boot/kernel/kernel
+     100.0%  [7447]          taskqueue_thread_loop
+      100.0%  [7447]           fork_exit
+
+14.44%  [5156]     fletcher_2_native @ /boot/kernel/zfs.ko
+ 50.62%  [2610]      arc_cksum_verify
+  66.02%  [1723]       arc_buf_thaw
+   86.48%  [1490]        dmu_buf_will_dirty_impl
+    99.93%  [1489]         dmu_write_uio_dnode
+     100.0%  [1489]          zfs_write
+      100.0%  [1489]           zfs_freebsd_write
+       100.0%  [1489]            VOP_WRITE_APV @ /boot/kernel/kernel
+        100.0%  [1489]             vn_write
+         100.0%  [1489]              vn_io_fault_doio
+          100.0%  [1489]               vn_io_fault1
+           100.0%  [1489]                vn_io_fault
+            100.0%  [1489]                 dofilewrite
+             100.0%  [1489]                  kern_pwritev
+              100.0%  [1489]                   sys_pwrite
+               100.0%  [1489]                    amd64_syscall
+    00.07%  [1]            dmu_write_impl @ /boot/kernel/zfs.ko
+     100.0%  [1]             dmu_write
+      100.0%  [1]              metaslab_set_unflushed_txg
+       100.0%  [1]               metaslab_unflushed_bump
+        100.0%  [1]                spa_flush_metaslabs
+         100.0%  [1]                 spa_sync
+          100.0%  [1]                  txg_sync_thread
+           100.0%  [1]                   fork_exit @ /boot/kernel/kernel
+invalid line
diff --git a/sample/profiles/pmcstat/simple.txt b/sample/profiles/pmcstat/simple.txt
new file mode 100644
index 0000000..32fe4cb
--- /dev/null
+++ b/sample/profiles/pmcstat/simple.txt
@@ -0,0 +1,34 @@
+@ inst_retired.any [35705 samples]
+
+20.86%  [7447]     zfs_lz4_compress @ /boot/kernel/zfs.ko
+ 100.0%  [7447]      zio_compress_data
+  100.0%  [7447]       zio_write_compress
+   100.0%  [7447]        zio_execute
+    100.0%  [7447]         taskqueue_run_locked @ /boot/kernel/kernel
+     100.0%  [7447]          taskqueue_thread_loop
+      100.0%  [7447]           fork_exit
+
+14.44%  [5156]     fletcher_2_native @ /boot/kernel/zfs.ko
+ 50.62%  [2610]      arc_cksum_verify
+  66.02%  [1723]       arc_buf_thaw
+   86.48%  [1490]        dmu_buf_will_dirty_impl
+    99.93%  [1489]         dmu_write_uio_dnode
+     100.0%  [1489]          zfs_write
+      100.0%  [1489]           zfs_freebsd_write
+       100.0%  [1489]            VOP_WRITE_APV @ /boot/kernel/kernel
+        100.0%  [1489]             vn_write
+         100.0%  [1489]              vn_io_fault_doio
+          100.0%  [1489]               vn_io_fault1
+           100.0%  [1489]                vn_io_fault
+            100.0%  [1489]                 dofilewrite
+             100.0%  [1489]                  kern_pwritev
+              100.0%  [1489]                   sys_pwrite
+               100.0%  [1489]                    amd64_syscall
+    00.07%  [1]            dmu_write_impl @ /boot/kernel/zfs.ko
+     100.0%  [1]             dmu_write
+      100.0%  [1]              metaslab_set_unflushed_txg
+       100.0%  [1]               metaslab_unflushed_bump
+        100.0%  [1]                spa_flush_metaslabs
+         100.0%  [1]                 spa_sync
+          100.0%  [1]                  txg_sync_thread
+           100.0%  [1]                   fork_exit @ /boot/kernel/kernel
diff --git a/src/import/index.ts b/src/import/index.ts
index 47ff7f9..f66a6ca 100644
--- a/src/import/index.ts
+++ b/src/import/index.ts
@@ -23,6 +23,7 @@ import {importFromChromeHeapProfile} from './v8heapalloc'
 import {isTraceEventFormatted, importTraceEvents} from './trace-event'
 import {importFromCallgrind} from './callgrind'
 import {importFromPapyrus} from './papyrus'
+import {importFromPMCStatCallGraph} from './pmcstat-callgraph'
 
 export async function importProfileGroupFromText(
   fileName: string,
@@ -123,6 +124,9 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
   } else if (fileName.startsWith('callgrind.')) {
     console.log('Importing as Callgrind profile')
     return importFromCallgrind(contents, fileName)
+  } else if (fileName.endsWith('.pmcstat.graph')) {
+    console.log('Importing as pmcstat callgraph format')
+    return toGroup(importFromPMCStatCallGraph(contents))
   }
 
   // Second pass: Try to guess what file format it is based on structure
@@ -204,6 +208,12 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
       console.log('Importing as collapsed stack format')
       return toGroup(fromBGFlameGraph)
     }
+
+    const fromPMCStatCallGraph = importFromPMCStatCallGraph(contents)
+    if (fromPMCStatCallGraph) {
+      console.log('Importing as pmcstat callgraph format')
+      return toGroup(fromPMCStatCallGraph)
+    }
   }
 
   // Unrecognized format
diff --git a/src/import/pmcstat-callgraph.ts b/src/import/pmcstat-callgraph.ts
new file mode 100644
index 0000000..a30eb2e
--- /dev/null
+++ b/src/import/pmcstat-callgraph.ts
@@ -0,0 +1,30 @@
+import {Profile, FrameInfo, StackListProfileBuilder} from '../lib/profile'
+import {TextFileContent} from './utils'
+
+export function importFromPMCStatCallGraph(contents: TextFileContent): Profile | null {
+  const profile = new StackListProfileBuilder()
+  const stack: FrameInfo[] = []
+  let file: string | undefined
+  let prevDuration = '0'
+  let prevIndent = -1
+  for (const line of contents.splitLines()) {
+    const match = /^( *)[\d.]+%  \[(\d+)\]\s*(\S+)(?: @ (.*))?$/gm.exec(line)
+    if (!match) continue
+    const indent = match[1].length
+    if (indent <= prevIndent) {
+      const frames = stack.slice(0, prevIndent + 1).reverse()
+      const duration = parseInt(prevDuration, 10)
+      profile.appendSampleWithWeight(frames, duration)
+    }
+    const name = match[3]
+    file = match[4] || file
+    stack[indent] = {key: name, name: name, file: file}
+    prevDuration = match[2]
+    prevIndent = indent
+  }
+  if (prevIndent == -1) return null
+  const frames = stack.slice(0, prevIndent + 1).reverse()
+  const duration = parseInt(prevDuration, 10)
+  profile.appendSampleWithWeight(frames, duration)
+  return profile.build()
+}


Test patch:
diff --git a/scripts/test-setup.sh b/scripts/test-setup.sh
index 6b11a56..00edd24 100755
--- a/scripts/test-setup.sh
+++ b/scripts/test-setup.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 set -e
 
 for f in `find sample/profiles -name '*.zip' | grep -v Instruments`; do
diff --git a/src/import/__snapshots__/pmcstat-callgraph.test.ts.snap b/src/import/__snapshots__/pmcstat-callgraph.test.ts.snap
new file mode 100644
index 0000000..65a340a
--- /dev/null
+++ b/src/import/__snapshots__/pmcstat-callgraph.test.ts.snap
@@ -0,0 +1,575 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`importFromPMCStatCallGraph 1`] = `
+Object {
+  "frames": Array [
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "fork_exit",
+      "line": undefined,
+      "name": "fork_exit",
+      "selfWeight": 0,
+      "totalWeight": 7448,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "taskqueue_thread_loop",
+      "line": undefined,
+      "name": "taskqueue_thread_loop",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "taskqueue_run_locked",
+      "line": undefined,
+      "name": "taskqueue_run_locked",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zio_execute",
+      "line": undefined,
+      "name": "zio_execute",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zio_write_compress",
+      "line": undefined,
+      "name": "zio_write_compress",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zio_compress_data",
+      "line": undefined,
+      "name": "zio_compress_data",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zfs_lz4_compress",
+      "line": undefined,
+      "name": "zfs_lz4_compress",
+      "selfWeight": 7447,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "amd64_syscall",
+      "line": undefined,
+      "name": "amd64_syscall",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "sys_pwrite",
+      "line": undefined,
+      "name": "sys_pwrite",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "kern_pwritev",
+      "line": undefined,
+      "name": "kern_pwritev",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "dofilewrite",
+      "line": undefined,
+      "name": "dofilewrite",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_io_fault",
+      "line": undefined,
+      "name": "vn_io_fault",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_io_fault1",
+      "line": undefined,
+      "name": "vn_io_fault1",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_io_fault_doio",
+      "line": undefined,
+      "name": "vn_io_fault_doio",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_write",
+      "line": undefined,
+      "name": "vn_write",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "VOP_WRITE_APV",
+      "line": undefined,
+      "name": "VOP_WRITE_APV",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zfs_freebsd_write",
+      "line": undefined,
+      "name": "zfs_freebsd_write",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zfs_write",
+      "line": undefined,
+      "name": "zfs_write",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_write_uio_dnode",
+      "line": undefined,
+      "name": "dmu_write_uio_dnode",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_buf_will_dirty_impl",
+      "line": undefined,
+      "name": "dmu_buf_will_dirty_impl",
+      "selfWeight": 0,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "arc_buf_thaw",
+      "line": undefined,
+      "name": "arc_buf_thaw",
+      "selfWeight": 0,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "arc_cksum_verify",
+      "line": undefined,
+      "name": "arc_cksum_verify",
+      "selfWeight": 0,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "fletcher_2_native",
+      "line": undefined,
+      "name": "fletcher_2_native",
+      "selfWeight": 1490,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "txg_sync_thread",
+      "line": undefined,
+      "name": "txg_sync_thread",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "spa_sync",
+      "line": undefined,
+      "name": "spa_sync",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "spa_flush_metaslabs",
+      "line": undefined,
+      "name": "spa_flush_metaslabs",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "metaslab_unflushed_bump",
+      "line": undefined,
+      "name": "metaslab_unflushed_bump",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "metaslab_set_unflushed_txg",
+      "line": undefined,
+      "name": "metaslab_set_unflushed_txg",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_write",
+      "line": undefined,
+      "name": "dmu_write",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_write_impl",
+      "line": undefined,
+      "name": "dmu_write_impl",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+  ],
+  "name": "simple.txt",
+  "stacks": Array [
+    "fork_exit;taskqueue_thread_loop;taskqueue_run_locked;zio_execute;zio_write_compress;zio_compress_data;zfs_lz4_compress 7,447",
+    "amd64_syscall;sys_pwrite;kern_pwritev;dofilewrite;vn_io_fault;vn_io_fault1;vn_io_fault_doio;vn_write;VOP_WRITE_APV;zfs_freebsd_write;zfs_write;dmu_write_uio_dnode;dmu_buf_will_dirty_impl;arc_buf_thaw;arc_cksum_verify;fletcher_2_native 1,489",
+    "fork_exit;txg_sync_thread;spa_sync;spa_flush_metaslabs;metaslab_unflushed_bump;metaslab_set_unflushed_txg;dmu_write;dmu_write_impl;dmu_buf_will_dirty_impl;arc_buf_thaw;arc_cksum_verify;fletcher_2_native 1",
+  ],
+}
+`;
+
+exports[`importFromPMCStatCallGraph with invalid lines 1`] = `
+Object {
+  "frames": Array [
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "fork_exit",
+      "line": undefined,
+      "name": "fork_exit",
+      "selfWeight": 0,
+      "totalWeight": 7448,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "taskqueue_thread_loop",
+      "line": undefined,
+      "name": "taskqueue_thread_loop",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "taskqueue_run_locked",
+      "line": undefined,
+      "name": "taskqueue_run_locked",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zio_execute",
+      "line": undefined,
+      "name": "zio_execute",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zio_write_compress",
+      "line": undefined,
+      "name": "zio_write_compress",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zio_compress_data",
+      "line": undefined,
+      "name": "zio_compress_data",
+      "selfWeight": 0,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zfs_lz4_compress",
+      "line": undefined,
+      "name": "zfs_lz4_compress",
+      "selfWeight": 7447,
+      "totalWeight": 7447,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "amd64_syscall",
+      "line": undefined,
+      "name": "amd64_syscall",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "sys_pwrite",
+      "line": undefined,
+      "name": "sys_pwrite",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "kern_pwritev",
+      "line": undefined,
+      "name": "kern_pwritev",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "dofilewrite",
+      "line": undefined,
+      "name": "dofilewrite",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_io_fault",
+      "line": undefined,
+      "name": "vn_io_fault",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_io_fault1",
+      "line": undefined,
+      "name": "vn_io_fault1",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_io_fault_doio",
+      "line": undefined,
+      "name": "vn_io_fault_doio",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "vn_write",
+      "line": undefined,
+      "name": "vn_write",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/kernel",
+      "key": "VOP_WRITE_APV",
+      "line": undefined,
+      "name": "VOP_WRITE_APV",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zfs_freebsd_write",
+      "line": undefined,
+      "name": "zfs_freebsd_write",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "zfs_write",
+      "line": undefined,
+      "name": "zfs_write",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_write_uio_dnode",
+      "line": undefined,
+      "name": "dmu_write_uio_dnode",
+      "selfWeight": 0,
+      "totalWeight": 1489,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_buf_will_dirty_impl",
+      "line": undefined,
+      "name": "dmu_buf_will_dirty_impl",
+      "selfWeight": 0,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "arc_buf_thaw",
+      "line": undefined,
+      "name": "arc_buf_thaw",
+      "selfWeight": 0,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "arc_cksum_verify",
+      "line": undefined,
+      "name": "arc_cksum_verify",
+      "selfWeight": 0,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "fletcher_2_native",
+      "line": undefined,
+      "name": "fletcher_2_native",
+      "selfWeight": 1490,
+      "totalWeight": 1490,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "txg_sync_thread",
+      "line": undefined,
+      "name": "txg_sync_thread",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "spa_sync",
+      "line": undefined,
+      "name": "spa_sync",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "spa_flush_metaslabs",
+      "line": undefined,
+      "name": "spa_flush_metaslabs",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "metaslab_unflushed_bump",
+      "line": undefined,
+      "name": "metaslab_unflushed_bump",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "metaslab_set_unflushed_txg",
+      "line": undefined,
+      "name": "metaslab_set_unflushed_txg",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_write",
+      "line": undefined,
+      "name": "dmu_write",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+    Frame {
+      "col": undefined,
+      "file": "/boot/kernel/zfs.ko",
+      "key": "dmu_write_impl",
+      "line": undefined,
+      "name": "dmu_write_impl",
+      "selfWeight": 0,
+      "totalWeight": 1,
+    },
+  ],
+  "name": "simple-with-invalids.txt",
+  "stacks": Array [
+    "fork_exit;taskqueue_thread_loop;taskqueue_run_locked;zio_execute;zio_write_compress;zio_compress_data;zfs_lz4_compress 7,447",
+    "amd64_syscall;sys_pwrite;kern_pwritev;dofilewrite;vn_io_fault;vn_io_fault1;vn_io_fault_doio;vn_write;VOP_WRITE_APV;zfs_freebsd_write;zfs_write;dmu_write_uio_dnode;dmu_buf_will_dirty_impl;arc_buf_thaw;arc_cksum_verify;fletcher_2_native 1,489",
+    "fork_exit;txg_sync_thread;spa_sync;spa_flush_metaslabs;metaslab_unflushed_bump;metaslab_set_unflushed_txg;dmu_write;dmu_write_impl;dmu_buf_will_dirty_impl;arc_buf_thaw;arc_cksum_verify;fletcher_2_native 1",
+  ],
+}
+`;
+
+exports[`importFromPMCStatCallGraph with invalid lines: indexToView 1`] = `0`;
+
+exports[`importFromPMCStatCallGraph with invalid lines: profileGroup.name 1`] = `"simple-with-invalids.txt"`;
+
+exports[`importFromPMCStatCallGraph: indexToView 1`] = `0`;
+
+exports[`importFromPMCStatCallGraph: profileGroup.name 1`] = `"simple.txt"`;
diff --git a/src/import/pmcstat-callgraph.test.ts b/src/import/pmcstat-callgraph.test.ts
new file mode 100644
index 0000000..d6f5679
--- /dev/null
+++ b/src/import/pmcstat-callgraph.test.ts
@@ -0,0 +1,9 @@
+import {checkProfileSnapshot} from '../lib/test-utils'
+
+test('importFromPMCStatCallGraph', async () => {
+  await checkProfileSnapshot('./sample/profiles/pmcstat/simple.txt')
+})
+
+test('importFromPMCStatCallGraph with invalid lines', async () => {
+  await checkProfileSnapshot('./sample/profiles/pmcstat/simple-with-invalids.txt')
+})