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

Commit 38976dd

Browse files
Simon ZeltserSergeyKanzhelev
authored andcommitted
Fixing stackdriver export interval propagation (#66)
* 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 * First working version of Stackdriver Stats Exporter. 1) Fixed bugs related to Stackdriver Metrics Descriptor creation 2) Renamed Metrics Exporter to Stats Exporter to follow Opencensus terminology 3) Cache Stackdriver Metrics Descriptors locally so it's easy which descriptor holds the time series to upload 4) Fixed test code that was producing the metrics using Opencensus and uploading it to Stackdriver 5) Minor refactoring Current limitations: 1) Supporting only string labels 2) We support only "Global" monitored resource. We need to detect dynamically where the code is running and attach corresponding labels to the monitored resource, so metrics are stored correctly 3) More Tests * Removing commented lines * Fixed Stackdriver export interval propagation Added Opencensus headers into every call into Stackdriver APIs Added consts for resources that Stackdriver Exporter should auto-detect when running on GCP * - Added test project for Stackdriver Exporter - Added tests for StackdriverStatsConfiguration * Trying to fix the tests that fail on CI as a result of wrong signing key for InternalVisibleTo * Updating Travis build definition to latest .netcore sdk and running all tests * Adding an option to set google cloud projectId in environment variable for testing purposes(Stackdriver exporter). * Temporarily commenting out lines that read from a friend assembly as it doesn't work on a build server * Added AssemblyInfo.cs into Stackdriver Exporter, so it's visible to tests on both signed and unsigned builds
1 parent e751960 commit 38976dd

15 files changed

+302
-71
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: csharp
22
mono: none
3-
dotnet: 2.1.402
3+
dotnet: 2.1.500
44
script:
55
- dotnet restore
66
- dotnet build
7-
- dotnet test ./test/OpenCensus.Tests/OpenCensus.Tests.csproj
7+
- dotnet test

OpenCensus.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.2.0", "t
5959
EndProject
6060
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Ocagent", "src\OpenCensus.Exporter.Ocagent\OpenCensus.Exporter.Ocagent.csproj", "{56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}"
6161
EndProject
62+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Stackdriver.Tests", "test\OpenCensus.Exporter.Stackdriver.Tests\OpenCensus.Exporter.Stackdriver.Tests.csproj", "{6875032B-DFDC-4CDE-A283-37CA7F99926A}"
63+
EndProject
6264
Global
6365
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6466
Debug|Any CPU = Debug|Any CPU
@@ -121,11 +123,16 @@ Global
121123
{56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
122124
{56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
123125
{56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Release|Any CPU.Build.0 = Release|Any CPU
126+
{6875032B-DFDC-4CDE-A283-37CA7F99926A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
127+
{6875032B-DFDC-4CDE-A283-37CA7F99926A}.Debug|Any CPU.Build.0 = Debug|Any CPU
128+
{6875032B-DFDC-4CDE-A283-37CA7F99926A}.Release|Any CPU.ActiveCfg = Release|Any CPU
129+
{6875032B-DFDC-4CDE-A283-37CA7F99926A}.Release|Any CPU.Build.0 = Release|Any CPU
124130
EndGlobalSection
125131
GlobalSection(SolutionProperties) = preSolution
126132
HideSolutionNode = FALSE
127133
EndGlobalSection
128134
GlobalSection(NestedProjects) = preSolution
135+
{7CB2F02E-03FA-4FFF-89A5-C51F107623FD} = {61188153-47FB-4567-AC9B-79B2435853EB}
129136
{F2F81E76-6A0E-466B-B673-EBBF1A9ED075} = {77C7929A-2EED-4AA6-8705-B5C443C8AA0F}
130137
EndGlobalSection
131138
GlobalSection(ExtensibilityGlobals) = postSolution
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// <copyright file="AssemblyInfo.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+
using System.Runtime.CompilerServices;
18+
19+
[assembly: InternalsVisibleTo("OpenCensus.Exporter.Stackdriver.Tests" + AssemblyInfo.PublicKey)]
20+
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)]
21+
22+
#if SIGNED
23+
internal static class AssemblyInfo
24+
{
25+
public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898";
26+
public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7";
27+
}
28+
#else
29+
internal static class AssemblyInfo
30+
{
31+
public const string PublicKey = "";
32+
public const string MoqPublicKey = "";
33+
}
34+
#endif

src/OpenCensus.Exporter.Stackdriver/Implementation/Constants.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
namespace OpenCensus.Exporter.Stackdriver.Implementation
1818
{
1919
using System;
20-
using System.Collections.Generic;
21-
using System.Collections.Specialized;
2220

2321
internal class Constants
2422
{
@@ -34,6 +32,17 @@ internal class Constants
3432
public const string PROJECT_ID_LABEL_KEY = "project_id";
3533
public static readonly string OPENCENSUS_TASK_VALUE_DEFAULT = generateDefaultTaskValue();
3634

35+
public const string GCP_GCE_INSTANCE_TYPE = "cloud.google.com/gce/instance";
36+
public const string GCP_INSTANCE_ID_KEY = "cloud.google.com/gce/instance_id";
37+
public const string GCP_ACCOUNT_ID_KEY = "cloud.google.com/gce/project_id";
38+
public const string GCP_ZONE_KEY = "cloud.google.com/gce/zone";
39+
40+
public const string K8S_CONTAINER_TYPE = "k8s.io/container";
41+
public const string K8S_CLUSTER_NAME_KEY = "k8s.io/cluster/name";
42+
public const string K8S_CONTAINER_NAME_KEY = "k8s.io/container/name";
43+
public const string K8S_NAMESPACE_NAME_KEY = "k8s.io/namespace/name";
44+
public const string K8S_POD_NAME_KEY = "k8s.io/pod/name";
45+
3746
private static string generateDefaultTaskValue()
3847
{
3948
// Something like '<pid>@<hostname>'

src/OpenCensus.Exporter.Stackdriver/Implementation/GoogleCloudResourceUtils.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,44 @@
1717
namespace OpenCensus.Exporter.Stackdriver.Implementation
1818
{
1919
using Google.Api;
20+
using System;
2021
using System.Collections.Generic;
22+
using System.IO;
2123

2224
/// <summary>
2325
/// Utility methods for working with Google Cloud Resources
2426
/// </summary>
2527
public static class GoogleCloudResourceUtils
2628
{
27-
private static Dictionary<string, string> gcpResourceLabelMappings = new Dictionary<string, string>()
28-
{
29-
{ "project_id", Constants.PROJECT_ID_LABEL_KEY },
30-
{ "instance_id", Constants.GCP_GCE_INSTANCE },
31-
{ "zone", null }
32-
};
33-
3429
/// <summary>
3530
/// Detects Google Cloud ProjectId based on the environment on which the code runs.
36-
/// Supports GCE/GKE/GAE
31+
/// Supports GCE/GKE/GAE and projectId tied to service account
3732
/// In case the code runs in a different environment,
3833
/// the method returns null
3934
/// </summary>
4035
/// <returns>Google Cloud Project ID</returns>
4136
public static string GetProjectId()
4237
{
38+
// Try to detect projectId from the environment where the code is running
4339
var instance = Google.Api.Gax.Platform.Instance();
4440
var projectId = instance?.ProjectId;
41+
if (!string.IsNullOrEmpty(projectId))
42+
{
43+
return projectId;
44+
}
45+
46+
// Try to detect projectId from service account credential if it exists
47+
string serviceAccountFilePath = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS");
48+
if (!string.IsNullOrEmpty(serviceAccountFilePath) && File.Exists(serviceAccountFilePath))
49+
{
50+
using (var stream = new FileStream(serviceAccountFilePath, FileMode.Open))
51+
{
52+
var credential = Google.Apis.Auth.OAuth2.ServiceAccountCredential.FromServiceAccountData(stream);
53+
return credential.ProjectId;
54+
}
55+
}
4556

57+
projectId = Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID");
4658
return projectId;
4759
}
4860

src/OpenCensus.Exporter.Stackdriver/Implementation/MetricsConversions.cs

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ namespace OpenCensus.Exporter.Stackdriver.Implementation
2323
using OpenCensus.Exporter.Stackdriver.Utils;
2424
using OpenCensus.Stats;
2525
using OpenCensus.Stats.Aggregations;
26+
using OpenCensus.Stats.Measures;
2627
using OpenCensus.Tags;
27-
using System;
2828
using System.Collections.Generic;
2929
using static Google.Api.Distribution.Types;
3030
using static Google.Api.MetricDescriptor.Types;
@@ -52,17 +52,30 @@ public static MetricKind ToMetricKind(
5252
}
5353

5454
/// <summary>
55-
/// Converts from Opencensus Measure to Stackdriver ValueType
55+
/// Converts from Opencensus Measure+Aggregation to Stackdriver's ValueType
5656
/// </summary>
57-
/// <param name="measure"></param>
57+
/// <param name="measure">Opencensus Measure definition</param>
58+
/// <param name="aggregation">Opencensus Aggregation definition</param>
5859
/// <returns></returns>
59-
public static MetricDescriptor.Types.ValueType ToValueType(
60-
this IMeasure measure)
60+
public static ValueType ToValueType(
61+
this IMeasure measure, IAggregation aggregation)
6162
{
62-
return measure.Match(
63-
v => MetricDescriptor.Types.ValueType.Double, // Double
64-
v => MetricDescriptor.Types.ValueType.Int64, // Long
65-
v => MetricDescriptor.Types.ValueType.Unspecified); // Unrecognized
63+
MetricKind metricKind = aggregation.ToMetricKind();
64+
if (aggregation is IDistribution && (metricKind == MetricKind.Cumulative || metricKind == MetricKind.Gauge))
65+
return ValueType.Distribution;
66+
67+
if (measure is IMeasureDouble && (metricKind == MetricKind.Cumulative || metricKind == MetricKind.Gauge))
68+
{
69+
return ValueType.Double;
70+
}
71+
72+
if (measure is IMeasureLong && (metricKind == MetricKind.Cumulative || metricKind == MetricKind.Gauge))
73+
{
74+
return ValueType.Int64;
75+
}
76+
77+
// TODO - zeltser - we currently don't support money and string as Opencensus doesn't support them yet
78+
return ValueType.Unspecified;
6679
}
6780

6881
public static LabelDescriptor ToLabelDescriptor(this ITagKey tagKey)
@@ -135,8 +148,8 @@ public static MetricDescriptor CreateMetricDescriptor(
135148
var unit = GetUnit(view.Aggregation, view.Measure);
136149
metricDescriptor.Unit = unit;
137150
metricDescriptor.MetricKind = view.Aggregation.ToMetricKind();
138-
metricDescriptor.ValueType = view.Measure.ToValueType();
139-
151+
metricDescriptor.ValueType = view.Measure.ToValueType(view.Aggregation);
152+
140153
return metricDescriptor;
141154
}
142155

@@ -236,6 +249,7 @@ public static Metric GetMetric(
236249
/// Convert ViewData to a list of TimeSeries, so that ViewData can be uploaded to Stackdriver.
237250
/// </summary>
238251
/// <param name="viewData">OpenCensus View</param>
252+
/// <param name="metricDescriptor">Stackdriver Metric Descriptor</param>
239253
/// <param name="monitoredResource">Stackdriver Resource to which the metrics belong</param>
240254
/// <param name="domain">The metrics domain (namespace)</param>
241255
/// <returns></returns>
@@ -262,7 +276,7 @@ public static List<TimeSeries> CreateTimeSeriesList(
262276
IAggregationData points = entry.Value;
263277

264278
timeSeries.Resource = monitoredResource;
265-
timeSeries.ValueType = view.Measure.ToValueType();
279+
timeSeries.ValueType = view.Measure.ToValueType(view.Aggregation);
266280
timeSeries.MetricKind = view.Aggregation.ToMetricKind();
267281

268282
timeSeries.Metric = GetMetric(view, labels, metricDescriptor, domain);
@@ -288,25 +302,6 @@ private static Point ExtractPointInInterval(
288302
Value = CreateTypedValue(aggregation, points),
289303
Interval = CreateTimeInterval(startTime, endTime)
290304
};
291-
/*
292-
var ret = points.Match(
293-
v => new Point { Value = new TypedValue { DoubleValue = v.Sum }, Interval = CreateTimeInterval(startTime, endTime) }, // ISumDataDouble
294-
v => new Point { Value = new TypedValue { Int64Value = v.Sum }, Interval = CreateTimeInterval(startTime, endTime) }, // ISumDataLong
295-
v => new Point { Value = new TypedValue { Int64Value = v.Count }, Interval = CreateTimeInterval(startTime, endTime) }, // ICountData
296-
v => new Point { Value = new TypedValue { DoubleValue = v.Mean }, Interval = CreateTimeInterval(startTime, endTime) }, // IMeanData
297-
v => new Point {
298-
Value = new TypedValue
299-
{
300-
DistributionValue = CreateDistribution(v, ((IDistribution)aggregation).BucketBoundaries),
301-
},
302-
Interval = CreateTimeInterval(startTime, endTime)
303-
}, // IDistributionData
304-
v => new Point { Value = new TypedValue { DoubleValue = v.LastValue }, Interval = CreateTimeInterval(startTime, endTime) }, // ILastValueDataDouble
305-
v => new Point { Value = new TypedValue { Int64Value = v.LastValue }, Interval = CreateTimeInterval(startTime, endTime) }, // ILastValueDataLong
306-
v => throw new InvalidOperationException());
307-
308-
return ret;
309-
*/
310305
}
311306

312307
private static TimeInterval CreateTimeInterval(ITimestamp start, ITimestamp end)

src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsConfiguration.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,20 @@ public class StackdriverStatsConfiguration
5252
/// <summary>
5353
/// Default Stats Configuration for Stackdriver
5454
/// </summary>
55-
public static StackdriverStatsConfiguration Default { get; } = new StackdriverStatsConfiguration
55+
public static StackdriverStatsConfiguration Default
5656
{
57-
ExportInterval = DEFAULT_INTERVAL,
58-
ProjectId = GoogleCloudResourceUtils.GetProjectId(),
59-
MetricNamePrefix = string.Empty,
60-
};
57+
get
58+
{
59+
var defaultConfig = new StackdriverStatsConfiguration
60+
{
61+
ExportInterval = DEFAULT_INTERVAL,
62+
ProjectId = GoogleCloudResourceUtils.GetProjectId(),
63+
MetricNamePrefix = string.Empty,
64+
};
65+
66+
defaultConfig.MonitoredResource = GoogleCloudResourceUtils.GetDefaultResource(defaultConfig.ProjectId);
67+
return defaultConfig;
68+
}
69+
}
6170
}
6271
}

src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsExporter.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace OpenCensus.Exporter.Stackdriver.Implementation
1818
{
1919
using Google.Api;
2020
using Google.Api.Gax;
21+
using Google.Api.Gax.Grpc;
2122
using Google.Cloud.Monitoring.V3;
2223
using Grpc.Core;
2324
using OpenCensus.Exporter.Stackdriver.Utils;
@@ -44,15 +45,19 @@ internal class StackdriverStatsExporter
4445
private const string CUSTOM_METRIC_DOMAIN = "custom.googleapis.com/";
4546
private const string CUSTOM_OPENCENSUS_DOMAIN = CUSTOM_METRIC_DOMAIN + "opencensus/";
4647

48+
// TODO - zeltser - figure out how to extract OpenCensus package version
49+
private const string USER_AGENT = "opencensus-csharp [0.0]";
50+
private const string USER_AGENT_KEY = "user-agent";
51+
4752
private readonly string domain;
4853
private readonly string displayNamePrefix;
4954

5055
private bool isStarted;
5156

5257
/// <summary>
53-
/// Interval between two subsequent metrics collection operations
58+
/// Interval between two subsequent stats collection operations
5459
/// </summary>
55-
private readonly TimeSpan collectionInterval = TimeSpan.FromSeconds(15);
60+
private TimeSpan collectionInterval = TimeSpan.FromMinutes(1);
5661

5762
/// <summary>
5863
/// Interval within which the cancellation should be honored
@@ -69,11 +74,16 @@ public StackdriverStatsExporter(
6974
GaxPreconditions.CheckNotNull(configuration, "configuration");
7075
GaxPreconditions.CheckNotNullOrEmpty(configuration.ProjectId, "configuration.ProjectId");
7176
GaxPreconditions.CheckNotNull(configuration.MonitoredResource, "configuration.MonitoredResource");
77+
GaxPreconditions.CheckArgument(
78+
configuration.ExportInterval != TimeSpan.Zero,
79+
paramName: "configuration.ExportInterval",
80+
message: "Export interval can't be zero. Typically it's 1 minute");
7281

7382
this.viewManager = viewManager;
7483
monitoredResource = configuration.MonitoredResource;
84+
collectionInterval = configuration.ExportInterval;
7585
project = new ProjectName(configuration.ProjectId);
76-
metricServiceClient = MetricServiceClient.Create();
86+
7787
domain = GetDomain(configuration.MetricNamePrefix);
7888
displayNamePrefix = GetDisplayNamePrefix(configuration.MetricNamePrefix);
7989
}
@@ -85,6 +95,7 @@ public void Start()
8595
if (!isStarted)
8696
{
8797
tokenSource = new CancellationTokenSource();
98+
metricServiceClient = CreateMetricServiceClient(tokenSource);
8899

89100
Task.Factory.StartNew(DoWork, tokenSource.Token);
90101

@@ -248,6 +259,25 @@ private void Export()
248259
}
249260
}
250261

262+
private static MetricServiceClient CreateMetricServiceClient(CancellationTokenSource tokenSource)
263+
{
264+
// Make sure to add Opencensus header to every outgoing call to Stackdriver APIs
265+
Action<Metadata> addOpencensusHeader = m => m.Add(USER_AGENT_KEY, USER_AGENT);
266+
var callSettings = new CallSettings(
267+
cancellationToken: tokenSource.Token,
268+
credentials: null,
269+
timing: null,
270+
headerMutation: addOpencensusHeader,
271+
writeOptions: WriteOptions.Default,
272+
propagationToken: null);
273+
var metricServiceSettings = new MetricServiceSettings()
274+
{
275+
CallSettings = callSettings
276+
};
277+
278+
return MetricServiceClient.Create(settings: metricServiceSettings);
279+
}
280+
251281
private static string GetDomain(string metricNamePrefix)
252282
{
253283
string domain;

src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverTraceExporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ static class SpanExtensions
2727
{
2828
/// <summary>
2929
/// Translating <see cref="ISpanData"/> to Stackdriver's Span
30-
/// According to <see cref="https://cloud.google.com/trace/docs/reference/v2/rpc/google.devtools.cloudtrace.v2"/> specifications
30+
/// According to <see href="https://cloud.google.com/trace/docs/reference/v2/rpc/google.devtools.cloudtrace.v2"/> specifications
3131
/// </summary>
3232
/// <param name="spanData">Span in OpenCensus format</param>
3333
/// <param name="projectId">Google Cloud Platform Project Id</param>

0 commit comments

Comments
 (0)