Repository: kopia/kopia
Pull request #3683 (2024-02-26 23:26:48)

build(deps): bump codecov/codecov-action from 4.0.1 to 4.1.0
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.1 to 4.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/codecov/codecov-action/releases">codecov/codecov-action's releases</a>.</em></p>
<blockquote>
<h2>v4.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: set safe directory by <a href="https://github.com/thomasrockhu-codecov"><code>@​thomasrockhu-codecov</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1304">codecov/codecov-action#1304</a></li>
<li>build(deps): bump github/codeql-action from 3.24.3 to 3.24.5 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1306">codecov/codecov-action#1306</a></li>
<li>build(deps-dev): bump eslint from 8.56.0 to 8.57.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1305">codecov/codecov-action#1305</a></li>
<li>chore(release): v4.1.0 by <a href="https://github.com/thomasrockhu-codecov"><code>@​thomasrockhu-codecov</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1307">codecov/codecov-action#1307</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/codecov/codecov-action/compare/v4.0.2...v4.1.0">https://github.com/codecov/codecov-action/compare/v4.0.2...v4.1.0</a></p>
<h2>v4.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README.md by <a href="https://github.com/thomasrockhu-codecov"><code>@​thomasrockhu-codecov</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1251">codecov/codecov-action#1251</a></li>
<li>build(deps-dev): bump <code>@​types/jest</code> from 29.5.11 to 29.5.12 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1257">codecov/codecov-action#1257</a></li>
<li>build(deps): bump github/codeql-action from 3.23.2 to 3.24.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1266">codecov/codecov-action#1266</a></li>
<li>Escape pipes in table of arguments by <a href="https://github.com/jwodder"><code>@​jwodder</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1265">codecov/codecov-action#1265</a></li>
<li>Add link to docs on Dependabot secrets by <a href="https://github.com/ianlewis"><code>@​ianlewis</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1260">codecov/codecov-action#1260</a></li>
<li>fix: working-directory input for all stages by <a href="https://github.com/Bo98"><code>@​Bo98</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1272">codecov/codecov-action#1272</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.20.0 to 6.21.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1271">codecov/codecov-action#1271</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.20.0 to 6.21.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1269">codecov/codecov-action#1269</a></li>
<li>build(deps): bump github/codeql-action from 3.24.0 to 3.24.3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1298">codecov/codecov-action#1298</a></li>
<li>Use updated syntax for GitHub Markdown notes by <a href="https://github.com/jamacku"><code>@​jamacku</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1300">codecov/codecov-action#1300</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.21.0 to 7.0.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1290">codecov/codecov-action#1290</a></li>
<li>build(deps): bump actions/upload-artifact from 4.3.0 to 4.3.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1286">codecov/codecov-action#1286</a></li>
<li>chore(release): bump to 4.0.2 by <a href="https://github.com/thomasrockhu-codecov"><code>@​thomasrockhu-codecov</code></a> in <a href="https://redirect.github.com/codecov/codecov-action/pull/1302">codecov/codecov-action#1302</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jwodder"><code>@​jwodder</code></a> made their first contribution in <a href="https://redirect.github.com/codecov/codecov-action/pull/1265">codecov/codecov-action#1265</a></li>
<li><a href="https://github.com/ianlewis"><code>@​ianlewis</code></a> made their first contribution in <a href="https://redirect.github.com/codecov/codecov-action/pull/1260">codecov/codecov-action#1260</a></li>
<li><a href="https://github.com/Bo98"><code>@​Bo98</code></a> made their first contribution in <a href="https://redirect.github.com/codecov/codecov-action/pull/1272">codecov/codecov-action#1272</a></li>
<li><a href="https://github.com/jamacku"><code>@​jamacku</code></a> made their first contribution in <a href="https://redirect.github.com/codecov/codecov-action/pull/1300">codecov/codecov-action#1300</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/codecov/codecov-action/compare/v4.0.1...v4.0.2">https://github.com/codecov/codecov-action/compare/v4.0.1...v4.0.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/codecov/codecov-action/commit/54bcd8715eee62d40e33596ef5e8f0f48dbbccab"><code>54bcd87</code></a> chore(release): v4.1.0 (<a href="https://redirect.github.com/codecov/codecov-action/issues/1307">#1307</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/8ba77ef8d7b55bda0f69297c841058adf8459f89"><code>8ba77ef</code></a> build(deps-dev): bump eslint from 8.56.0 to 8.57.0 (<a href="https://redirect.github.com/codecov/codecov-action/issues/1305">#1305</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/c60aa801e394788e00c8c837b5e250085fbe5df8"><code>c60aa80</code></a> build(deps): bump github/codeql-action from 3.24.3 to 3.24.5 (<a href="https://redirect.github.com/codecov/codecov-action/issues/1306">#1306</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/2fc4847d3f22c2ca7f5bab5504f530b55d4a9768"><code>2fc4847</code></a> fix: set safe directory (<a href="https://redirect.github.com/codecov/codecov-action/issues/1304">#1304</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1"><code>0cfda1d</code></a> chore(release): bump to 4.0.2 (<a href="https://redirect.github.com/codecov/codecov-action/issues/1302">#1302</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/7d3a55eb5ecdfe4abc7d47043c527908f5f9ae09"><code>7d3a55e</code></a> build(deps): bump actions/upload-artifact from 4.3.0 to 4.3.1 (<a href="https://redirect.github.com/codecov/codecov-action/issues/1286">#1286</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/fe84a0b3c07e94ba49e9c87299d78d0116594782"><code>fe84a0b</code></a> build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.21.0 to 7.0.0 (...</li>
<li><a href="https://github.com/codecov/codecov-action/commit/e12c9402c4274f2ab3fd378ebbd6d68109363a55"><code>e12c940</code></a> Use updated syntax for GitHub Markdown notes (<a href="https://redirect.github.com/codecov/codecov-action/issues/1300">#1300</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/ef7f8a5d3c2b37427f931fd83cab3237e2b4958a"><code>ef7f8a5</code></a> build(deps): bump github/codeql-action from 3.24.0 to 3.24.3 (<a href="https://redirect.github.com/codecov/codecov-action/issues/1298">#1298</a>)</li>
<li><a href="https://github.com/codecov/codecov-action/commit/b8a1d6a4243f1ff280a5533872c4d80179cc5b4a"><code>b8a1d6a</code></a> build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.20.0 to 6.21.0 ...</li>
<li>Additional commits viewable in <a href="https://github.com/codecov/codecov-action/compare/e0b68c6749509c5f83f984dd99a76a1c1a231044...54bcd8715eee62d40e33596ef5e8f0f48dbbccab">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=4.0.1&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index c0df6dc8..bc907e7f 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -24,7 +24,7 @@ jobs:
     - name: Run Tests
       run: make test-with-coverage
     - name: Upload Coverage
-      uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
+      uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0
       with:
         files: coverage.txt
     - name: Upload Logs
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index b26f249c..53e59c3f 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -37,7 +37,7 @@ jobs:
       id: go
       if: ${{ !contains(matrix.os, 'ARMHF') }}
     - id: govulncheck
-      uses: golang/govulncheck-action@7da72f730e37eeaad891fcff0a532d27ed737cd4 # v1.0.1
+      uses: golang/govulncheck-action@3a32958c2706f7048305d5a2e53633d7e37e97d0 # v1.0.2
       with:
         repo-checkout: false
         cache: false
diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml
index 6b75296a..ad0db67c 100644
--- a/.github/workflows/make.yml
+++ b/.github/workflows/make.yml
@@ -144,13 +144,13 @@ jobs:
     - name: Set up Docker Buildx
       uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
     - name: Download Artifacts
-      uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
+      uses: actions/download-artifact@87c55149d96e628cc2ef7e6fc2aab372015aec85 # v4.1.3
       with:
         pattern: kopia-*
         merge-multiple: true
         path: dist
     - name: Download Kopia Binaries
-      uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
+      uses: actions/download-artifact@87c55149d96e628cc2ef7e6fc2aab372015aec85 # v4.1.3
       with:
         pattern: kopia_binaries-*
         merge-multiple: true
diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml
index 09c90565..8fa3ea80 100644
--- a/.github/workflows/ossf-scorecard.yml
+++ b/.github/workflows/ossf-scorecard.yml
@@ -39,7 +39,7 @@ jobs:
       -
         # Upload the results to GitHub's code scanning dashboard.
         name: "Upload to results to dashboard"
-        uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
+        uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
         with:
           sarif_file: results.sarif
       -
diff --git a/README.md b/README.md
index f5694d6b..6cf340ea 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@ Kopia
 [![GoDoc](https://godoc.org/github.com/kopia/kopia/repo?status.svg)](https://godoc.org/github.com/kopia/kopia/repo)
 [![Coverage Status](https://codecov.io/gh/kopia/kopia/branch/master/graph/badge.svg?token=CRK4RMRFSH)](https://codecov.io/gh/kopia/kopia)[![Go Report Card](https://goreportcard.com/badge/github.com/kopia/kopia)](https://goreportcard.com/report/github.com/kopia/kopia)
 [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)
+[![Docker Pulls](https://img.shields.io/docker/pulls/kopia/kopia)](https://hub.docker.com/r/kopia/kopia/tags?page=1&ordering=name)
+[![Downloads](https://img.shields.io/github/downloads/kopia/kopia/total.svg)](https://github.com/kopia/kopia/releases)
 
 > _n._
 >
diff --git a/go.mod b/go.mod
index 8d506ec7..6453bdd2 100644
--- a/go.mod
+++ b/go.mod
@@ -27,7 +27,7 @@ require (
 	github.com/gorilla/mux v1.8.1
 	github.com/hanwen/go-fuse/v2 v2.5.0
 	github.com/hashicorp/cronexpr v1.1.2
-	github.com/klauspost/compress v1.17.6
+	github.com/klauspost/compress v1.17.7
 	github.com/klauspost/pgzip v1.2.6
 	github.com/klauspost/reedsolomon v1.12.1
 	github.com/kopia/htmluibuild v0.0.1-0.20231019063300-75c2a788c7d0
@@ -55,7 +55,7 @@ require (
 	go.opentelemetry.io/otel/sdk v1.23.1
 	go.opentelemetry.io/otel/trace v1.23.1
 	go.uber.org/multierr v1.11.0
-	go.uber.org/zap v1.26.0
+	go.uber.org/zap v1.27.0
 	golang.org/x/crypto v0.19.0
 	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
 	golang.org/x/mod v0.15.0
diff --git a/go.sum b/go.sum
index 2c910aad..ef3b8fbb 100644
--- a/go.sum
+++ b/go.sum
@@ -189,8 +189,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
-github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
+github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
@@ -332,8 +332,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
-go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
diff --git a/internal/pproflogging/pproflogging.go b/internal/pproflogging/pproflogging.go
index 06263c26..ca683669 100644
--- a/internal/pproflogging/pproflogging.go
+++ b/internal/pproflogging/pproflogging.go
@@ -63,23 +63,33 @@
 	ProfileNameCPU = "cpu"
 )
 
-// ProfileConfig configuration flags for a profile.
-type ProfileConfig struct {
-	flags []string
-	buf   *bytes.Buffer
+var (
+	// ErrEmptyProfileName returned when a profile configuration flag has no argument.
+	ErrEmptyProfileName = errors.New("empty profile flag")
+
+	//nolint:gochecknoglobals
+	pprofConfigs = newProfileConfigs(os.Stderr)
+)
+
+// Writer interface supports destination for PEM output.
+type Writer interface {
+	io.Writer
+	io.StringWriter
 }
 
 // ProfileConfigs configuration flags for all requested profiles.
 type ProfileConfigs struct {
 	mu sync.Mutex
-
+	// wrt represents the final destination for the PPROF PEM output.  Typically,
+	// this is attached to stderr or log output.  A custom writer is used because
+	// not all loggers support line oriented output through the io.Writer interface...
+	// support is often attached th a io.StringWriter.
+	// +checklocks:mu
+	wrt Writer
 	// +checklocks:mu
 	pcm map[ProfileName]*ProfileConfig
 }
 
-//nolint:gochecknoglobals
-var pprofConfigs = &ProfileConfigs{}
-
 type pprofSetRate struct {
 	setter       func(int)
 	defaultValue int
@@ -97,6 +107,37 @@ type pprofSetRate struct {
 	},
 }
 
+func newProfileConfigs(wrt Writer) *ProfileConfigs {
+	q := &ProfileConfigs{
+		wrt: wrt,
+	}
+
+	return q
+}
+
+// LoadProfileConfig configure PPROF profiling from the config in ppconfigss.
+func LoadProfileConfig(ctx context.Context, ppconfigss string) (map[ProfileName]*ProfileConfig, error) {
+	// if empty, then don't bother configuring but emit a log message - user might be expecting them to be configured
+	if ppconfigss == "" {
+		log(ctx).Debug("no profile configuration. skipping PPROF setup")
+		return nil, nil
+	}
+
+	bufSizeB := DefaultDebugProfileDumpBufferSizeB
+
+	// look for matching services.  "*" signals all services for profiling
+	log(ctx).Info("configuring profile buffers")
+
+	// acquire global lock when performing operations with global side-effects
+	return parseProfileConfigs(bufSizeB, ppconfigss)
+}
+
+// ProfileConfig configuration flags for a profile.
+type ProfileConfig struct {
+	flags []string
+	buf   *bytes.Buffer
+}
+
 // GetValue get the value of the named flag, `s`.  False will be returned
 // if the flag does not exist. True will be returned if flag exists without
 // a value.
@@ -117,7 +158,7 @@ func (p ProfileConfig) GetValue(s string) (string, bool) {
 	return "", false
 }
 
-func parseProfileConfigs(bufSizeB int, ppconfigs string) map[ProfileName]*ProfileConfig {
+func parseProfileConfigs(bufSizeB int, ppconfigs string) (map[ProfileName]*ProfileConfig, error) {
 	pbs := map[ProfileName]*ProfileConfig{}
 	allProfileOptions := strings.Split(ppconfigs, ":")
 
@@ -127,14 +168,19 @@ func parseProfileConfigs(bufSizeB int, ppconfigs string) map[ProfileName]*Profil
 		flagValue := ""
 
 		if len(profileFlagNameValuePairs) > 1 {
+			// only <key>=<value> allowed
 			flagValue = profileFlagNameValuePairs[1]
 		}
 
-		flagKey := ProfileName(strings.ToLower(profileFlagNameValuePairs[0]))
+		flagKey := ProfileName(profileFlagNameValuePairs[0])
+		if flagKey == "" {
+			return nil, ErrEmptyProfileName
+		}
+
 		pbs[flagKey] = newProfileConfig(bufSizeB, flagValue)
 	}
 
-	return pbs
+	return pbs, nil
 }
 
 // newProfileConfig create a new profiling configuration.
@@ -207,7 +253,7 @@ func StartProfileBuffers(ctx context.Context) {
 	ppconfigs := os.Getenv(EnvVarKopiaDebugPprof)
 	// if empty, then don't bother configuring but emit a log message - use might be expecting them to be configured
 	if ppconfigs == "" {
-		log(ctx).Debug("no profile buffers enabled")
+		log(ctx).Warn("no profile buffers enabled")
 		return
 	}
 
@@ -220,7 +266,13 @@ func StartProfileBuffers(ctx context.Context) {
 	pprofConfigs.mu.Lock()
 	defer pprofConfigs.mu.Unlock()
 
-	pprofConfigs.pcm = parseProfileConfigs(bufSizeB, ppconfigs)
+	var err error
+
+	pprofConfigs.pcm, err = parseProfileConfigs(bufSizeB, ppconfigs)
+	if err != nil {
+		log(ctx).With("cause", err).Warnf("cannot start PPROF config, %q, due to parse error", ppconfigs)
+		return
+	}
 
 	// profiling rates need to be set before starting profiling
 	setupProfileFractions(ctx, pprofConfigs.pcm)
@@ -249,18 +301,22 @@ func DumpPem(bs []byte, types string, wrt *os.File) error {
 	// will make best use of output buffer and help prevent overflows or
 	// stalls in the output path.
 	pr, pw := io.Pipe()
+
+	// ensure read-end of the pipe is close
+	//nolint:errcheck
+	defer pr.Close()
+
 	// encode PEM in the background and output in a line oriented
 	// fashion - this prevents the need for a large buffer to hold
 	// the encoded PEM.
 	go func() {
 		// writer close on exit of background process
+		// pipe writer will not return a meaningful error
 		//nolint:errcheck
 		defer pw.Close()
+
 		// do the encoding
 		err0 = pem.Encode(pw, blk)
-		if err0 != nil {
-			return
-		}
 	}()
 
 	// connect rdr to pipe reader
@@ -281,7 +337,10 @@ func DumpPem(bs []byte, types string, wrt *os.File) error {
 		return fmt.Errorf("could not write PEM: %w", err2)
 	}
 
-	// did not get a read error.  file ends in newline
+	if err0 != nil {
+		return fmt.Errorf("could not write PEM: %w", err0)
+	}
+
 	if err1 == nil {
 		return nil
 	}


Test patch:
diff --git a/internal/pproflogging/pproflogging_test.go b/internal/pproflogging/pproflogging_test.go
index 06dceef0..3aed2824 100644
--- a/internal/pproflogging/pproflogging_test.go
+++ b/internal/pproflogging/pproflogging_test.go
@@ -1,24 +1,70 @@
 package pproflogging
 
 import (
+	"bytes"
+	"context"
 	"fmt"
+	"io"
+	"os"
+	"regexp"
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"golang.org/x/exp/maps"
+
+	"github.com/kopia/kopia/repo/logging"
 )
 
+func TestDebug_StartProfileBuffers(t *testing.T) {
+	saveLockEnv(t)
+	// placeholder to make coverage happy
+	tcs := []struct {
+		in string
+		rx *regexp.Regexp
+	}{
+		{
+			in: "",
+			rx: regexp.MustCompile("no profile buffers enabled"),
+		},
+		{
+			in: ":",
+			rx: regexp.MustCompile(`cannot start PPROF config, ".*", due to parse error`),
+		},
+	}
+	for _, tc := range tcs {
+		lg := &bytes.Buffer{}
+		ctx := logging.WithLogger(context.Background(), logging.ToWriter(lg))
+
+		t.Setenv(EnvVarKopiaDebugPprof, tc.in)
+		StartProfileBuffers(ctx)
+		require.Regexp(t, tc.rx, lg.String())
+	}
+}
+
 func TestDebug_parseProfileConfigs(t *testing.T) {
+	saveLockEnv(t)
+
 	tcs := []struct {
-		in     string
-		key    ProfileName
-		expect []string
+		in            string
+		key           ProfileName
+		expect        []string
+		expectError   error
+		expectMissing bool
+		n             int
 	}{
+		{
+			in:     "foo",
+			key:    "foo",
+			expect: nil,
+			n:      1,
+		},
 		{
 			in:  "foo=bar",
 			key: "foo",
 			expect: []string{
 				"bar",
 			},
+			n: 1,
 		},
 		{
 			in:  "first=one=1",
@@ -26,6 +72,7 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 			expect: []string{
 				"one=1",
 			},
+			n: 1,
 		},
 		{
 			in:  "foo=bar:first=one=1",
@@ -33,6 +80,7 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 			expect: []string{
 				"one=1",
 			},
+			n: 2,
 		},
 		{
 			in:  "foo=bar:first=one=1,two=2",
@@ -41,6 +89,7 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 				"one=1",
 				"two=2",
 			},
+			n: 2,
 		},
 		{
 			in:  "foo=bar:first=one=1,two=2:second:third",
@@ -49,6 +98,7 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 				"one=1",
 				"two=2",
 			},
+			n: 4,
 		},
 		{
 			in:  "foo=bar:first=one=1,two=2:second:third",
@@ -56,24 +106,67 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 			expect: []string{
 				"bar",
 			},
+			n: 4,
 		},
 		{
 			in:     "foo=bar:first=one=1,two=2:second:third",
 			key:    "second",
 			expect: nil,
+			n:      4,
 		},
 		{
 			in:     "foo=bar:first=one=1,two=2:second:third",
 			key:    "third",
 			expect: nil,
+			n:      4,
+		},
+		{
+			in:            "=",
+			key:           "",
+			expectMissing: true,
+			expectError:   ErrEmptyProfileName,
+		},
+		{
+			in:            ":",
+			key:           "",
+			expectMissing: true,
+			expectError:   ErrEmptyProfileName,
+		},
+		{
+			in:     ",",
+			key:    ",",
+			expect: nil,
+			n:      1,
+		},
+		{
+			in:            "=,:",
+			key:           "",
+			expectMissing: true,
+			expectError:   ErrEmptyProfileName,
+		},
+		{
+			in:            "",
+			key:           "",
+			expectMissing: true,
+			expectError:   ErrEmptyProfileName,
+		},
+		{
+			in:            ":=",
+			key:           "cpu",
+			expectMissing: true,
+			expectError:   ErrEmptyProfileName,
 		},
 	}
 	for i, tc := range tcs {
 		t.Run(fmt.Sprintf("%d %s", i, tc.in), func(t *testing.T) {
-			pbs := parseProfileConfigs(1<<10, tc.in)
+			pbs, err := parseProfileConfigs(1<<10, tc.in)
+			require.ErrorIs(t, tc.expectError, err)
+			require.Len(t, pbs, tc.n)
 			pb, ok := pbs[tc.key] // no negative testing for missing keys (see newProfileConfigs)
-			require.True(t, ok)
-			require.NotNil(t, pb)                 // always not nil
+			require.Equalf(t, !tc.expectMissing, ok, "key %q for set %q expect missing %t", tc.key, maps.Keys(pbs), tc.expectMissing)
+			if tc.expectMissing {
+				return
+			}
 			require.Equal(t, 1<<10, pb.buf.Cap()) // bufsize is always 1024
 			require.Equal(t, 0, pb.buf.Len())
 			require.Equal(t, tc.expect, pb.flags)
@@ -82,6 +175,8 @@ func TestDebug_parseProfileConfigs(t *testing.T) {
 }
 
 func TestDebug_newProfileConfigs(t *testing.T) {
+	saveLockEnv(t)
+
 	tcs := []struct {
 		in     string
 		key    string
@@ -124,3 +219,140 @@ func TestDebug_newProfileConfigs(t *testing.T) {
 		})
 	}
 }
+
+func TestDebug_LoadProfileConfigs(t *testing.T) {
+	// save environment and restore after testing
+	saveLockEnv(t)
+
+	ctx := context.Background()
+
+	tcs := []struct {
+		inArgs                       string
+		profileKey                   ProfileName
+		profileFlagKey               string
+		expectProfileFlagValue       string
+		expectProfileFlagExists      bool
+		expectConfigurationCount     int
+		expectError                  error
+		expectProfileConfigNotExists bool
+	}{
+		{
+			inArgs:                       "",
+			expectConfigurationCount:     0,
+			profileKey:                   "",
+			expectError:                  nil,
+			expectProfileConfigNotExists: true,
+		},
+		{
+			inArgs:                   "block=rate=10:cpu:mutex=10",
+			expectConfigurationCount: 3,
+			profileKey:               "block",
+			profileFlagKey:           "rate",
+			expectProfileFlagExists:  true,
+			expectProfileFlagValue:   "10",
+			expectError:              nil,
+		},
+		{
+			inArgs:                   "block=rate=10:cpu:mutex=10",
+			expectConfigurationCount: 3,
+			profileKey:               "cpu",
+			profileFlagKey:           "rate",
+			expectProfileFlagExists:  false,
+		},
+		{
+			inArgs:                   "block=rate=10:cpu:mutex=10",
+			expectConfigurationCount: 3,
+			profileKey:               "mutex",
+			profileFlagKey:           "10",
+			expectProfileFlagExists:  true,
+		},
+		{
+			inArgs:                       "mutex=10",
+			expectConfigurationCount:     1,
+			profileKey:                   "cpu",
+			profileFlagKey:               "10",
+			expectProfileConfigNotExists: true,
+		},
+	}
+
+	for i, tc := range tcs {
+		t.Run(fmt.Sprintf("%d: %q", i, tc.inArgs), func(t *testing.T) {
+			pmp, err := LoadProfileConfig(ctx, tc.inArgs)
+			require.ErrorIs(t, tc.expectError, err)
+			if err != nil {
+				return
+			}
+			val, ok := pmp[tc.profileKey]
+			require.Equalf(t, tc.expectProfileConfigNotExists, !ok, "expecting key %q to %t exist", tc.profileKey, !tc.expectProfileConfigNotExists)
+			if tc.expectProfileConfigNotExists {
+				return
+			}
+			flagValue, ok := val.GetValue(tc.profileFlagKey)
+			require.Equal(t, tc.expectProfileFlagExists, ok, "expecting key %q to %t exist", tc.profileKey, tc.expectProfileFlagExists)
+			if tc.expectProfileFlagExists {
+				return
+			}
+			require.Equal(t, tc.expectProfileFlagValue, flagValue)
+		})
+	}
+}
+
+//nolint:gocritic
+func saveLockEnv(t *testing.T) {
+	t.Helper()
+
+	oldEnv := os.Getenv(EnvVarKopiaDebugPprof)
+
+	t.Cleanup(func() {
+		// restore the old environment
+		t.Setenv(EnvVarKopiaDebugPprof, oldEnv)
+	})
+}
+
+func TestErrorWriter(t *testing.T) {
+	eww := &ErrorWriter{mx: 5, err: io.EOF}
+	n, err := eww.WriteString("Hello World")
+	require.ErrorIs(t, io.EOF, err)
+	require.Equal(t, 5, n)
+	require.Equal(t, "Hello", string(eww.bs))
+}
+
+// ErrorWriter allows injection of errors into the write stream.  There are a few
+// failures in PPROF dumps that are worth modeling for tests ([io.EOF] is one)
+// For use specify the error, ErrorWriter.err, and byte index, ErrorWriter.mx,
+// in which it should occur.
+type ErrorWriter struct {
+	bs  []byte
+	mx  int
+	err error
+}
+
+func (p *ErrorWriter) Write(bs []byte) (int, error) {
+	n := len(bs)
+
+	if len(bs)+len(p.bs) > p.mx {
+		// error will be produced at p.mx
+		// so don't return any more than
+		// n
+		n = p.mx - len(p.bs)
+	}
+
+	// append the bytes to the local buffer just
+	// in case someone wants to know.
+	p.bs = append(p.bs, bs[:n]...)
+	if n < len(bs) {
+		// here we assume that any less than len(bs)
+		// bytes written returns an error.  This
+		// allows setting ErrorWriter up once
+		// to produce an error after multiple
+		// writes
+		return n, p.err
+	}
+
+	return n, nil
+}
+
+//nolint:gocritic
+func (p *ErrorWriter) WriteString(s string) (int, error) {
+	return p.Write([]byte(s))
+}