Skip to content

Commit 180f2a2

Browse files
authored
Add profiling pipeline (#7961)
2 parents d2cf5de + 84295f3 commit 180f2a2

File tree

12 files changed

+358
-148
lines changed

12 files changed

+358
-148
lines changed

azure-pipelines-official.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ parameters:
2828
default: auto
2929

3030
variables:
31-
defaultDotnetVersion: '8.0.403'
31+
- template: /azure-pipelines/dotnet-variables.yml@self
3232

3333
resources:
3434
repositories:

azure-pipelines.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ schedules:
2929
- main
3030

3131
variables:
32-
- name: defaultDotnetVersion
33-
value: '8.0.403'
32+
- template: /azure-pipelines/dotnet-variables.yml@self
3433
- name: testVSCodeVersion
3534
${{ if eq( variables['Build.Reason'], 'Schedule' ) }}:
3635
value: insiders

azure-pipelines/dotnet-variables.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
variables:
2+
- name: defaultDotnetVersion
3+
value: '8.0.403'

azure-pipelines/profiling.yml

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,51 @@ schedules:
99

1010
variables:
1111
- name: TRACE_DROP_LOCATION
12-
value: $(Build.ArtifactStagingDirectory)/traces/
12+
value: $(Build.SourcesDirectory)/out/profiling/
1313
- name: MERGED_TRACE_LOCATION
14-
value: $(Build.ArtifactStagingDirectory)/mergedtrace/
14+
value: $(Build.SourcesDirectory)/out/profiling/merged/
15+
- name: LOGS_DIRECTORY
16+
value: $(Build.SourcesDirectory)/out/logs/
17+
- template: /azure-pipelines/dotnet-variables.yml@self
1518

19+
20+
resources:
21+
repositories:
22+
- repository: 1ESPipelineTemplates
23+
type: git
24+
name: 1ESPipelineTemplates/1ESPipelineTemplates
25+
ref: refs/tags/release
1626
extends:
1727
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
1828
parameters:
19-
pool:
20-
name: AzurePipelines-EO
21-
image: 1ESPT-Ubuntu20.04
22-
os: Linux
29+
sdl:
30+
sourceAnalysisPool:
31+
name: netcore1espool-internal
32+
image: 1es-windows-2022
33+
os: windows
2334
stages:
24-
- stage: run
35+
- stage: Run_Profiling
36+
displayName: Run Profiling
2537
jobs:
26-
- job: run
38+
- job: Profile
39+
pool:
40+
name: netcore1espool-internal
41+
image: 1es-ubuntu-2204
42+
os: linux
43+
templateContext:
44+
outputs:
45+
- output: pipelineArtifact
46+
targetPath: $(TRACE_DROP_LOCATION)
47+
artifactName: traces
48+
displayName: 📢 Publish intermediate trace files
49+
condition: failed()
50+
- output: pipelineArtifact
51+
targetPath: $(MERGED_TRACE_LOCATION)
52+
artifactName: merged mibc
53+
displayName: 📢 Publish merged MIBC
2754
steps:
28-
- pwsh: echo "Profiling"
55+
- template: /azure-pipelines/test.yml@self
56+
parameters:
57+
dotnetVersion: $(defaultDotnetVersion)
58+
npmCommand: profiling
59+
testVSCodeVersion: stable

azure-pipelines/test.yml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,17 @@ steps:
4444
mergeTestResults: true
4545
testRunTitle: $(System.StageDisplayName) $(Agent.JobName) (Attempt $(System.JobAttempt))
4646

47-
- task: PublishPipelineArtifact@1
48-
condition: failed()
49-
displayName: 'Upload integration test logs'
50-
inputs:
51-
targetPath: '$(Build.SourcesDirectory)/out/logs'
52-
artifactName: 'Test Logs ($(System.StageDisplayName)-$(Agent.JobName)-$(System.JobAttempt))'
47+
- ${{ if eq(variables['System.TeamProject'], 'internal') }}:
48+
- task: 1ES.PublishPipelineArtifact@1
49+
condition: failed()
50+
displayName: 'Upload integration test logs'
51+
inputs:
52+
path: '$(Build.SourcesDirectory)/out/logs'
53+
artifact: 'Test Logs ($(System.StageDisplayName)-$(Agent.JobName)-$(System.JobAttempt))'
54+
- ${{ else }}:
55+
- task: PublishPipelineArtifact@1
56+
condition: failed()
57+
displayName: 'Upload integration test logs'
58+
inputs:
59+
targetPath: '$(Build.SourcesDirectory)/out/logs'
60+
artifactName: 'Test Logs ($(System.StageDisplayName)-$(Agent.JobName)-$(System.JobAttempt))'

gulpfile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ require('./tasks/createTagsTasks');
1111
require('./tasks/debuggerTasks');
1212
require('./tasks/snapTasks');
1313
require('./tasks/signingTasks');
14+
require('./tasks/profilingTasks');

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"test:integration:devkit": "tsc -p ./ && gulp test:integration:devkit",
7474
"test:razor": "tsc -p ./ && npm run compile:razorTextMate && gulp test:razor",
7575
"test:razorintegration": "tsc -p ./ && gulp test:razorintegration",
76+
"profiling": "tsc -p ./ && gulp profiling",
7677
"test:artifacts": "tsc -p ./ && gulp test:artifacts",
7778
"omnisharptest": "tsc -p ./ && gulp omnisharptest",
7879
"omnisharptest:unit": "tsc -p ./ && gulp omnisharptest:unit",

src/lsptoolshost/profiling.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { EOL } from 'os';
7+
import { LogOutputChannel } from 'vscode';
8+
9+
export function getProfilingEnvVars(outputChannel: LogOutputChannel): NodeJS.ProcessEnv {
10+
let profilingEnvVars = {};
11+
if (process.env.ROSLYN_DOTNET_EventPipeOutputPath) {
12+
profilingEnvVars = {
13+
DOTNET_EnableEventPipe: 1,
14+
DOTNET_EventPipeConfig: 'Microsoft-Windows-DotNETRuntime:0x1F000080018:5',
15+
DOTNET_EventPipeOutputPath: process.env.ROSLYN_DOTNET_EventPipeOutputPath,
16+
DOTNET_ReadyToRun: 0,
17+
DOTNET_TieredCompilation: 1,
18+
DOTNET_TC_CallCounting: 0,
19+
DOTNET_TC_QuickJitForLoops: 1,
20+
DOTNET_JitCollect64BitCounts: 1,
21+
};
22+
outputChannel.trace(`Profiling enabled with:${EOL}${JSON.stringify(profilingEnvVars)}`);
23+
}
24+
25+
return profilingEnvVars;
26+
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import { registerSourceGeneratedFilesContentProvider } from './sourceGeneratedFi
7979
import { registerMiscellaneousFileNotifier } from './miscellaneousFileNotifier';
8080
import { TelemetryEventNames } from '../shared/telemetryEventNames';
8181
import { RazorDynamicFileChangedParams } from '../razor/src/dynamicFile/dynamicFileUpdatedParams';
82+
import { getProfilingEnvVars } from './profiling';
8283

8384
let _channel: vscode.LogOutputChannel;
8485
let _traceChannel: vscode.OutputChannel;
@@ -664,18 +665,23 @@ export class RoslynLanguageServer {
664665

665666
args.push('--extensionLogDirectory', context.logUri.fsPath);
666667

667-
const env = dotnetInfo.env;
668+
let env = dotnetInfo.env;
668669
if (!languageServerOptions.useServerGC) {
669670
// The server by default uses serverGC, if the user opts out we need to set the environment variable to disable it.
670671
env.DOTNET_gcServer = '0';
671672
_channel.debug('ServerGC disabled');
672673
}
673674

675+
const profilingEnvVars = getProfilingEnvVars(_channel);
676+
env = { ...env, ...profilingEnvVars };
677+
678+
_channel.trace(`Profiling environment variables: ${JSON.stringify(profilingEnvVars)}`);
679+
674680
let childProcess: cp.ChildProcessWithoutNullStreams;
675681
const cpOptions: cp.SpawnOptionsWithoutStdio = {
676682
detached: true,
677683
windowsHide: true,
678-
env: dotnetInfo.env,
684+
env: env,
679685
};
680686

681687
if (serverPath.endsWith('.dll')) {

tasks/profilingTasks.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import fs from 'fs';
7+
import * as gulp from 'gulp';
8+
import * as path from 'path';
9+
import { basicSlnTestProject, runIntegrationTest } from './testHelpers';
10+
import { outPath } from './projectPaths';
11+
import { execSync } from 'child_process';
12+
13+
createProfilingTasks();
14+
15+
function createProfilingTasks() {
16+
const profilingOutputFolder = path.join(outPath, 'profiling');
17+
gulp.task(`profiling:csharp:${basicSlnTestProject}`, async () => {
18+
// Ensure the profiling output folder exists, otherwise the outputs will not get written.
19+
fs.mkdirSync(path.join(outPath, 'profiling'), { recursive: true });
20+
21+
await runIntegrationTest(
22+
basicSlnTestProject,
23+
path.join('lsptoolshost', 'integrationTests'),
24+
`[C#][${basicSlnTestProject}]`,
25+
undefined,
26+
undefined,
27+
{
28+
ROSLYN_DOTNET_EventPipeOutputPath: path.join(profilingOutputFolder, 'csharp-trace.{pid}.nettrace'),
29+
}
30+
);
31+
32+
const files = fs.readdirSync(profilingOutputFolder);
33+
const nettraceFiles = files.filter((f) => f.endsWith('.nettrace'));
34+
if (nettraceFiles.length === 0) {
35+
throw new Error('No .nettrace files found in the profiling output folder.');
36+
}
37+
});
38+
39+
gulp.task('mergeTraces', async () => {
40+
await mergeTraces(profilingOutputFolder);
41+
});
42+
43+
gulp.task('profiling', gulp.series(`profiling:csharp:${basicSlnTestProject}`, 'mergeTraces'));
44+
}
45+
46+
async function mergeTraces(profilingOutputFolder: string) {
47+
const files = fs.readdirSync(profilingOutputFolder);
48+
const nettraceFiles = files.filter((f) => f.endsWith('.nettrace'));
49+
if (nettraceFiles.length === 0) {
50+
throw new Error('No .nettrace files found in the profiling output folder.');
51+
}
52+
53+
// Function to spawn a tool
54+
function spawnTool(command: string, args: string[], warnOnError = false) {
55+
try {
56+
console.log(`##[command] ${command} ${args.join(' ')}`);
57+
execSync(`${command} ${args.join(' ')}`, { stdio: 'inherit' });
58+
} catch (error) {
59+
if (warnOnError) {
60+
console.warn(`Failed: ${error}`);
61+
} else {
62+
throw error;
63+
}
64+
}
65+
}
66+
67+
// Ensure the dotnet-pgo tool is installed.
68+
// Additional versions of this can be found at https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet8-transport/NuGet/dotnet-pgo/
69+
spawnTool('dotnet', [
70+
'tool',
71+
'update',
72+
'-g',
73+
'dotnet-pgo',
74+
'--version',
75+
'8.0.0-rc.2.23479.6',
76+
'--add-source',
77+
'https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json',
78+
'--ignore-failed-sources',
79+
]);
80+
81+
console.log('##[group] Converting .nettrace to .mibc');
82+
nettraceFiles.forEach((file) => {
83+
spawnTool(
84+
'dotnet-pgo',
85+
[
86+
'create-mibc',
87+
'-t',
88+
path.join(profilingOutputFolder, file),
89+
'-o',
90+
path.join(profilingOutputFolder, `${path.basename(file, '.nettrace')}.mibc`),
91+
],
92+
true
93+
);
94+
});
95+
console.log('##[endgroup]');
96+
97+
const mibcFiles = fs.readdirSync(profilingOutputFolder).filter((f) => f.endsWith('.mibc'));
98+
if (mibcFiles.length === 0) {
99+
throw new Error('No .mibc files were produced.');
100+
}
101+
102+
const mergedTraceLocation = path.join(profilingOutputFolder, 'merged');
103+
fs.mkdirSync(mergedTraceLocation, { recursive: true });
104+
105+
const inputArgs = ['merge', '--compressed'];
106+
mibcFiles.forEach((file) => {
107+
inputArgs.push('-i', path.join(profilingOutputFolder, file));
108+
});
109+
inputArgs.push('-o', path.join(mergedTraceLocation, 'merged.mibc'));
110+
111+
spawnTool('dotnet-pgo', inputArgs);
112+
}

0 commit comments

Comments
 (0)