Skip to content
This repository was archived by the owner on Dec 18, 2023. It is now read-only.

Commit 383f8d1

Browse files
Simon ZeltserSergeyKanzhelev
authored andcommitted
Introducing a skeleton of metrics support in Stackdriver Exporter (#55)
* Introducing Stackdriver Exporter for Opencensus C# library - Current implementation can only store string values - Added the exporter and trace handler only - The exporter relies on newest Trace API from Stackdriver. * Updating translation from ISpan to Stackdriver's Span to cover more fields * Fixing the issue that prevented Stackdriver API call to succeed: now construction of Span resource is taken care of by SpanName class that is part of Stackdriver Trace V2 API. * - Added support for capturing all types of trace spans (long/bool/string) - Fixed csproj, so it produces both .NET Core and .NET versions. It also means signing the assembly using the same mechanism as other assemblies in the solution * - Added support for storing links - Minor fixes to proto<->opencensus translation methods * Fixing merge issue * Added command line support for the Samples project. This will make it easier to script testing multiple exporters as well as prevent commenting and uncommenting different test procedures for different exporters. Finally, it will making test code cleaner for showing samples embedded in documentation website. In order to test the exporter, you can either execute it from the command line: samples.dll prometheus or to debug using Visual Studio: Debug => Sample Properties => Debug. Now create a profile for your testing execution and fill application arguments if needed. Now choose your profile in running configuration of Visual Studio. Running settings are local to the machine and are ignored in PRs. * Adding sceleton of metrics support in Stackdriver Exporter. Currently we can't detect all types of monitoring resources but we can ship the batches of metrics with time series to Stackdriver. Thorough testing TBD * Fixing a few bugs in Stackdriver metrics exporter - Small refactoring - Assinging default values for time intervals * - Fixing a few bugs around metric creation and labels - Refactoring MetricsConversions - Fixing stackdriver stats configuration - population of default resource and projects
1 parent da6c3d5 commit 383f8d1

File tree

11 files changed

+823
-12
lines changed

11 files changed

+823
-12
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// <copyright file="ApplicationInsightsExporter.cs" company="OpenCensus Authors">
2+
// Copyright 2018, OpenCensus Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of theLicense at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace OpenCensus.Exporter.Stackdriver.Implementation
18+
{
19+
using System;
20+
21+
internal class Constants
22+
{
23+
public const string LABEL_DESCRIPTION = "OpenCensus TagKey";
24+
public const string OPENCENSUS_TASK = "opencensus_task";
25+
public const string OPENCENSUS_TASK_DESCRIPTION = "Opencensus task identifier";
26+
public const string GCP_GKE_CONTAINER = "k8s_container";
27+
public const string GCP_GCE_INSTANCE = "gce_instance";
28+
public const string AWS_EC2_INSTANCE = "aws_ec2_instance";
29+
public const string GLOBAL = "global";
30+
public const string PROJECT_ID_LABEL_KEY = "project_id";
31+
public static readonly string OPENCENSUS_TASK_VALUE_DEFAULT = generateDefaultTaskValue();
32+
33+
private static string generateDefaultTaskValue()
34+
{
35+
// Something like '<pid>@<hostname>'
36+
return $"dotnet-{System.Diagnostics.Process.GetCurrentProcess().Id}@{Environment.MachineName}";
37+
}
38+
}
39+
}
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
// <copyright file="ApplicationInsightsExporter.cs" company="OpenCensus Authors">
2+
// Copyright 2018, OpenCensus Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of theLicense at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace OpenCensus.Exporter.Stackdriver.Implementation
18+
{
19+
using Google.Api;
20+
using Google.Cloud.Monitoring.V3;
21+
using OpenCensus.Exporter.Stackdriver.Utils;
22+
using OpenCensus.Stats;
23+
using OpenCensus.Stats.Aggregations;
24+
using OpenCensus.Tags;
25+
using System;
26+
using System.Collections.Generic;
27+
using static Google.Api.Distribution.Types;
28+
using static Google.Api.MetricDescriptor.Types;
29+
30+
/// <summary>
31+
/// Conversion methods from Opencensus Stats API to Stackdriver Metrics API
32+
/// </summary>
33+
internal static class MetricsConversions
34+
{
35+
/// <summary>
36+
/// Converts between OpenCensus aggregation and Stackdriver metric kind
37+
/// </summary>
38+
/// <param name="aggregation">Stats Aggregation</param>
39+
/// <returns>Stackdriver Metric Kind</returns>
40+
public static MetricKind ToMetricKind(
41+
this IAggregation aggregation)
42+
{
43+
if (aggregation is ILastValue)
44+
{
45+
return MetricKind.Gauge;
46+
}
47+
48+
return aggregation.Match(
49+
v => MetricKind.Cumulative, // Sum
50+
v => MetricKind.Cumulative, // Count
51+
v => MetricKind.Cumulative, // Mean
52+
v => MetricKind.Cumulative, // Distribution
53+
v => MetricKind.Gauge, // Last value
54+
v => MetricKind.Unspecified); // Default
55+
}
56+
57+
public static MetricDescriptor.Types.ValueType ToValueType(
58+
this IAggregationData aggregationData)
59+
{
60+
return aggregationData.Match(
61+
v => MetricDescriptor.Types.ValueType.Double, // Sum Double
62+
v => MetricDescriptor.Types.ValueType.Int64, // Sum Long
63+
v => MetricDescriptor.Types.ValueType.Int64, // Count
64+
v => MetricDescriptor.Types.ValueType.Unspecified, // Mean - this measure should be deprecated
65+
v => MetricDescriptor.Types.ValueType.Distribution, // Distribution
66+
v => MetricDescriptor.Types.ValueType.Double, // LastValue Double
67+
v => MetricDescriptor.Types.ValueType.Int64, // LastValue Long
68+
v => MetricDescriptor.Types.ValueType.Unspecified); // Unrecognized
69+
}
70+
71+
public static MetricDescriptor.Types.ValueType ToValueType(
72+
this IMeasure measure)
73+
{
74+
return measure.Match(
75+
v => MetricDescriptor.Types.ValueType.Double, // Double
76+
v => MetricDescriptor.Types.ValueType.Int64, // Long
77+
v => MetricDescriptor.Types.ValueType.Unspecified); // Unrecognized
78+
}
79+
80+
public static LabelDescriptor ToLabelDescriptor(this ITagKey tagKey)
81+
{
82+
var labelDescriptor = new LabelDescriptor();
83+
84+
// TODO - zeltser - looks like we don't support / and . in the label key. Need to confirm with Stackdriver team
85+
labelDescriptor.Key = MetricsUtils.GetLabelKey(tagKey.Name);
86+
labelDescriptor.Description = Constants.LABEL_DESCRIPTION;
87+
88+
// TODO - zeltser - Now we only support string tags
89+
labelDescriptor.ValueType = LabelDescriptor.Types.ValueType.String;
90+
return labelDescriptor;
91+
}
92+
93+
public static Distribution CreateDistribution(
94+
IDistributionData distributionData,
95+
IBucketBoundaries bucketBoundaries)
96+
{
97+
var bucketOptions = CreateBucketOptions(bucketBoundaries);
98+
var distribution = new Distribution
99+
{
100+
BucketOptions = bucketOptions,
101+
BucketCounts = { distributionData.BucketCounts },
102+
Count = distributionData.Count,
103+
Mean = distributionData.Mean,
104+
SumOfSquaredDeviation = distributionData.SumOfSquaredDeviations,
105+
Range = new Range { Max = distributionData.Max, Min = distributionData.Min }
106+
};
107+
108+
return distribution;
109+
}
110+
111+
// Construct a MetricDescriptor using a View.
112+
public static MetricDescriptor CreateMetricDescriptor(
113+
IView view,
114+
ProjectName project,
115+
string domain,
116+
string displayNamePrefix)
117+
{
118+
var metricDescriptor = new MetricDescriptor();
119+
string viewName = view.Name.AsString;
120+
string type = MetricsUtils.GenerateTypeName(viewName, domain);
121+
122+
metricDescriptor.Name = string.Format($"projects/{project.ProjectId}/metricDescriptors/{type}");
123+
metricDescriptor.Type = type;
124+
metricDescriptor.Description = view.Description;
125+
metricDescriptor.DisplayName = MetricsUtils.GetDisplayName(viewName, displayNamePrefix);
126+
127+
foreach (ITagKey tagKey in view.Columns)
128+
{
129+
var labelDescriptor = tagKey.ToLabelDescriptor();
130+
metricDescriptor.Labels.Add(labelDescriptor);
131+
}
132+
metricDescriptor.Labels.Add(
133+
new LabelDescriptor
134+
{
135+
Key = Constants.OPENCENSUS_TASK,
136+
Description = Constants.OPENCENSUS_TASK_DESCRIPTION,
137+
ValueType = LabelDescriptor.Types.ValueType.String,
138+
});
139+
140+
var unit = CreateUnit(view.Aggregation, view.Measure);
141+
metricDescriptor.Unit = unit;
142+
metricDescriptor.MetricKind = view.Aggregation.ToMetricKind();
143+
metricDescriptor.ValueType = view.Measure.ToValueType();
144+
145+
return metricDescriptor;
146+
}
147+
148+
public static TypedValue CreateTypedValue(
149+
IAggregation aggregation,
150+
IAggregationData aggregationData)
151+
{
152+
return aggregationData.Match(
153+
v => new TypedValue { DoubleValue = v.Sum }, // Double
154+
v => new TypedValue { Int64Value = v.Sum }, // Long
155+
v => new TypedValue { Int64Value = v.Count }, // Count
156+
v => new TypedValue { DoubleValue = v.Count }, // Mean
157+
v => new TypedValue { DistributionValue = CreateDistribution(v, ((IDistribution)aggregation).BucketBoundaries) }, //Distribution
158+
v => new TypedValue { DoubleValue = v.LastValue }, // LastValue Double
159+
v => new TypedValue { Int64Value = v.LastValue }, // LastValue Long
160+
v => new TypedValue { BoolValue = false }); // Default
161+
}
162+
163+
public static Point CreatePoint(
164+
IAggregationData aggregationData,
165+
IViewData windowData,
166+
IAggregation aggregation)
167+
{
168+
return new Point
169+
{
170+
Interval = CreateTimeInterval(windowData, aggregation),
171+
Value = CreateTypedValue(aggregation, aggregationData)
172+
};
173+
}
174+
175+
private static BucketOptions CreateBucketOptions(IBucketBoundaries bucketBoundaries)
176+
{
177+
return new BucketOptions
178+
{
179+
ExplicitBuckets = new BucketOptions.Types.Explicit
180+
{
181+
Bounds = { bucketBoundaries.Boundaries }
182+
}
183+
};
184+
}
185+
186+
private static TimeInterval CreateTimeInterval(
187+
IViewData windowData,
188+
IAggregation aggregation)
189+
{
190+
return windowData.View.Measure.Match(
191+
v =>
192+
{
193+
var interval = new TimeInterval { EndTime = windowData.End.ToTimestamp() };
194+
if (!(windowData.View.Aggregation is ILastValue))
195+
{
196+
interval.StartTime = windowData.Start.ToTimestamp();
197+
}
198+
return interval;
199+
},
200+
v =>
201+
{
202+
// TODO - zeltser - figure out why this is called (long variant)
203+
var interval = new TimeInterval { EndTime = windowData.End.ToTimestamp() };
204+
if (!(windowData.View.Aggregation is ILastValue))
205+
{
206+
interval.StartTime = windowData.Start.ToTimestamp();
207+
}
208+
return interval;
209+
},
210+
v => throw new InvalidOperationException());
211+
}
212+
213+
// Create a Metric using the TagKeys and TagValues.
214+
public static Metric CreateMetric(
215+
IView view,
216+
IList<ITagValue> tagValues,
217+
string domain)
218+
{
219+
var metric = new Metric();
220+
metric.Type = MetricsUtils.GenerateTypeName(view.Name.AsString, domain);
221+
222+
IList<ITagKey> columns = view.Columns;
223+
224+
// Populate metric labels
225+
for (int i = 0; i < tagValues.Count; i++)
226+
{
227+
ITagKey key = columns[i];
228+
ITagValue value = tagValues[i];
229+
if (value == null)
230+
{
231+
continue;
232+
}
233+
234+
string labelKey = MetricsUtils.GetLabelKey(key.Name);
235+
metric.Labels.Add(labelKey, value.AsString);
236+
}
237+
metric.Labels.Add(Constants.OPENCENSUS_TASK, Constants.OPENCENSUS_TASK_VALUE_DEFAULT);
238+
239+
return metric;
240+
}
241+
242+
/// <summary>
243+
/// Convert ViewData to a list of TimeSeries, so that ViewData can be uploaded to Stackdriver.
244+
/// </summary>
245+
/// <param name="viewData">OpenCensus View</param>
246+
/// <param name="monitoredResource">Stackdriver Resource to which the metrics belong</param>
247+
/// <param name="domain">The metrics domain (namespace)</param>
248+
/// <returns></returns>
249+
public static List<TimeSeries> CreateTimeSeriesList(
250+
IViewData viewData,
251+
MonitoredResource monitoredResource,
252+
string domain)
253+
{
254+
var timeSeriesList = new List<TimeSeries>();
255+
if (viewData == null)
256+
{
257+
return timeSeriesList;
258+
}
259+
260+
IView view = viewData.View;
261+
262+
// Each entry in AggregationMap will be converted into an independent TimeSeries object
263+
foreach (var entry in viewData.AggregationMap)
264+
{
265+
var timeSeries = new TimeSeries();
266+
267+
timeSeries.Metric = CreateMetric(view, entry.Key.Values, domain);
268+
269+
timeSeries.Resource = monitoredResource;
270+
271+
//timeSeries.MetricKind = view.Aggregation.ToMetricKind();
272+
//timeSeries.ValueType = entry.Value.ToValueType();
273+
274+
var point = CreatePoint(entry.Value, viewData, view.Aggregation);
275+
timeSeries.Points.Add(point);
276+
timeSeriesList.Add(timeSeries);
277+
}
278+
279+
return timeSeriesList;
280+
}
281+
282+
internal static string CreateUnit(IAggregation aggregation, IMeasure measure)
283+
{
284+
if (aggregation is ICount)
285+
{
286+
return "1";
287+
}
288+
289+
return measure.Unit;
290+
}
291+
}
292+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// <copyright file="ApplicationInsightsExporter.cs" company="OpenCensus Authors">
2+
// Copyright 2018, OpenCensus Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of theLicense at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace OpenCensus.Exporter.Stackdriver.Implementation
18+
{
19+
using Google.Api;
20+
21+
/// <summary>
22+
/// Utility methods for metrics
23+
/// </summary>
24+
public static class MetricsUtils
25+
{
26+
public static string GetProjectId()
27+
{
28+
var instance = Google.Api.Gax.Platform.Instance();
29+
var projectId = instance?.ProjectId;
30+
31+
return projectId;
32+
}
33+
34+
/// <summary>
35+
/// Determining the resource to which the metrics belong
36+
/// </summary>
37+
/// <returns>Stackdriver Monitored Resource</returns>
38+
public static MonitoredResource GetDefaultResource(string projectId)
39+
{
40+
var builder = new MonitoredResource();
41+
builder.Type = Constants.GLOBAL;
42+
builder.Labels.Add(Constants.PROJECT_ID_LABEL_KEY, projectId);
43+
44+
// TODO - zeltser - setting monitored resource labels for detected resource
45+
// along with all the other metadata
46+
47+
return builder;
48+
}
49+
50+
public static string GetLabelKey(string label)
51+
{
52+
return label.Replace('/', '_');
53+
}
54+
55+
public static string GenerateTypeName(string viewName, string domain)
56+
{
57+
return domain + viewName;
58+
}
59+
60+
public static string GetDisplayName(string viewName, string displayNamePrefix)
61+
{
62+
return displayNamePrefix + viewName;
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)