@@ -26,9 +26,38 @@ public async Task RunAsync(string sprintName, string outFile)
2626 {
2727 using FileStream fileStream = OpenOutputFile ( outFile ) ;
2828
29- ReportData data = await GetDataAsync ( sprintName ) ;
29+ PipelineData buildPipelineData = new PipelineData ( )
30+ {
31+ PipelineName = "NuGet.Client-PrivateDev" ,
32+ BranchFilterQueryString = "101196%2C101196%2C101196%2C101196%2C101196" ,
33+ DatabaseName = "AzureDevOps" ,
34+ OrganizationName = "devdiv" ,
35+ ProjectId = "0bdbc590-a062-4c3f-b0f6-9383f67865ee" ,
36+ ProjectName = "DevDiv" ,
37+ DefinitionId = "8118" ,
38+ SourceBranch = "refs/heads/dev" ,
39+ Reason = "schedule" ,
40+ } ;
41+
42+ ReportData buildReportData = await GetDataAsync ( sprintName , queryName : "dev branch builds" , buildPipelineData ) ;
43+
44+ PipelineData funcUnitTestPipelineData = new PipelineData ( )
45+ {
46+ PipelineName = "NuGet.Client CI" ,
47+ DatabaseName = "AzureDevOps" ,
48+ BranchFilterQueryString = "86197%2C86197%2C86197" ,
49+ OrganizationName = "dnceng-public" ,
50+ ProjectId = "cbb18261-c48f-4abb-8651-8cdcb5474649" ,
51+ ProjectName = "public" ,
52+ DefinitionId = "289" ,
53+ SourceBranch = "refs/heads/dev" ,
54+ Reason = "individualCI" ,
55+ } ;
3056
31- Output ( data , fileStream ) ;
57+ ReportData funcUnitTestReportData = await GetDataAsync ( sprintName , queryName : "dev branch Functional & Unit tests" , funcUnitTestPipelineData ) ;
58+
59+ var data = new Tuple < PipelineData , ReportData > [ ] { new ( buildPipelineData , buildReportData ) , new ( funcUnitTestPipelineData , funcUnitTestReportData ) } ;
60+ Output ( data , fileStream , reportSprintName : sprintName ) ;
3261 }
3362
3463 private FileStream OpenOutputFile ( string outFile )
@@ -52,7 +81,7 @@ private FileStream OpenOutputFile(string outFile)
5281 return fileStream ;
5382 }
5483
55- private async Task < ReportData > GetDataAsync ( string sprintName )
84+ private async Task < ReportData > GetDataAsync ( string sprintName , string queryName , PipelineData pipelineData )
5685 {
5786 TextWriter ? log ;
5887 if ( Console . IsOutputRedirected )
@@ -71,11 +100,11 @@ private async Task<ReportData> GetDataAsync(string sprintName)
71100 string failedBuildsQuery = $@ "let start = startofday(datetime(""{ startOfSprint . ToString ( "yyyy-MM-dd" ) } ""));
72101let end = endofday(datetime(""{ endOfSprint . ToString ( "yyyy-MM-dd" ) } ""));
73102let nugetBuilds = Build
74- | where OrganizationName == 'devdiv ' and ProjectId == '0bdbc590-a062-4c3f-b0f6-9383f67865ee ' and DefinitionId == 8118 and FinishTime between (start..end) and SourceBranch == 'refs/heads/dev ' and Reason == 'schedule ';
103+ | where OrganizationName == '{ pipelineData . OrganizationName } ' and ProjectId == '{ pipelineData . ProjectId } ' and DefinitionId == { pipelineData . DefinitionId } and FinishTime between (start..end) and SourceBranch == '{ pipelineData . SourceBranch } ' and Reason == '{ pipelineData . Reason } ';
75104let sprintBuilds = nugetBuilds
76105| project BuildId;
77106let previousAttempts = BuildTimelineRecord
78- | where OrganizationName == 'devdiv ' and ProjectId == '0bdbc590-a062-4c3f-b0f6-9383f67865ee ' and BuildId in (sprintBuilds)
107+ | where OrganizationName == '{ pipelineData . OrganizationName } ' and ProjectId == '{ pipelineData . ProjectId } ' and BuildId in (sprintBuilds)
79108| summarize PreviousAttempts=countif(PreviousAttempts !in ('', '[]')) by BuildId
80109| where PreviousAttempts > 0
81110| project BuildId;
@@ -86,10 +115,10 @@ private async Task<ReportData> GetDataAsync(string sprintName)
86115 string buildCountQuery = $@ "let start = startofday(datetime(""{ startOfSprint . ToString ( "yyyy-MM-dd" ) } ""));
87116let end = endofday(datetime(""{ endOfSprint . ToString ( "yyyy-MM-dd" ) } ""));
88117Build
89- | where OrganizationName == 'devdiv ' and ProjectId == '0bdbc590-a062-4c3f-b0f6-9383f67865ee ' and DefinitionId == 8118 and FinishTime between (start..end) and SourceBranch == 'refs/heads/dev '
118+ | where OrganizationName == '{ pipelineData . OrganizationName } ' and ProjectId == '{ pipelineData . ProjectId } ' and DefinitionId == { pipelineData . DefinitionId } and FinishTime between (start..end) and SourceBranch == '{ pipelineData . SourceBranch } '
90119| summarize count()" ;
91120
92- var connectionBuilder = new KustoConnectionStringBuilder ( "https://1es.kusto.windows.net/" , "AzureDevOps" )
121+ var connectionBuilder = new KustoConnectionStringBuilder ( "https://1es.kusto.windows.net/" , pipelineData . DatabaseName )
93122 {
94123 FederatedSecurity = true
95124 } ;
@@ -102,15 +131,18 @@ private async Task<ReportData> GetDataAsync(string sprintName)
102131
103132 using ( var client = KustoClientFactory . CreateCslQueryProvider ( connectionBuilder ) )
104133 {
134+ log ? . WriteLine ( "Query arguments: pipelineName =" + pipelineData . PipelineName + " | " + "organizationName = " + pipelineData . OrganizationName + " | " + "projectId=" + pipelineData . ProjectId + " | " + "definitionId ="
135+ + pipelineData . DefinitionId + " | " + "sourceBranch=" + pipelineData . SourceBranch + " | " + "reason=" + pipelineData . Reason ) ;
105136 log ? . WriteLine ( $ "Querying builds from { startOfSprint : yyyy-MM-dd} to { endOfSprint : yyyy-MM-dd} ") ;
106- var ( failedBuilds , trackingIssues ) = await GetFailedBuilds ( client , crp , failedBuildsQuery , log ) ;
137+ var ( failedBuilds , trackingIssues ) = await GetFailedBuilds ( client , crp , failedBuildsQuery , pipelineData , log ) ;
107138
108139 log ? . WriteLine ( "Querying total builds in sprint" ) ;
109- int totalBuilds = await GetBuildCount ( client , crp , buildCountQuery ) ;
140+ int totalBuilds = await GetBuildCount ( client , crp , buildCountQuery , pipelineData ) ;
110141
111142 data = new ReportData ( )
112143 {
113144 SprintName = sprintName ,
145+ QueryName = queryName ,
114146 KustoQuery = failedBuildsQuery ,
115147 FailedBuilds = failedBuilds ,
116148 TrackingIssues = trackingIssues ,
@@ -121,9 +153,9 @@ private async Task<ReportData> GetDataAsync(string sprintName)
121153 return data ;
122154 }
123155
124- private async Task < int > GetBuildCount ( ICslQueryProvider client , ClientRequestProperties crp , string query )
156+ private async Task < int > GetBuildCount ( ICslQueryProvider client , ClientRequestProperties crp , string query , PipelineData pipelineData )
125157 {
126- using var result = await client . ExecuteQueryAsync ( "AzureDevOps" , query , crp ) ;
158+ using var result = await client . ExecuteQueryAsync ( pipelineData . DatabaseName , query , crp ) ;
127159
128160 if ( ! result . Read ( ) )
129161 {
@@ -141,12 +173,13 @@ private async Task<int> GetBuildCount(ICslQueryProvider client, ClientRequestPro
141173 ICslQueryProvider client ,
142174 ClientRequestProperties crp ,
143175 string query ,
176+ PipelineData pipelineData ,
144177 TextWriter ? log )
145178 {
146179 List < ReportData . FailedBuild > failedBuilds = new ( ) ;
147180 Dictionary < string , string > trackingIssues = new ( ) ;
148181
149- var result = await client . ExecuteQueryAsync ( "AzureDevOps" , query , crp ) ;
182+ var result = await client . ExecuteQueryAsync ( pipelineData . DatabaseName , query , crp ) ;
150183
151184 int buildIdColumn = result . GetOrdinal ( "BuildId" ) ;
152185 int buildNumberColumn = result . GetOrdinal ( "BuildNumber" ) ;
@@ -168,7 +201,7 @@ private async Task<int> GetBuildCount(ICslQueryProvider client, ClientRequestPro
168201 {
169202 log ? . WriteLine ( $ "Checking failed build { i + 1 } /{ failedBuilds . Count } ") ;
170203
171- var ( details , tracking ) = await GetFailedBuildDetails ( failedBuilds [ i ] . Id , client , crp ) ;
204+ var ( details , tracking ) = await GetFailedBuildDetails ( failedBuilds [ i ] . Id , client , crp , pipelineData ) ;
172205
173206 foreach ( var kvp in tracking )
174207 {
@@ -190,16 +223,17 @@ private async Task<int> GetBuildCount(ICslQueryProvider client, ClientRequestPro
190223 private async Task < ( IReadOnlyList < ReportData . FailureDetail > details , IReadOnlyDictionary < string , string > tracking ) > GetFailedBuildDetails (
191224 long buildId ,
192225 ICslQueryProvider client ,
193- ClientRequestProperties crp )
226+ ClientRequestProperties crp ,
227+ PipelineData pipelineData )
194228 {
195229 List < ReportData . FailureDetail > details = new ( ) ;
196230 Dictionary < string , string > trackingIssues = new ( ) ;
197231
198232 List < Dictionary < string , object > > rows = new ( ) ;
199233
200- var query = @"BuildTimelineRecord
201- | where OrganizationName == 'devdiv ' and ProjectId == '0bdbc590-a062-4c3f-b0f6-9383f67865ee ' and BuildId == " + buildId ;
202- using ( var result = await client . ExecuteQueryAsync ( "AzureDevOps" , query , crp ) )
234+ var query = $ @ "BuildTimelineRecord
235+ | where OrganizationName == '{ pipelineData . OrganizationName } ' and ProjectId == '{ pipelineData . ProjectId } ' and BuildId == { buildId } " ;
236+ using ( var result = await client . ExecuteQueryAsync ( pipelineData . DatabaseName , query , crp ) )
203237 {
204238 while ( result . Read ( ) )
205239 {
@@ -293,26 +327,57 @@ bool IsFailedTask(Dictionary<string, object> timelineEntry, string parentId)
293327 }
294328 }
295329
296- private void Output ( ReportData data , FileStream outputFileStream )
330+ private void Output ( Tuple < PipelineData , ReportData > [ ] pipelineReportDataList , FileStream outputFileStream , string reportSprintName )
297331 {
298- if ( data == null ) { throw new ArgumentNullException ( nameof ( data ) ) ; }
299332 if ( outputFileStream is null || ! outputFileStream . CanRead || ! outputFileStream . CanWrite ) { throw new ArgumentException ( paramName : nameof ( outputFileStream ) , message : "Cannot read and write to output file" ) ; }
300- if ( data . FailedBuilds == null ) { throw new ArgumentException ( paramName : nameof ( data . FailedBuilds ) , message : "data.FailedBuilds must not be null" ) ; }
333+ using var sw = new StreamWriter ( outputFileStream , new UTF8Encoding ( encoderShouldEmitUTF8Identifier : false ) ) ;
301334
302- float reliability = ( data . TotalBuilds - data . FailedBuilds . Count ) * 100.0f / data . TotalBuilds ;
303- int failedBuildsOnlyBecauseOfApex = data . FailedBuilds . Where ( b => b . Details ? . Count == 1 && b . Details [ 0 ] . Job == "Apex Test Execution" ) . Count ( ) ;
304- float reliabilityIgnoringApex = ( data . TotalBuilds - data . FailedBuilds . Count + failedBuildsOnlyBecauseOfApex ) * 100.0f / data . TotalBuilds ;
335+ sw . WriteLine ( $ "# NuGet Client CI Reliability " + reportSprintName ) ;
305336
306- using var sw = new StreamWriter ( outputFileStream , new UTF8Encoding ( encoderShouldEmitUTF8Identifier : false ) ) ;
307- sw . WriteLine ( "# NuGet.Client CI Reliability " + data . SprintName ) ;
337+ foreach ( var pipelineReportData in pipelineReportDataList )
338+ {
339+ var pipelineData = pipelineReportData . Item1 ;
340+ var data = pipelineReportData . Item2 ;
341+ if ( data == null ) { throw new ArgumentNullException ( nameof ( data ) ) ; }
342+ if ( data . FailedBuilds == null ) { throw new ArgumentException ( paramName : nameof ( data . FailedBuilds ) , message : "data.FailedBuilds must not be null" ) ; }
343+
344+ float reliability = ( data . TotalBuilds - data . FailedBuilds . Count ) * 100.0f / data . TotalBuilds ;
345+ int failedBuildsOnlyBecauseOfApex = data . FailedBuilds . Where ( b => b . Details ? . Count == 1 && b . Details [ 0 ] . Job == "Apex Test Execution" ) . Count ( ) ;
346+ float reliabilityIgnoringApex = ( data . TotalBuilds - data . FailedBuilds . Count + failedBuildsOnlyBecauseOfApex ) * 100.0f / data . TotalBuilds ;
347+
348+ OutputPipelineData ( pipelineData , data , reliability , reliabilityIgnoringApex , sw ) ;
349+
350+ sw . WriteLine ( ) ;
351+ sw . WriteLine ( ) ;
352+ sw . WriteLine ( "### Tracking" ) ;
353+ if ( data . TrackingIssues . Count == 0 )
354+ {
355+ sw . WriteLine ( "No tracking issues" ) ;
356+ }
357+ else
358+ {
359+ foreach ( var kvp in data . TrackingIssues )
360+ {
361+ sw . WriteLine ( ) ;
362+ sw . WriteLine ( $ "- { kvp . Key } ") ;
363+ sw . WriteLine ( ) ;
364+ sw . WriteLine ( kvp . Value ) ;
365+ }
366+ }
367+ }
368+ }
369+
370+ private static void OutputPipelineData ( PipelineData pipelineData , ReportData data , float reliability , float reliabilityIgnoringApex , StreamWriter sw )
371+ {
372+ sw . WriteLine ( $ "## { pipelineData . PipelineName } ") ;
308373 sw . WriteLine ( ) ;
309- sw . WriteLine ( "[NuGet.Client-PR dev branch builds ](https://dev.azure.com/devdiv/DevDiv /_build?definitionId=8118 &branchFilter=101196%2C101196%2C101196%2C101196%2C101196 )") ;
374+ sw . WriteLine ( $ "[ { pipelineData . PipelineName } { data . QueryName } ](https://dev.azure.com/{ pipelineData . OrganizationName } / { pipelineData . ProjectName } /_build?definitionId={ pipelineData . DefinitionId } &branchFilter={ pipelineData . BranchFilterQueryString } )") ;
310375 sw . WriteLine ( ) ;
311376 sw . WriteLine ( "|Total Builds|Failed Builds|Reliability|Reliability Ignoring Apex|" ) ;
312377 sw . WriteLine ( "|:--:|:--:|:--:|:--:|" ) ;
313378 sw . WriteLine ( $ "|{ data . TotalBuilds } |{ data . FailedBuilds . Count } |{ reliability : f1} %|{ reliabilityIgnoringApex : f1} %|") ;
314379 sw . WriteLine ( ) ;
315- sw . WriteLine ( "## Failed Builds" ) ;
380+ sw . WriteLine ( "### Failed Builds" ) ;
316381 sw . WriteLine ( ) ;
317382 sw . WriteLine ( "**Note:**: Includes builds that succeeded on retry, so first attempt failed" ) ;
318383 sw . WriteLine ( ) ;
@@ -339,11 +404,11 @@ private void Output(ReportData data, FileStream outputFileStream)
339404 {
340405 if ( build . Details . Count > 1 )
341406 {
342- sw . WriteLine ( $ " <td rowspan=\" { build . Details . Count } \" ><a href=\" https://dev.azure.com/devdiv/DevDiv /_build/results?buildId={ build . Id } \" >{ build . Number } </a></td>") ;
407+ sw . WriteLine ( $ " <td rowspan=\" { build . Details . Count } \" ><a href=\" https://dev.azure.com/{ pipelineData . OrganizationName } / { pipelineData . ProjectName } /_build/results?buildId={ build . Id } \" >{ build . Number } </a></td>") ;
343408 }
344409 else
345410 {
346- sw . WriteLine ( $ " <td><a href=\" https://dev.azure.com/devdiv/DevDiv /_build/results?buildId={ build . Id } \" >{ build . Number } </a></td>") ;
411+ sw . WriteLine ( $ " <td><a href=\" https://dev.azure.com/{ pipelineData . OrganizationName } / { pipelineData . ProjectName } /_build/results?buildId={ build . Id } \" >{ build . Number } </a></td>") ;
347412 }
348413 }
349414 sw . WriteLine ( $ " <td>{ build . Details [ i ] . Job } </td>") ;
@@ -353,16 +418,6 @@ private void Output(ReportData data, FileStream outputFileStream)
353418 }
354419 }
355420 sw . WriteLine ( "</table>" ) ;
356- sw . WriteLine ( ) ;
357- sw . WriteLine ( "### Tracking" ) ;
358-
359- foreach ( var kvp in data . TrackingIssues )
360- {
361- sw . WriteLine ( ) ;
362- sw . WriteLine ( $ "- { kvp . Key } ") ;
363- sw . WriteLine ( ) ;
364- sw . WriteLine ( kvp . Value ) ;
365- }
366421 }
367422
368423 private class CiReliabilityCommandFactory : ICommandFactory
@@ -389,7 +444,7 @@ public Command CreateCommand(Type type, GitHubPatBinder patBinder)
389444 return command ;
390445 }
391446
392- public async Task RunAsync ( string sprint , string outfile )
447+ public async Task RunAsync ( string sprint , string outFile )
393448 {
394449 var serviceProvider = new ServiceCollection ( )
395450 . AddGithubIssueTagger ( )
@@ -399,7 +454,7 @@ public async Task RunAsync(string sprint, string outfile)
399454 using ( scopeFactory . CreateScope ( ) )
400455 {
401456 var report = serviceProvider . GetRequiredService < CiReliabilityReport > ( ) ;
402- await report . RunAsync ( sprint , outfile ) ;
457+ await report . RunAsync ( sprint , outFile ) ;
403458 }
404459 }
405460 }
0 commit comments