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

Add GC heap size and count in Runtime metrics #412

Merged
merged 20 commits into from
Jun 11, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
77749e7
Add `gc.heapsize` and change `gc.count` to multi-dimensional metrics
xiang17 Jun 8, 2022
0caf3d2
Update CHANGELOG.md
xiang17 Jun 8, 2022
cae39a2
Merge branch 'main' into xiang17/addGCCounter
xiang17 Jun 8, 2022
210708d
Reword CHANGELOG.md message
xiang17 Jun 8, 2022
463a55a
Set automatic name if the number of heap goes beyond 5
xiang17 Jun 8, 2022
0dfadc1
Merge branch 'xiang17/addGCCounter' of https://github.com/xiang17/ope…
xiang17 Jun 8, 2022
41325bf
Merge branch 'main' into xiang17/addGCCounter
xiang17 Jun 9, 2022
cb74640
Allocate measurement arrays during initialization as static fields to…
xiang17 Jun 9, 2022
b767996
Merge branch 'xiang17/addGCCounter' of https://github.com/xiang17/ope…
xiang17 Jun 9, 2022
72ee40f
Update unit tests according for the value type change for active.time…
xiang17 Jun 9, 2022
ac77d17
Sharing the variable is not thread safe in case multiple exporter acc…
xiang17 Jun 10, 2022
e1ae0dd
Reword
xiang17 Jun 10, 2022
4cb0bcd
Reword
xiang17 Jun 10, 2022
f471e45
Limit max number of generations shown
xiang17 Jun 10, 2022
eef80a1
Use `s` as unit and `double` as value type for CPU time
xiang17 Jun 10, 2022
0d894a1
Update unit tests for CPU unit and value type change
xiang17 Jun 10, 2022
0f23495
Merge branch 'main' into xiang17/addGCCounter
xiang17 Jun 10, 2022
d38fe7a
Rename HeapName to GenName to keep the names consistent
xiang17 Jun 10, 2022
7eb56f7
Merge branch 'main' into xiang17/addGCCounter
xiang17 Jun 10, 2022
bf5f62e
Put `.` at the end of the description text
xiang17 Jun 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* Updated OTel SDK package version to 1.3.0
([#411](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/411))
* Add GC heap size and refactor GC count as multi-dimensional metrics in Runtime
metrics ([#412](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/412))

## 0.1.0-alpha.1

Expand Down
43 changes: 33 additions & 10 deletions src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ internal class RuntimeMetrics : IDisposable
internal static readonly AssemblyName AssemblyName = typeof(RuntimeMetrics).Assembly.GetName();
internal static readonly string InstrumentationName = AssemblyName.Name;
internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString();
private static readonly string[] HeapNames = new string[] { "gen0", "gen1", "gen2", "loh", "poh" };
private static readonly int NumberOfGenerations = 3;
private static string metricPrefix = "process.runtime.dotnet.";
private readonly Meter meter;

Expand All @@ -47,16 +49,16 @@ public RuntimeMetrics(RuntimeMetricsOptions options)
if (options.IsGcEnabled)
{
this.meter.CreateObservableGauge($"{metricPrefix}gc.heap", () => GC.GetTotalMemory(false), "By", "GC Heap Size");
this.meter.CreateObservableGauge($"{metricPrefix}gen_0-gc.count", () => GC.CollectionCount(0), description: "Gen 0 GC Count");
this.meter.CreateObservableGauge($"{metricPrefix}gen_1-gc.count", () => GC.CollectionCount(1), description: "Gen 1 GC Count");
this.meter.CreateObservableGauge($"{metricPrefix}gen_2-gc.count", () => GC.CollectionCount(2), description: "Gen 2 GC Count");
this.meter.CreateObservableGauge($"{metricPrefix}gc.count", () => GetGarbageCollectionCounts(), description: "GC Count for all generations");

#if NETCOREAPP3_1_OR_GREATER
this.meter.CreateObservableCounter($"{metricPrefix}alloc.rate", () => GC.GetTotalAllocatedBytes(), "By", "Allocation Rate");
this.meter.CreateObservableCounter($"{metricPrefix}gc.fragmentation", GetFragmentation, description: "GC Fragmentation");
#endif

#if NET6_0_OR_GREATER
this.meter.CreateObservableCounter($"{metricPrefix}gc.committed", () => (double)(GC.GetGCMemoryInfo().TotalCommittedBytes / 1_000_000), "Mi", description: "GC Committed Bytes");
this.meter.CreateObservableGauge($"{metricPrefix}gc.heapsize", () => GetGarbageCollectionHeapSizes(), "By", "Heap size for all generations");
xiang17 marked this conversation as resolved.
Show resolved Hide resolved
#endif
}

Expand All @@ -82,7 +84,7 @@ public RuntimeMetrics(RuntimeMetricsOptions options)

if (options.IsProcessEnabled)
{
this.meter.CreateObservableCounter("process.cpu.time", this.GetProcessorTimes, "s", "Processor time of this process");
this.meter.CreateObservableCounter("process.cpu.time", GetProcessorTimes, "s", "Processor time of this process");

// Not yet official: https://github.com/open-telemetry/opentelemetry-specification/pull/2392
this.meter.CreateObservableGauge("process.cpu.count", () => Environment.ProcessorCount, description: "The number of available logical CPUs");
Expand All @@ -102,6 +104,14 @@ public void Dispose()
this.meter?.Dispose();
}

private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts()
{
for (int i = 0; i < NumberOfGenerations; ++i)
{
yield return new(GC.CollectionCount(i), new KeyValuePair<string, object>("gen", HeapNames[i]));
xiang17 marked this conversation as resolved.
Show resolved Hide resolved
}
}

#if NETCOREAPP3_1_OR_GREATER
private static double GetFragmentation()
{
Expand All @@ -110,14 +120,27 @@ private static double GetFragmentation()
}
#endif

private IEnumerable<Measurement<double>> GetProcessorTimes()
#if NET6_0_OR_GREATER
private static IEnumerable<Measurement<long>> GetGarbageCollectionHeapSizes()
{
var process = Process.GetCurrentProcess();
return new[]
var generationInfo = GC.GetGCMemoryInfo().GenerationInfo;
xiang17 marked this conversation as resolved.
Show resolved Hide resolved

Measurement<long>[] measurements = new Measurement<long>[generationInfo.Length];
Copy link
Contributor

@utpilla utpilla Jun 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause allocation. We could either use a pre allocated array/list or make use of the yield return semantic.

Copy link
Contributor Author

@xiang17 xiang17 Jun 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This metrics is an Observable and gets called once every 15~20 seconds. So it's not worth doing too much optimization for now. Most importantly, if there are multiple readers reading it at almost the same time, they could have incorrect values from reading pre allocated shared array.

I've tried to use yield return and it worked for two other methods, however, for this one I got an error. See discussion thread at #412 (comment)

The compile will fail with GetGarbageCollectionHeapSizes because it needs a Span<T>: "CS4013 Instance of type 'ReadOnlySpan' cannot be used inside a nested function, query expression, iterator block or async method".

int maxSupportedLength = Math.Min(generationInfo.Length, HeapNames.Length);
for (int i = 0; i < maxSupportedLength; ++i)
{
new Measurement<double>(process.UserProcessorTime.TotalSeconds, new KeyValuePair<string, object>("state", "user")),
new Measurement<double>(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair<string, object>("state", "system")),
};
measurements[i] = new(generationInfo[i].SizeAfterBytes, new KeyValuePair<string, object>("gen", HeapNames[i]));
}

return measurements;
}
#endif

private static IEnumerable<Measurement<double>> GetProcessorTimes()
{
var process = Process.GetCurrentProcess();
yield return new(process.UserProcessorTime.TotalSeconds, new KeyValuePair<string, object>("state", "user"));
yield return new(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair<string, object>("state", "system"));
}
}
}