• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / storage / 29259895928

13 Jul 2026 02:53PM UTC coverage: 78.9% (-0.5%) from 79.376%
29259895928

Pull #1226

github

web-flow
Merge 59c339845 into b6b815fbd
Pull Request #1226: feat: add continuous profiling and client

5207 of 7138 branches covered (72.95%)

Branch coverage included in aggregate %.

348 of 416 new or added lines in 11 files covered. (83.65%)

9 existing lines in 5 files now uncovered.

10098 of 12260 relevant lines covered (82.37%)

444.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.06
/src/scripts/pprof-client.ts
1
import { parseArgs } from 'node:util'
2
import {
3
  downloadStoredProfile,
4
  fetchPprofStream,
5
  fetchStoredProfile,
6
  fetchStoredProfiles,
7
} from '@internal/monitoring/pprof/client-http'
8
import { writePprofCaptureToFile } from '@internal/monitoring/pprof/download'
9
import { generateFlameArtifacts, resolveFlameMdFormat } from '@internal/monitoring/pprof/flame'
10
import type {
11
  PprofRequestTargetType,
12
  ProfileClass,
13
  ProfileKind,
14
} from '@internal/monitoring/pprof/types'
15

16
const USAGE = `Usage:
1✔
17
  npm run pprof -- capture <profile|heap|heap-snapshot> [--seconds N] [--output FILE] [--flame]
18
  npm run pprof -- list --class <auto|manual> [--service NAME] [--kind <cpu|heap>] [--days-ago N | --date YYYY-MM-DD | --all] [--limit N] [--cursor TOKEN]
19
  npm run pprof -- detail <id>
20
  npm run pprof -- download <id> [--output FILE] [--flame]`
21

22
type PprofCommand =
23
  | {
24
      name: 'capture'
25
      target: PprofRequestTargetType
26
      seconds?: number
27
      output?: string
28
      generateFlame: boolean
29
    }
30
  | {
31
      name: 'list'
32
      class: ProfileClass
33
      service?: string
34
      kind?: ProfileKind
35
      date?: string
36
      limit?: number
37
      cursor?: string
38
    }
39
  | { name: 'detail'; id: string }
40
  | { name: 'download'; id: string; output?: string; generateFlame: boolean }
41

42
function parsePositiveInteger(value: string | undefined, name: string, maximum?: number) {
43
  if (!value || !/^\d+$/.test(value)) throw new Error(`${name} must be a positive integer`)
3!
44
  const parsed = Number.parseInt(value, 10)
3✔
45
  if (!Number.isSafeInteger(parsed) || parsed <= 0 || (maximum !== undefined && parsed > maximum)) {
3✔
46
    throw new Error(
1✔
47
      `${name} must be a positive integer${maximum ? ` no greater than ${maximum}` : ''}`
1!
48
    )
49
  }
50
  return parsed
2✔
51
}
52

53
function parseNonNegativeInteger(value: string, name: string) {
54
  if (!/^\d+$/.test(value)) throw new Error(`${name} must be a non-negative integer`)
1!
55
  const parsed = Number.parseInt(value, 10)
1✔
56
  if (!Number.isSafeInteger(parsed)) throw new Error(`${name} must be a non-negative integer`)
1!
57
  return parsed
1✔
58
}
59

60
function parseProfileDate(value: string) {
61
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new Error('date must use YYYY-MM-DD')
2!
62
  const timestamp = Date.parse(`${value}T00:00:00.000Z`)
2✔
63
  if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString().slice(0, 10) !== value) {
2✔
64
    throw new Error('date must use YYYY-MM-DD')
1✔
65
  }
66
  return value
1✔
67
}
68

69
function utcDateDaysAgo(now: Date, daysAgo: number) {
70
  const date = new Date(
2✔
71
    Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - daysAgo)
72
  )
73
  if (Number.isNaN(date.getTime())) throw new Error('days-ago is outside the supported date range')
2!
74
  const formatted = date.toISOString().slice(0, 10)
2✔
75
  if (!/^\d{4}-\d{2}-\d{2}$/.test(formatted)) {
2!
NEW
76
    throw new Error('days-ago is outside the supported date range')
×
77
  }
78
  return formatted
2✔
79
}
80

81
function requireId(value: string | undefined) {
82
  if (!value || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error('id must be base64url text')
4✔
83
  return value
3✔
84
}
85

86
export function parsePprofCommand(args: string[], now = new Date()): PprofCommand {
18✔
87
  const [name, ...rest] = args
18✔
88

89
  if (name === 'capture') {
18✔
90
    const { values, positionals } = parseArgs({
6✔
91
      args: rest,
92
      allowPositionals: true,
93
      strict: true,
94
      options: {
95
        seconds: { type: 'string' },
96
        output: { type: 'string' },
97
        flame: { type: 'boolean' },
98
      },
99
    })
100
    const [target, ...extra] = positionals
6✔
101
    if (
6!
102
      extra.length > 0 ||
19✔
103
      (target !== 'profile' && target !== 'heap' && target !== 'heap-snapshot')
104
    ) {
NEW
105
      throw new Error(USAGE)
×
106
    }
107
    if (target === 'heap-snapshot' && values.seconds !== undefined) {
6✔
108
      throw new Error('--seconds is not valid for heap-snapshot')
1✔
109
    }
110
    if (target === 'heap-snapshot' && values.flame) {
5✔
111
      throw new Error('--flame is not valid for heap-snapshot')
1✔
112
    }
113
    return {
4✔
114
      name,
115
      target,
116
      seconds:
117
        target === 'heap-snapshot'
4✔
118
          ? undefined
119
          : values.seconds === undefined
3✔
120
            ? 30
121
            : parsePositiveInteger(values.seconds, 'seconds', 300),
122
      output: values.output,
123
      generateFlame: values.flame === true,
124
    }
125
  }
126

127
  if (name === 'list') {
12✔
128
    const { values, positionals } = parseArgs({
7✔
129
      args: rest,
130
      allowPositionals: true,
131
      strict: true,
132
      options: {
133
        class: { type: 'string' },
134
        service: { type: 'string' },
135
        kind: { type: 'string' },
136
        'days-ago': { type: 'string' },
137
        date: { type: 'string' },
138
        all: { type: 'boolean' },
139
        limit: { type: 'string' },
140
        cursor: { type: 'string' },
141
      },
142
    })
143
    if (positionals.length > 0 || (values.class !== 'auto' && values.class !== 'manual')) {
7✔
144
      throw new Error('--class must be auto or manual')
1✔
145
    }
146
    if (values.kind !== undefined && values.kind !== 'cpu' && values.kind !== 'heap') {
6!
NEW
147
      throw new Error('--kind must be cpu or heap')
×
148
    }
149
    const dateSelectors = [values['days-ago'], values.date, values.all === true].filter(
6✔
150
      (value) => value !== undefined && value !== false
18✔
151
    )
152
    if (dateSelectors.length > 1)
6✔
153
      throw new Error('--days-ago, --date and --all are mutually exclusive')
1✔
154
    const daysAgo =
155
      values['days-ago'] === undefined ? 0 : parseNonNegativeInteger(values['days-ago'], 'days-ago')
5✔
156
    return {
7✔
157
      name,
158
      class: values.class,
159
      service: values.service,
160
      kind: values.kind,
161
      date:
162
        values.all === true
5✔
163
          ? undefined
164
          : values.date === undefined
4✔
165
            ? utcDateDaysAgo(now, daysAgo)
166
            : parseProfileDate(values.date),
167
      limit:
168
        values.limit === undefined ? undefined : parsePositiveInteger(values.limit, 'limit', 1000),
4✔
169
      cursor: values.cursor,
170
    }
171
  }
172

173
  if (name === 'detail') {
5✔
174
    const { positionals } = parseArgs({ args: rest, allowPositionals: true, strict: true })
2✔
175
    if (positionals.length !== 1) throw new Error(USAGE)
2!
176
    return { name, id: requireId(positionals[0]) }
2✔
177
  }
178

179
  if (name === 'download') {
3✔
180
    const { values, positionals } = parseArgs({
2✔
181
      args: rest,
182
      allowPositionals: true,
183
      strict: true,
184
      options: {
185
        output: { type: 'string' },
186
        flame: { type: 'boolean' },
187
      },
188
    })
189
    if (positionals.length !== 1) throw new Error(USAGE)
2!
190
    return {
2✔
191
      name,
192
      id: requireId(positionals[0]),
193
      output: values.output,
194
      generateFlame: values.flame === true,
195
    }
196
  }
197

198
  throw new Error(USAGE)
1✔
199
}
200

201
async function generateFlame(profilePath: string, enabled: boolean) {
NEW
202
  if (!enabled) return
×
NEW
203
  await generateFlameArtifacts(profilePath, {
×
204
    env: {
205
      ...process.env,
206
      FLAME_SOURCEMAPS_DIRS: process.env.FLAME_SOURCEMAPS_DIRS || 'dist',
×
207
    },
208
    mdFormat: resolveFlameMdFormat(process.env.PPROF_FLAME_MD_FORMAT),
209
  })
210
}
211

212
async function execute(command: PprofCommand, adminUrl: string, apiKey: string) {
NEW
213
  if (command.name === 'list') {
×
NEW
214
    console.log(
×
215
      JSON.stringify(
216
        await fetchStoredProfiles({
217
          adminUrl,
218
          apiKey,
219
          class: command.class,
220
          service: command.service,
221
          kind: command.kind,
222
          date: command.date,
223
          limit: command.limit,
224
          cursor: command.cursor,
225
        }),
226
        null,
227
        2
228
      )
229
    )
UNCOV
230
    return
×
231
  }
232

NEW
233
  if (command.name === 'detail') {
×
NEW
234
    console.log(
×
235
      JSON.stringify(await fetchStoredProfile({ adminUrl, apiKey, id: command.id }), null, 2)
236
    )
UNCOV
237
    return
×
238
  }
239

NEW
240
  if (command.name === 'download') {
×
NEW
241
    const response = await downloadStoredProfile({ adminUrl, apiKey, id: command.id })
×
NEW
242
    const { outputPath } = await writePprofCaptureToFile(
×
243
      response.stream,
244
      {
245
        contentDisposition: response.contentDisposition,
246
        type: 'profile',
247
      },
248
      {
249
        outputPath: command.output,
250
      }
251
    )
NEW
252
    await generateFlame(outputPath, command.generateFlame)
×
UNCOV
253
    return
×
254
  }
255

NEW
256
  const response = await fetchPprofStream({
×
257
    adminUrl,
258
    apiKey,
259
    type: command.target,
260
    seconds: command.seconds,
261
  })
NEW
262
  const { outputPath } = await writePprofCaptureToFile(
×
263
    response.stream,
264
    {
265
      contentDisposition: response.contentDisposition,
266
      type: command.target,
267
    },
268
    { outputPath: command.output }
269
  )
NEW
270
  await generateFlame(outputPath, command.generateFlame)
×
271
}
272

273
async function main() {
NEW
274
  const adminUrl = process.env.ADMIN_URL
×
NEW
275
  const apiKey = process.env.ADMIN_API_KEY
×
NEW
276
  if (!adminUrl) throw new Error('Please provide ADMIN_URL')
×
NEW
277
  if (!apiKey) throw new Error('Please provide ADMIN_API_KEY')
×
NEW
278
  await execute(parsePprofCommand(process.argv.slice(2)), adminUrl, apiKey)
×
279
}
280

281
if (require.main === module) {
1!
UNCOV
282
  main().catch((error) => {
×
283
    process.exitCode = 1
×
NEW
284
    console.error(error instanceof Error ? error.message : error)
×
285
  })
286
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc