Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(flamegraph/models): make it mandatory to handle all spyNames #1300

Merged
merged 6 commits into from Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.js
Expand Up @@ -109,6 +109,8 @@ module.exports = {

// makes it easier to check what are local variables computated dynamically and what are static props
'react/destructuring-assignment': 'off',

'@typescript-eslint/switch-exhaustiveness-check': 'error',
},
env: {
browser: true,
Expand Down
Expand Up @@ -5,6 +5,7 @@ import {
Flamebearer,
singleFF,
doubleFF,
SpyName,
} from '@pyroscope/models/src';
import { PX_PER_LEVEL, BAR_HEIGHT, COLLAPSE_THRESHOLD } from './constants';
import type { FlamegraphPalette } from './colorPalette';
Expand Down Expand Up @@ -78,7 +79,9 @@ export default class Flamegraph {
sampleRate: this.flamebearer.sampleRate,
names: this.flamebearer.names,
levels: this.flamebearer.levels,
spyName: this.flamebearer.spyName,
// keep type narrow https://stackoverflow.com/q/54333982
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
spyName: this.flamebearer.spyName as SpyName,
units: this.flamebearer.units,
maxSelf: this.flamebearer.maxSelf,

Expand Down
Expand Up @@ -22,7 +22,7 @@ THIS SOFTWARE.
*/

/* eslint-disable no-continue */
import { createFF, Flamebearer } from '@pyroscope/models/src';
import { createFF, Flamebearer, SpyName } from '@pyroscope/models/src';
import {
formatPercent,
getFormatter,
Expand Down Expand Up @@ -240,7 +240,9 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
selectedLevel,
highlightModeOn,
isHighlighted,
spyName,
// keep type narrow https://stackoverflow.com/q/54333982
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
spyName: spyName as SpyName,
palette,
};

Expand Down Expand Up @@ -368,7 +370,7 @@ type getColorCfg = {
highlightModeOn: boolean;
isHighlighted: boolean;
names: string[];
spyName: string;
spyName: SpyName;
palette: FlamegraphPalette;
};

Expand Down
Expand Up @@ -189,7 +189,7 @@ describe('getPackageNameFromStackTrace', () => {
],
])(`.getPackageNameFromStackTrace('%s')`, (a, expected) => {
it(`returns '${expected}'`, () => {
expect(getPackageNameFromStackTrace('default', a)).toBe(expected);
expect(getPackageNameFromStackTrace('unknown', a)).toBe(expected);
});
});
});
Expand Down
@@ -1,6 +1,7 @@
/* eslint-disable camelcase */
import Color from 'color';
import { scaleLinear } from 'd3-scale';
import type { SpyName } from '@pyroscope/models/src';
import murmurhash3_32_gc from './murmur3';
import type { FlamegraphPalette } from './colorPalette';

Expand Down Expand Up @@ -71,8 +72,8 @@ export function colorGreyscale(v: number, a: number) {
return Color.rgb(v, v, v).alpha(a);
}

// TODO use @pyroscope/models?
function spyToRegex(spyName: string) {
function spyToRegex(spyName: SpyName): RegExp {
// eslint-disable-next-line default-case
switch (spyName) {
case 'dotnetspy':
return /^(?<packageName>.+)\.(.+)\.(.+)\(.*\)$/;
Expand Down Expand Up @@ -100,16 +101,16 @@ function spyToRegex(spyName: string) {
return /^(?<packageName>.+\/)(?<filename>.+\.)(?<functionName>.+)$/;
case 'pyroscope-rs':
return /^(?<packageName>[^::]+)/;

// we don't have enough information
default:
case 'unknown':
return /^(?<packageName>.+)$/;
}

return /^(?<packageName>.+)$/;
}

// TODO spy names?
export function getPackageNameFromStackTrace(
spyName: string,
spyName: SpyName,
stackTrace: string
) {
if (stackTrace.length === 0) {
Expand Down
Expand Up @@ -5,7 +5,7 @@ const TestData = {
numTicks: 0,
sampleRate: 0,
units: 'samples' as const,
spyName: '',
spyName: 'unknown' as const,
format: 'single' as const,
version: 0,
},
Expand Down Expand Up @@ -35,7 +35,7 @@ const TestData = {
units: 'samples' as const,
fitMode: 'HEAD',

spyName: 'gospy',
spyName: 'gospy' as const,
version: 1,
},
ComplexTree: {
Expand Down Expand Up @@ -360,7 +360,7 @@ const TestData = {
],
numTicks: 178,
maxSelf: 16,
spyName: 'gospy',
spyName: 'gospy' as const,
sampleRate: 100,
units: 'samples' as const,
format: 'single' as const,
Expand Down Expand Up @@ -414,7 +414,7 @@ const TestData = {
],
numTicks: 1978,
maxSelf: 604,
spyName: 'gospy',
spyName: 'gospy' as const,
sampleRate: 100,
units: 'samples' as const,
format: 'double' as const,
Expand Down
Expand Up @@ -53,7 +53,7 @@ function mountFlamebearer(p: { profile?: Profile; flamebearer?: Flamebearer }) {
names: [],
units: '',
levels: [[]],
spyName: '',
spyName: 'unknown',
numTicks: 0,
sampleRate: 0,
maxSelf: 0,
Expand Down
10 changes: 2 additions & 8 deletions packages/pyroscope-models/src/flamebearer.ts
@@ -1,3 +1,4 @@
import { SpyName } from './spyName';
/**
* @deprecated one should use the Profile model
*/
Expand Down Expand Up @@ -31,14 +32,7 @@ export type Flamebearer = {
| 'lock_nanoseconds'
| 'trace_samples'
| '';
spyName:
| 'dotneyspy'
| 'ebpfspy'
| 'gospy'
| 'phpspy'
| 'pyspy'
| 'rbspy'
| string;
spyName: SpyName;
// format: 'double' | 'single';
// leftTicks?: number;
// rightTicks?: number;
Expand Down
1 change: 1 addition & 0 deletions packages/pyroscope-models/src/index.ts
Expand Up @@ -2,3 +2,4 @@ export * from './profile';
export * from './flamebearer';
export * from './trace';
export * from './decode';
export * from './spyName';
2 changes: 1 addition & 1 deletion packages/pyroscope-models/src/profile.spec.ts
Expand Up @@ -6,7 +6,7 @@ describe('Profile', () => {
const expected = {
format: 'single',
sampleRate: 100,
spyName: '',
spyName: 'unknown',
units: '',
};

Expand Down
5 changes: 2 additions & 3 deletions packages/pyroscope-models/src/profile.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { SpyNameSchema } from './spyName';

export const FlamebearerSchema = z.object({
names: z.array(z.string().nonempty()),
Expand Down Expand Up @@ -40,9 +41,7 @@ export const MetadataSchema = z.object({

format: z.enum(['single', 'double']),
sampleRate: z.number(),
spyName: z
.enum(['dotnetspy', 'ebpfspy', 'gospy', 'phpspy', 'pyspy', 'rbspy'])
.or(z.string()),
spyName: SpyNameSchema,

units: UnitsSchema,
});
Expand Down
18 changes: 18 additions & 0 deletions packages/pyroscope-models/src/spyName.spec.ts
@@ -0,0 +1,18 @@
import { SpyNameSchema, AllSpyNames } from './spyName';

describe('SpyName', () => {
it('fails when when passing an unsupported value', () => {
expect(SpyNameSchema.safeParse('foo').success).toBe(false);
});

it('defaults to "unknown" when absent', () => {
expect(SpyNameSchema.parse('')).toBe('unknown');
});

test.each(AllSpyNames.map((a) => [a, a]))(
'parse("%s") should return "%s"',
(spyName) => {
expect(SpyNameSchema.parse(spyName)).toBe(spyName);
}
);
});
30 changes: 30 additions & 0 deletions packages/pyroscope-models/src/spyName.ts
@@ -0,0 +1,30 @@
import { z } from 'zod';

export const SpyNameFirstClass = [
'dotnetspy',
'ebpfspy',
'gospy',
'phpspy',
'pyspy',
'rbspy',
'nodespy',
'javaspy',
'pyroscope-rs',
] as const;

export const SpyNameOther = [
'scrape', // for compability purposes, it should be golang
'tracing',
'unknown',
] as const;

export const AllSpyNames = [...SpyNameFirstClass, ...SpyNameOther] as const;

export const SpyNameSchema = z.preprocess((val) => {
if (!val) {
return 'unknown';
}
return val;
}, z.enum(AllSpyNames).default('unknown'));

export type SpyName = z.infer<typeof SpyNameSchema>;