1 // This script generates documentation about the various configuration options that
2 // are available and how to use them.
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Globalization;
11 using System.Text.RegularExpressions;
12 using static System.Console;
14 char dirSep = Path.DirectorySeparatorChar;
15 var clrConfig = $"..{dirSep}..{dirSep}src{dirSep}inc{dirSep}clrconfigvalues.h";
16 var jitConfig = $"..{dirSep}..{dirSep}src{dirSep}jit{dirSep}jitconfigvalues.h";
17 Console.Out.NewLine = "\n";
18 Console.Error.NewLine = "\n";
23 private string _class;
26 public string Category;
28 public string DefaultValue;
29 public string Description;
39 value = value ?? String.Empty;
40 int indexOfUnderscore = value.IndexOf("_");
41 if (indexOfUnderscore > 0 && (value.Contains("INTERNAL") || value.Contains("EXTERNAL") || value.Contains("UNSUPPORTED")))
43 _name = value.Substring(indexOfUnderscore + 1);
57 value = value ?? String.Empty;
58 int indexOfUnderscore = value.IndexOf("_");
59 if (indexOfUnderscore > 0 && (value.Contains("INTERNAL") || value.Contains("EXTERNAL") || value.Contains("UNSUPPORTED")))
61 _class = value.Substring(0, indexOfUnderscore);
70 public string Location => $"{File}:{Line}";
72 private const string StringType = "STRING";
73 private const string DWORD_Type = "DWORD";
74 private const string SpaceSeparatedValues = "SSV";
76 private static Regex s_cppString = new Regex("\"([^\"\\\\]|\\\\.)*\"", RegexOptions.Compiled);
88 public Knob(string line, bool isRetail, bool isClrConfigFile, StreamReader reader, ref int lineNum, out string nextLine)
93 _class = String.Empty;
96 Category = String.Empty;
98 DefaultValue = String.Empty;
99 Description = String.Empty;
100 Flags = String.Empty;
102 File = isClrConfigFile ? "clrconfigvalues.h" : "jitconfigvalues.h";
104 string[] parts0 = null;
105 string[] parts1 = null;
106 string[] parts2 = null;
108 // Split on first open parenthesis
109 parts0 = line.Split(new char[]{'('}, 2, StringSplitOptions.None);
111 if (parts0.Length > 1)
113 Fields[] numParts1 = null;
116 // CONFIG_DWORD_INFO(symbol, name, defaultValue, description)
117 case "CONFIG_DWORD_INFO":
118 case "RETAIL_CONFIG_DWORD_INFO":
119 numParts1 = new Fields[] { Fields.Symbol, Fields.Name, Fields.DefaultValue, Fields.Description };
120 parts1 = parts0[1].Split(new[] { ',' }, 4);
124 // CONFIG_DWORD_INFO_DIRECT_ACCESS(symbol, name, description)
125 case "CONFIG_DWORD_INFO_DIRECT_ACCESS":
126 case "RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS":
127 numParts1 = new Fields[] { Fields.Symbol, Fields.Name, Fields.Description };
128 parts1 = parts0[1].Split(new[] { ',' }, 3);
132 // CONFIG_STRING_INFO(symbol, name, description)
133 case "CONFIG_STRING_INFO":
134 case "RETAIL_CONFIG_STRING_INFO":
135 case "CONFIG_STRING_INFO_DIRECT_ACCESS":
136 case "RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS":
137 numParts1 = new Fields[] { Fields.Symbol, Fields.Name, Fields.Description };
138 parts1 = parts0[1].Split(new[] { ',' }, 3);
142 // CONFIG_DWORD_INFO_EX(symbol, name, defaultValue, description, lookupOptions)
143 case "CONFIG_DWORD_INFO_EX":
144 case "RETAIL_CONFIG_DWORD_INFO_EX":
145 numParts1 = new Fields[] { Fields.Symbol, Fields.Name, Fields.DefaultValue, Fields.Description, Fields.LookupOptions };
146 parts1 = parts0[1].Split(new[] { ',' }, 4);
150 // CONFIG_STRING_INFO_EX(symbol, name, description, lookupOptions)
151 case "CONFIG_STRING_INFO_EX":
152 case "RETAIL_CONFIG_STRING_INFO_EX":
153 numParts1 = new Fields[] { Fields.Symbol, Fields.Name, Fields.Description, Fields.LookupOptions };
154 parts1 = parts0[1].Split(new[] { ',' }, 3);
158 // CONFIG_INTEGER(symbol, name, defaultValue) description in single line comments
159 case "CONFIG_INTEGER":
160 numParts1 = new Fields[] { Fields.Symbol, Fields.Name, Fields.DefaultValue };
161 parts1 = parts0[1].Split(new[] { ',' }, 3);
165 // CONFIG_STRING(symbol, name) description in single line comments
166 case "CONFIG_STRING":
167 numParts1 = new Fields[] { Fields.Symbol, Fields.Name };
168 parts1 = parts0[1].Split(new[] { ',' }, 2);
172 // CONFIG_METHODSET(symbol, name) description in single line comments
173 case "CONFIG_METHODSET":
174 numParts1 = new Fields[] { Fields.Symbol, Fields.Name };
175 parts1 = parts0[1].Split(new[] { ',' }, 2);
176 Type = SpaceSeparatedValues;
180 throw new ArgumentException($"Unsupported C++ macro definition: {parts0[0]}", nameof(line));
184 if (numParts1[parts1.Length - 1] == Fields.Description)
186 var descMatch = s_cppString.Match(parts1[parts1.Length - 1]);
187 Description = descMatch.Value.Substring(1, descMatch.Length - 2);
189 // Parse Flags if present
190 if (parts1.Length < numParts1.Length && numParts1[parts1.Length] == Fields.LookupOptions)
192 var tempFlags = parts1[parts1.Length - 1].Substring(descMatch.Index + descMatch.Length).TrimStart(' ', ',');
193 Flags = tempFlags.Replace("CLRConfig::", null).Replace(")", null).Replace("|", "\\|");
197 // Parse Description in jitconfigvalues.h file which is in single line C++ comments
198 else if (!isClrConfigFile)
200 int commentIndex = line.IndexOf(" // ");
201 var description = commentIndex >= 0 ? line.Substring(commentIndex + 4) : String.Empty;
202 if (description.Length > 0)
204 nextLine = reader.ReadLine();
206 while (nextLine != null)
208 var workLine = nextLine.Trim();
209 if (workLine.StartsWith("// "))
211 description += workLine.Substring(2).TrimEnd();
212 nextLine = reader.ReadLine();
221 Description = description;
224 Description = Description.Replace("|", "\\|");
226 // Parse DefaultValue
227 int indexOfDefaultValue = -1;
228 if ((indexOfDefaultValue = Array.IndexOf<Fields>(numParts1, Fields.DefaultValue)) >= 0 && indexOfDefaultValue < parts1.Length)
232 DefaultValue = parts1[indexOfDefaultValue].TrimEnd(')');
236 int indexOfCloseParenth = parts1[indexOfDefaultValue].IndexOf(")", StringComparison.Ordinal);
237 DefaultValue = parts1[indexOfDefaultValue].Substring(0, indexOfCloseParenth);
240 DefaultValue = DefaultValue.Trim();
244 var nameMatch = s_cppString.Match(parts1[1]);
245 Name = nameMatch.Captures.Count > 0 ? nameMatch.Value.Substring(1, nameMatch.Length - 2) : String.Empty;
248 if (parts1[0].Length > 0)
250 Class = Name.Length > 0 ? parts1[0].Replace(Name, null) : parts1[0];
251 Class = Class.TrimEnd('_');
258 Flags = String.Empty;
264 public override string ToString()
266 return $"{{Knob: Name: {Name}, Category: {Category}, Retail: {Retail}, Class: {Class}, Type: {Type}, DefaultValue: {DefaultValue}, Description: {Description}, Flags: {Flags}, Location: {File}:{Line}}}";
270 public static void ParseConfigFile(string filePath, bool isClrConfigFile, SortedDictionary<string, SortedDictionary<string, Knob>> categorizedKnobsDictionary)
272 using (StreamReader clrReader = new StreamReader(filePath, new UTF8Encoding(false)))
274 SortedDictionary<string, Knob> knobsDictionary = null;
275 string currentCategory = null;
278 string line = clrReader.ReadLine();
281 bool isRetail = false;
282 string nextLine = null;
284 if (line.StartsWith("CONFIG_", StringComparison.Ordinal) || (isRetail = line.StartsWith("RETAIL_CONFIG_", StringComparison.Ordinal)))
286 var clrKnob = new Knob(line, isRetail, isClrConfigFile, clrReader, ref lineNumber, out nextLine);
287 clrKnob.Category = currentCategory;
288 clrKnob.Line = lineNumber;
289 if (!knobsDictionary.ContainsKey(clrKnob.Name))
291 knobsDictionary.Add(clrKnob.Name, clrKnob);
295 Knob dupKnob = knobsDictionary[clrKnob.Name];
296 if (dupKnob.File != clrKnob.File && !dupKnob.Retail)
298 WriteLine($"Duplicate: {dupKnob.Location} {dupKnob.Name} -> {clrKnob.Location} {clrKnob.Name}");
302 else if (line.StartsWith("///", StringComparison.Ordinal) && (line = line.Trim()).Length > 3)
304 line = line.Replace("///", null).Trim();
305 if (!categorizedKnobsDictionary.ContainsKey(line))
307 knobsDictionary = new SortedDictionary<string, Knob>();
308 categorizedKnobsDictionary.Add(line, knobsDictionary);
309 currentCategory = line;
313 knobsDictionary = categorizedKnobsDictionary[line];
314 currentCategory = line;
318 if (nextLine != null)
324 line = clrReader.ReadLine();
331 public static class ConfigKnobsDoc
333 public static string IntroSection =
334 "There are two primary ways to configure runtime behavior: CoreCLR hosts can pass in key-value string pairs during runtime initialization, or users can set special variables in the environment or registry. Today, the set of configuration options that can be set via the former method is relatively small, but moving forward, we expect to add more options there. Each set of options is described below.\n";
336 public static string HostConfigurationKnobsPart1 =
337 "## Host Configuration Knobs\nThese can be passed in by a host during initialization. Note that the values are all passed in as strings, so if the type is boolean, the value would be the string \"true\" or \"false\", and if it's a numeric value, it would be in the form \"123\".\n";
339 public static string HostConfigurationKnobsPart2 =
340 "\nName | Description | Type\n" +
341 "-----|-------------|------\n" +
342 "`System.GC.Concurrent` | Enable concurrent GC | boolean\n" +
343 "`System.GC.Server` | Enable server GC | boolean\n" +
344 "`System.GC.RetainVM` | Put segments that should be deleted on a standby list for future use instead of releasing them back to the OS | boolean\n" +
345 "`System.Runtime.TieredCompilation` | Enable tiered compilation | boolean\n" +
346 "`System.Threading.ThreadPool.MinThreads` | Override MinThreads for the ThreadPool worker pool | numeric\n" +
347 "`System.Threading.ThreadPool.MaxThreads` | Override MaxThreads for the ThreadPool worker pool | numeric\n\n\n";
349 public static string ClrConfigSectionHeader =
350 "## Environment/Registry Configuration Knobs\n";
352 public static string ClrConfigSectionInfo =
353 "This table was machine-generated using `clr-configuration-knobs.csx` script from repository commit [GIT_SHORT_HASH](https://github.com/dotnet/coreclr/commit/GIT_LONG_HASH) on DATE_CREATED. It might be out of date. To generate latest documentation run `{dotnet} csi clr-configuration-knobs.csx` from this file directory.\n";
355 public static string ClrConfigSectionUsage =
356 "When using these configurations from environment variables, the variables need to have the `COMPlus_` prefix in their names. e.g. To set DumpJittedMethods to 1, add the environment variable `COMPlus_DumpJittedMethods=1`.\n\nSee also [Setting configuration variables](../building/viewing-jit-dumps.md#setting-configuration-variables) for more information.\n";
358 public static string ClrConfigTableHeader =
359 "\nName | Description | Type | Class | Default Value | Flags \n" +
360 "-----|-------------|------|-------|---------------|-------\n";
362 public static string PalConfigurationKnobs =
363 "## PAL Configuration Knobs\n" +
364 "All the names below need to be prefixed by `COMPlus_`.\n\n" +
365 "Name | Description | Type | Default Value\n" +
366 "-----|-------------|------|---------------\n" +
367 "`DefaultStackSize` | Overrides the default stack size for secondary threads | `STRING` | `0`\n" +
368 "`DbgEnableMiniDump` | If set to 1, enables this core dump generation. The default is NOT to generate a dump | `DWORD` | `0`\n" +
369 "`DbgMiniDumpName` | If set, use as the template to create the dump path and file name. The pid can be placed in the name with %d. | `STRING` | `_/tmp/coredump.%d_`\n" +
370 "`DbgMiniDumpType` | If set to 1 generates _MiniDumpNormal_, 2 _MiniDumpWithPrivateReadWriteMemory_, 3 _MiniDumpFilterTriage_, 4 _MiniDumpWithFullMemory_ | `DWORD` | `1`\n" +
371 "`CreateDumpDiagnostics` | If set to 1, enables the _createdump_ utilities diagnostic messages (TRACE macro) | `DWORD` | `0`\n";
374 /// Writes documentation file "clr-configuration-knobs.md"
376 /// <param name="knobs"></param>
377 /// <param name="filePath"></param>
378 /// <returns></returns>
379 public static string WriteFile(SortedDictionary<string, SortedDictionary<string, Knob>> knobs, string filePath = "clr-configuration-knobs.md")
382 var hashLong = GetRepoHeadHash();
383 var date = DateTime.UtcNow.ToShortDateString();
385 using (StreamWriter writer = new StreamWriter(filePath, false, new UTF8Encoding(false)))
387 writer.NewLine = "\n";
389 writer.WriteLine(IntroSection);
391 writer.WriteLine(HostConfigurationKnobsPart1);
393 writer.WriteLine(HostConfigurationKnobsPart2);
395 writer.WriteLine(ClrConfigSectionHeader);
397 ClrConfigSectionInfo = ClrConfigSectionInfo.Replace("GIT_LONG_HASH", hashLong);
398 ClrConfigSectionInfo = ClrConfigSectionInfo.Replace("GIT_SHORT_HASH", hashLong.Substring(0, 7));
399 ClrConfigSectionInfo = ClrConfigSectionInfo.Replace("DATE_CREATED", date);
401 writer.WriteLine(ClrConfigSectionInfo);
403 writer.WriteLine(ClrConfigSectionUsage);
405 writer.WriteLine("#### Tables");
407 foreach(string category in knobs.Keys)
409 writer.WriteLine($"{index++}. [{category} Configuration Knobs](#{EscapeMdId(category)}-configuration-knobs)");
414 foreach (string category in knobs.Keys)
416 writer.WriteLine($"#### {category} Configuration Knobs");
417 writer.Write(ClrConfigTableHeader);
419 var catKnobs = knobs[category];
421 foreach (string key in catKnobs.Keys)
423 var knob = catKnobs[key];
424 writer.Write($"`{knob.Name}` | {knob.Description} | `{knob.Type}` | ");
425 writer.Write(knob.Class.Length > 0 ? $"`{knob.Class}` | " : " | ");
426 writer.Write(knob.DefaultValue.Length > 0 ? $"`{knob.DefaultValue}` | " : " | ");
427 writer.WriteLine($"{ knob.Flags}");
436 writer.WriteLine(PalConfigurationKnobs);
438 return $"Success: {count} parsed configuration knobs, documentation file {filePath} has been updated: commit {hashLong.Substring(0, 7)} date {date}.";
441 private static string EscapeMdId(string value)
443 var buffer = value.ToLower().ToCharArray();
444 StringBuilder sb = new StringBuilder(value.Length);
446 for (int i = 0; i < buffer.Length; i++)
468 sb.Append(buffer[i]);
473 return sb.ToString();
476 public static string GetRepoHeadHash()
478 var startInfo = new ProcessStartInfo
480 FileName = "cmd.exe",
481 Arguments = "/c \"git rev-parse HEAD\"",
482 CreateNoWindow = true,
483 RedirectStandardOutput = true,
484 RedirectStandardError = true,
485 UseShellExecute = false
488 var process = Process.Start(startInfo);
490 if (process.WaitForExit(20000))
492 string result = process.StandardOutput.ReadToEnd();
493 string error = process.StandardError.ReadToEnd();
494 if (process.ExitCode == 0)
496 return result.Trim();
500 throw new InvalidOperationException(error);
505 throw new TimeoutException("\"git rev-parse HEAD\" command timed out");
511 var knobsDictionary = new SortedDictionary<string, SortedDictionary<string, Knob>>(StringComparer.Create(CultureInfo.InvariantCulture, false));
513 WriteLine($"Processing header file {clrConfig}");
514 ParseConfigFile(clrConfig, true, knobsDictionary);
516 WriteLine($"Processing header file {jitConfig}");
517 ParseConfigFile(jitConfig, false, knobsDictionary);
519 WriteLine(ConfigKnobsDoc.WriteFile(knobsDictionary));