diff --git a/ChangeLog b/ChangeLog index c7f839fde..6075f58f9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,27 @@ +6.1.0 +===== + +- Fixed a few code examples + - SetupRemarketing.cs + - GetAccountHierarchy.cs + - AddShoppingProductListingGroupTree.cs + - UpdateSitelink.cs + - GetAdGroupBidModifiers.cs +- Fixed the build badge URL in the README.md file. +- Added region tags in all code examples. This makes it easier to embed the code examples + in the developer guides. +- Fixed a broken urls to guide references in some code examples. +- Added new code examples for FeedItemSets. + - Feeds\CreateFeedItemSet.cs + - Feeds\GetFeedItemsOfFeedItemSet.cs + - Feeds\LinkFeedItemSet.cs +- Added a profiler for the client library to capture client time breakdown by serialization, deserialization and over-the-wire times. +- Bumped the protobuf and grpc dependencies. This is required due to https://github.com/protocolbuffers/protobuf/issues/8027. The full version bump lists are: + - Google.Protobuf: 3.14.0 + - Grpc.Core: 2.34.0 + - Microsoft.Extensions.Configuration: 5.0.0. + - System.Configuration.ConfigurationManager: 5.0.0 + 6.0.0 ===== - Added support for v6 of Google Ads API. See diff --git a/examples/AdvancedOperations/GetAdGroupBidModifiers.cs b/examples/AdvancedOperations/GetAdGroupBidModifiers.cs index b7f2b87f9..c123b2d5c 100644 --- a/examples/AdvancedOperations/GetAdGroupBidModifiers.cs +++ b/examples/AdvancedOperations/GetAdGroupBidModifiers.cs @@ -18,6 +18,7 @@ using Google.Ads.GoogleAds.V6.Services; using Google.Api.Gax; using System; +using static Google.Ads.GoogleAds.V6.Resources.AdGroupBidModifier; namespace Google.Ads.GoogleAds.Examples.V6 { @@ -67,10 +68,20 @@ public void Run(GoogleAdsClient client, long customerId, long? adGroupId) GoogleAdsServiceClient googleAdsService = client.GetService(Services.V6.GoogleAdsService); - string searchQuery = - "SELECT ad_group.id, ad_group_bid_modifier.criterion_id, " - + "ad_group_bid_modifier.bid_modifier, ad_group_bid_modifier.device.type, " - + "campaign.id FROM ad_group_bid_modifier"; + string searchQuery = @" + SELECT + ad_group.id, ad_group_bid_modifier.criterion_id, campaign.id, + ad_group_bid_modifier.bid_modifier, + ad_group_bid_modifier.device.type, + ad_group_bid_modifier.hotel_date_selection_type.type, + ad_group_bid_modifier.hotel_advance_booking_window.min_days, + ad_group_bid_modifier.hotel_advance_booking_window.max_days, + ad_group_bid_modifier.hotel_length_of_stay.min_nights, + ad_group_bid_modifier.hotel_length_of_stay.max_nights, + ad_group_bid_modifier.hotel_check_in_day.day_of_week, + ad_group_bid_modifier.preferred_content.type + FROM + ad_group_bid_modifier"; if (adGroupId != null) { @@ -99,12 +110,48 @@ public void Run(GoogleAdsClient client, long customerId, long? adGroupId) AdGroup adGroup = googleAdsRow.AdGroup; Campaign campaign = googleAdsRow.Campaign; Console.WriteLine("Ad group bid modifier with criterion ID {0}, bid " + - "modifier value {1:0.00}, device type {2} was found in an ad group " + - "with ID {3} of campaign ID {4}.", + "modifier value {1:0.00} was found in an ad group with ID {2} of " + + "campaign ID {3}.", adGroupBidModifier.CriterionId, adGroupBidModifier.BidModifier, - adGroupBidModifier.Device.Type, adGroup.Id, campaign.Id); + + string criterionDetails = " - Criterion type: " + + $"{adGroupBidModifier.CriterionCase}, "; + switch (adGroupBidModifier.CriterionCase) + { + case CriterionOneofCase.Device: + criterionDetails += $"Type: {adGroupBidModifier.Device.Type}"; + break; + + case CriterionOneofCase.HotelAdvanceBookingWindow: + criterionDetails += + $"Min Days: {adGroupBidModifier.HotelAdvanceBookingWindow.MinDays}," + + $"Max Days: {adGroupBidModifier.HotelAdvanceBookingWindow.MaxDays}"; + break; + + case CriterionOneofCase.HotelCheckInDay: + criterionDetails += $"Day of the week: " + + $"{adGroupBidModifier.HotelCheckInDay.DayOfWeek}"; + break; + + case CriterionOneofCase.HotelDateSelectionType: + criterionDetails += $"Date selection type: " + + $"{adGroupBidModifier.HotelDateSelectionType.Type}"; + break; + + case CriterionOneofCase.HotelLengthOfStay: + criterionDetails += + $"Min Nights: {adGroupBidModifier.HotelLengthOfStay.MinNights}," + + $"Max Nights: {adGroupBidModifier.HotelLengthOfStay.MaxNights}"; + break; + + case CriterionOneofCase.PreferredContent: + criterionDetails += + $"Type: {adGroupBidModifier.PreferredContent.Type}"; + break; + } + Console.WriteLine(criterionDetails); } } catch (GoogleAdsException e) diff --git a/examples/Authentication/AuthenticateInWebApplication/AuthenticateInWebApplication.csproj b/examples/Authentication/AuthenticateInWebApplication/AuthenticateInWebApplication.csproj index 450248156..d79848974 100644 --- a/examples/Authentication/AuthenticateInWebApplication/AuthenticateInWebApplication.csproj +++ b/examples/Authentication/AuthenticateInWebApplication/AuthenticateInWebApplication.csproj @@ -48,8 +48,8 @@ {C691BD4D-683D-425B-8BC7-52F161475C7C} Google.Ads.GoogleAds - - ..\..\..\packages\Google.Ads.GoogleAds.6.0.0\lib\net472\Google.Ads.GoogleAds.dll + + ..\..\..\packages\Google.Ads.GoogleAds.6.1.0\lib\net472\Google.Ads.GoogleAds.dll ..\..\..\packages\Google.Api.CommonProtos.2.2.0\lib\net461\Google.Api.CommonProtos.dll @@ -81,33 +81,33 @@ ..\..\..\packages\Google.LongRunning.2.1.0\lib\net461\Google.LongRunning.dll - - ..\..\..\packages\Google.Protobuf.3.13.0\lib\net45\Google.Protobuf.dll + + ..\..\..\packages\Google.Protobuf.3.14.0\lib\net45\Google.Protobuf.dll - ..\..\..\packages\Grpc.Auth.2.33.1\lib\net45\Grpc.Auth.dll + ..\..\..\packages\Grpc.Auth.2.34.0\lib\net45\Grpc.Auth.dll - ..\..\..\packages\Grpc.Core.2.33.1\lib\net45\Grpc.Core.dll + ..\..\..\packages\Grpc.Core.2.34.0\lib\net45\Grpc.Core.dll - ..\..\..\packages\Grpc.Core.Api.2.33.1\lib\net45\Grpc.Core.Api.dll + ..\..\..\packages\Grpc.Core.Api.2.34.0\lib\net45\Grpc.Core.Api.dll - - ..\..\..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + ..\..\..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - ..\..\..\packages\Microsoft.Extensions.Configuration.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll + + ..\..\..\packages\Microsoft.Extensions.Configuration.5.0.0\lib\net461\Microsoft.Extensions.Configuration.dll - - ..\..\..\packages\Microsoft.Extensions.Configuration.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + ..\..\..\packages\Microsoft.Extensions.Configuration.Abstractions.5.0.0\lib\net461\Microsoft.Extensions.Configuration.Abstractions.dll - - ..\..\..\packages\Microsoft.Extensions.Primitives.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + ..\..\..\packages\Microsoft.Extensions.Primitives.5.0.0\lib\net461\Microsoft.Extensions.Primitives.dll ..\..\..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll @@ -115,6 +115,12 @@ ..\..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + ..\..\..\packages\System.Interactive.Async.5.0.0\lib\net461\System.Interactive.Async.dll + + + ..\..\..\packages\System.Linq.Async.5.0.0\lib\net461\System.Linq.Async.dll + ..\..\..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll @@ -124,11 +130,14 @@ ..\..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - ..\..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0-preview.6.20305.6\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + ..\..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll ..\..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + ..\..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + diff --git a/examples/Authentication/AuthenticateInWebApplication/Web.config b/examples/Authentication/AuthenticateInWebApplication/Web.config index ac46f5000..1a5fa25e2 100644 --- a/examples/Authentication/AuthenticateInWebApplication/Web.config +++ b/examples/Authentication/AuthenticateInWebApplication/Web.config @@ -118,7 +118,7 @@ - + @@ -134,11 +134,11 @@ - + - + @@ -160,6 +160,14 @@ + + + + + + + + diff --git a/examples/Authentication/AuthenticateInWebApplication/packages.config b/examples/Authentication/AuthenticateInWebApplication/packages.config index 2b31b524f..8d83c9ab0 100644 --- a/examples/Authentication/AuthenticateInWebApplication/packages.config +++ b/examples/Authentication/AuthenticateInWebApplication/packages.config @@ -1,6 +1,6 @@  - + @@ -9,17 +9,17 @@ - - - - - - + + + + + + - - - - + + + + @@ -29,11 +29,11 @@ - + - + @@ -45,7 +45,7 @@ - + @@ -57,6 +57,7 @@ + diff --git a/examples/Extensions/UpdateSitelink.cs b/examples/Extensions/UpdateSitelink.cs index 802f0dbea..f27c7081e 100644 --- a/examples/Extensions/UpdateSitelink.cs +++ b/examples/Extensions/UpdateSitelink.cs @@ -24,7 +24,7 @@ namespace Google.Ads.GoogleAds.Examples.V6 { /// - /// Updates the sitelink extension feed item with the specified link text and URL. + /// Updates the sitelink extension feed item with the specified link text. /// public class UpdateSitelink : ExampleBase { @@ -54,7 +54,7 @@ public static void Main(string[] args) /// Returns a description about the code example. /// public override string Description => - "Updates the sitelink extension feed item with the specified link text and URL."; + "Updates the sitelink extension feed item with the specified link text."; /// /// Runs the code example. diff --git a/examples/Feeds/CreateFeedItemSet.cs b/examples/Feeds/CreateFeedItemSet.cs new file mode 100644 index 000000000..9065e8002 --- /dev/null +++ b/examples/Feeds/CreateFeedItemSet.cs @@ -0,0 +1,132 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Ads.GoogleAds.Lib; +using Google.Ads.GoogleAds.V6.Errors; +using Google.Ads.GoogleAds.V6.Resources; +using Google.Ads.GoogleAds.V6.Services; +using System; + +namespace Google.Ads.GoogleAds.Examples.V6 +{ + /// + /// This code example creates a new feed item set for a specified feed, which must belong to + /// either a Google Ads location extension or an affiliate extension. This is equivalent to a + /// "location group" in the Google Ads UI.See https://support.google.com/google-ads/answer/9288588 + /// for more details. + /// + public class CreateFeedItemSet : ExampleBase + { + /// + /// Main method, to run this code example as a standalone application. + /// + /// The command line arguments. + public static void Main(string[] args) + { + CreateFeedItemSet codeExample = new CreateFeedItemSet(); + Console.WriteLine(codeExample.Description); + + // The Google Ads customer ID for which the call is made. + long customerId = long.Parse("INSERT_CUSTOMER_ID_HERE"); + + // The Feed ID for creating the feed item set. + long feedId = long.Parse("INSERT_FEED_ID_HERE"); + + codeExample.Run(new GoogleAdsClient(), customerId, feedId); + } + + /// + /// Returns a description about the code example. + /// + public override string Description => "This code example creates a new feed item set for " + + "a specified feed, which must belong to either a Google Ads location extension or " + + "an affiliate extension. This is equivalent to a 'location group' in the Google " + + "Ads UI. See https://support.google.com/google-ads/answer/9288588 for more details."; + + /// + /// Runs the code example. + /// + /// The Google Ads API client. + /// The Google Ads customer ID for which the call is made. + /// The Feed ID for creating the feed item set. + public void Run(GoogleAdsClient client, long customerId, long feedId) + { + // Get the FeedItemSetService. + FeedItemSetServiceClient feedItemService = client.GetService( + Services.V6.FeedItemSetService); + + FeedItemSet feedItemSet = new FeedItemSet() + { + Feed = ResourceNames.Feed(customerId, feedId), + DisplayName = "Feed Item Set #" + ExampleUtilities.GetRandomString(), + }; + + // A feed item set can be created as a dynamic set by setting an optional filter + // field below. If your feed is a location extension, uncomment the code that calls + // DynamicLocationSetFilter. If instead your feed is an affiliate extension, + // uncomment the code that sets the DynamicAffiliateLocationSetFilter instead. + + // 1) Location extension. + //feedItemSet.DynamicLocationSetFilter = new DynamicLocationSetFilter() + //{ + // // Dynamic location sets support filtering locations based on business name + // // and/or labels. If both are specified, the feed item set will only include + // // locations that satisfy both types of filters. + // BusinessNameFilter = new BusinessNameFilter() + // { + // BusinessName = "INSERT_YOUR_BUSINESS_NAME_HERE", + // FilterType = FeedItemSetStringFilterType.Exact, + // }, + // // Adds a filter by feed item label resource name. If multiple labels are + // // specified, the feed item set will only include feed items with all of the + // // specified labels. + // Labels = { "INSERT_BUSINESS_LABEL_HERE" } + //}; + + // 2) Affiliate extension. + //feedItemSet.DynamicAffiliateLocationSetFilter = new DynamicAffiliateLocationSetFilter() + //{ + // ChainIds = { long.Parse("INSERT_CHAIN_ID_HERE") } + //}; + + // Constructs an operation that will create a new feed item set. + FeedItemSetOperation operation = new FeedItemSetOperation() + { + Create = feedItemSet + }; + + try + { + // Issues a mutate request to add the feed item set on the server. + MutateFeedItemSetsResponse response = + feedItemService.MutateFeedItemSets(customerId.ToString(), new[] { operation }); + + // Prints the resource names of the added feed item sets. + foreach (MutateFeedItemSetResult addedFeedItemSet in response.Results) + { + Console.WriteLine("Created a feed item set with resource name " + + $"'{addedFeedItemSet.ResourceName}'"); + } + } + catch (GoogleAdsException e) + { + Console.WriteLine("Failure:"); + Console.WriteLine($"Message: {e.Message}"); + Console.WriteLine($"Failure: {e.Failure}"); + Console.WriteLine($"Request ID: {e.RequestId}"); + throw; + } + } + } +} diff --git a/examples/Feeds/GetFeedItemsOfFeedItemSet.cs b/examples/Feeds/GetFeedItemsOfFeedItemSet.cs new file mode 100644 index 000000000..843b32747 --- /dev/null +++ b/examples/Feeds/GetFeedItemsOfFeedItemSet.cs @@ -0,0 +1,110 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Ads.GoogleAds.Lib; +using Google.Ads.GoogleAds.V6.Errors; +using Google.Ads.GoogleAds.V6.Services; +using System; + +namespace Google.Ads.GoogleAds.Examples.V6 +{ + /// + /// This code example gets all feed items of the specified feed item set by fetching all + /// feed item set links. To create a new feed item set, run CreateFeedItemSet.cs. To link + /// a feed item to a feed item set, run LinkFeedItemSet.cs. + /// + public class GetFeedItemsOfFeedItemSet : ExampleBase + { + /// + /// Main method, to run this code example as a standalone application. + /// + /// The command line arguments. + public static void Main(string[] args) + { + GetFeedItemsOfFeedItemSet codeExample = new GetFeedItemsOfFeedItemSet(); + Console.WriteLine(codeExample.Description); + + // The Google Ads customer ID for which the call is made. + long customerId = long.Parse("INSERT_CUSTOMER_ID_HERE"); + + // ID of the feed associated with the feed item set. + long feedId = long.Parse("INSERT_FEED_ID_HERE"); + + // ID of the feed item set. + long feedItemSetId = long.Parse("FEED_ITEM_SET_ID"); + + codeExample.Run(new GoogleAdsClient(), customerId, feedId, feedItemSetId); + } + + /// + /// Returns a description about the code example. + /// + public override string Description => "This code example gets all feed items of the " + + "specified feed item set by fetching all feed item set links. To create a new feed " + + "item set, run CreateFeedItemSet.cs. To link a feed item to a feed item set, " + + "run LinkFeedItemSet.cs."; + + /// + /// Runs the code example. + /// + /// The Google Ads API client. + /// The Google Ads customer ID for which the call is made. + /// ID of the feed associated with the feed item set. + /// ID of the feed item set. + public void Run(GoogleAdsClient client, long customerId, long feedId, long feedItemSetId) + { + // Get the GoogleAdsService. + GoogleAdsServiceClient googleAdsService = client.GetService( + Services.V6.GoogleAdsService); + + string feedItemSetResourceName = ResourceNames.FeedItemSet( + customerId, feedId, feedItemSetId); + + // Creates a query that retrieves all feed item set links associated with the + // specified feed item set. + string query = $@" + SELECT + feed_item_set_link.feed_item + FROM + feed_item_set_link + WHERE + feed_item_set_link.feed_item_set = '{feedItemSetResourceName}'"; + + try + { + Console.WriteLine("The feed items with the following resource names are linked " + + $"with the feed item set with ID {feedItemSetId}:"); + + // Issue a search request. + googleAdsService.SearchStream(customerId.ToString(), query, + delegate (SearchGoogleAdsStreamResponse resp) + { + foreach (GoogleAdsRow googleAdsRow in resp.Results) + { + Console.WriteLine(googleAdsRow.FeedItemSetLink.FeedItem); + } + } + ); + } + catch (GoogleAdsException e) + { + Console.WriteLine("Failure:"); + Console.WriteLine($"Message: {e.Message}"); + Console.WriteLine($"Failure: {e.Failure}"); + Console.WriteLine($"Request ID: {e.RequestId}"); + throw; + } + } + } +} diff --git a/examples/Feeds/LinkFeedItemSet.cs b/examples/Feeds/LinkFeedItemSet.cs new file mode 100644 index 000000000..d85a8a7e0 --- /dev/null +++ b/examples/Feeds/LinkFeedItemSet.cs @@ -0,0 +1,122 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Ads.GoogleAds.Lib; +using Google.Ads.GoogleAds.V6.Errors; +using Google.Ads.GoogleAds.V6.Resources; +using Google.Ads.GoogleAds.V6.Services; +using System; + +namespace Google.Ads.GoogleAds.Examples.V6 +{ + /// + /// This code example links the specified feed item to the specified feed item set. The + /// specified feed item set must not be created as a dynamic set, i.e., neither + /// DynamicLocationSetFilter nor DynamicAffiliateLocationSetFilter + /// should be set. In addition, the feed item must belong to the feed associated with the + /// feed item set. + /// + public class LinkFeedItemSet : ExampleBase + { + /// + /// Main method, to run this code example as a standalone application. + /// + /// The command line arguments. + public static void Main(string[] args) + { + LinkFeedItemSet codeExample = new LinkFeedItemSet(); + Console.WriteLine(codeExample.Description); + + // The Google Ads customer ID for which the call is made. + long customerId = long.Parse("INSERT_CUSTOMER_ID_HERE"); + + // ID of the feed associated with the feed item set. + long feedId = long.Parse("INSERT_FEED_ID_HERE"); + + // ID of the feed item set. + long feedItemSetId = long.Parse("FEED_ITEM_SET_ID"); + + // ID of the feed item to link to the set. + long feedItemId = long.Parse("FEED_ITEM_ID"); + + codeExample.Run(new GoogleAdsClient(), customerId, feedId, feedItemSetId, feedItemId); + } + + /// + /// Returns a description about the code example. + /// + public override string Description => "This code example links the specified feed item " + + "to the specified feed item set. The specified feed item set must not be created as " + + "a dynamic set, i.e., neither DynamicLocationSetFilter nor " + + "DynamicAffiliateLocationSetFilter should be set. In addition, the " + + "feed item must belong to the feed associated with the feed item set."; + + /// + /// Runs the code example. + /// + /// The Google Ads API client. + /// The Google Ads customer ID for which the call is made. + /// ID of the feed associated with the feed item set. + /// ID of the feed item set. + /// ID of the feed item to link to the set. + public void Run(GoogleAdsClient client, long customerId, long feedId, long feedItemSetId, + long feedItemId) + { + // Get the FeedItemSetLinkService. + FeedItemSetLinkServiceClient feedItemSetLinkService = client.GetService( + Services.V6.FeedItemSetLinkService); + + // Creates a new feed item set link that binds the specified feed item set and + // feed item. + string feedItemSetResourceName = ResourceNames.FeedItemSet(customerId, feedId, + feedItemSetId); + string feedItemResourceName = ResourceNames.FeedItem(customerId, feedId, feedItemId); + FeedItemSetLink feedItemSetLink = new FeedItemSetLink() + { + FeedItemSet = feedItemSetResourceName, + FeedItem = feedItemResourceName + }; + + // Constructs a feed item set link operation. + FeedItemSetLinkOperation operation = new FeedItemSetLinkOperation() + { + Create = feedItemSetLink + }; + + try + { + // Issues a mutate request to add the feed item set link on the server. + MutateFeedItemSetLinksResponse response = + feedItemSetLinkService.MutateFeedItemSetLinks(customerId.ToString(), + new[] { operation }); + + // Prints some information about the created feed item set link. + foreach (MutateFeedItemSetLinkResult result in response.Results) + { + Console.WriteLine($"Created a feed item set link with resource name " + + $"'{result.ResourceName}' that links feed item set " + + $"'{feedItemSetResourceName}' to feed item '{feedItemResourceName}'."); + } + } + catch (GoogleAdsException e) + { + Console.WriteLine("Failure:"); + Console.WriteLine($"Message: {e.Message}"); + Console.WriteLine($"Failure: {e.Failure}"); + Console.WriteLine($"Request ID: {e.RequestId}"); + throw; + } + } + } +} diff --git a/examples/Google.Ads.GoogleAds.Examples.csproj b/examples/Google.Ads.GoogleAds.Examples.csproj index a3aefe7d2..094273ca3 100644 --- a/examples/Google.Ads.GoogleAds.Examples.csproj +++ b/examples/Google.Ads.GoogleAds.Examples.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/examples/Program.cs b/examples/Program.cs index dc5628588..f9116efe3 100644 --- a/examples/Program.cs +++ b/examples/Program.cs @@ -121,6 +121,8 @@ private static int RunExamplesFromCommandLineArguments(string[] args) string exampleName = args[0]; try { + // Optional: Turn on profiling to see how long an API call takes. + // runner.RunWithProfiling(exampleName, session, args); runner.Run(exampleName, session, args.Skip(1)); return 0; } diff --git a/src/Config/GoogleAdsConfig.cs b/src/Config/GoogleAdsConfig.cs index f1e5fb362..8b3349d35 100644 --- a/src/Config/GoogleAdsConfig.cs +++ b/src/Config/GoogleAdsConfig.cs @@ -157,6 +157,15 @@ public class GoogleAdsConfig : ConfigBase private ConfigSetting merchantCenterAccountId = new ConfigSetting("MerchantCenterAccountId", ""); + /// + /// A flag to determine whether or not to enable profiling. + /// + /// + /// This setting is only for testing purposes. + /// + private ConfigSetting enableProfiling = + new ConfigSetting("EnableProfiling", false); + /// /// Web proxy to be used with the services. /// @@ -406,6 +415,16 @@ internal string MerchantCenterAccountId set => SetPropertyAndNotify(merchantCenterAccountId, value); } + /// + /// A flag to determine whether or not to turn on profiling in the client library. + /// + /// This setting is only for testing purposes. + public bool EnableProfiling + { + get => enableProfiling.Value; + set => SetPropertyAndNotify(enableProfiling, value); + } + /// /// Gets or sets the OAuth2 prn email. /// diff --git a/src/Google.Ads.GoogleAds.csproj b/src/Google.Ads.GoogleAds.csproj index 7c28b414d..d66d5e9ac 100644 --- a/src/Google.Ads.GoogleAds.csproj +++ b/src/Google.Ads.GoogleAds.csproj @@ -3,7 +3,7 @@ Google Ads API Dotnet Client Library Google.Ads.GoogleAds - 6.0.0 + 6.1.0 This library provides you with functionality to access the Google Ads API. The Google Ads API is the modern programmatic interface to Google Ads and the next generation of the AdWords API. See https://developers.google.com/google-ads/api to learn more about Google Ads API. https://github.com/googleads/google-ads-dotnet/blob/master/ChangeLog GoogleAds Google @@ -30,8 +30,8 @@ true true true - 6.0.0.0 - 6.0.0.0 + 6.1.0.0 + 6.1.0.0 latest @@ -44,19 +44,19 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Lib/GoogleAdsServiceClientFactory.cs b/src/Lib/GoogleAdsServiceClientFactory.cs index f3c45efbb..b52653201 100644 --- a/src/Lib/GoogleAdsServiceClientFactory.cs +++ b/src/Lib/GoogleAdsServiceClientFactory.cs @@ -14,6 +14,7 @@ using Google.Ads.GoogleAds.Config; using Google.Ads.GoogleAds.Interceptors; +using Google.Ads.GoogleAds.Profiling; using Google.Api.Gax; using Google.Api.Gax.Grpc; using Grpc.Core; @@ -41,9 +42,12 @@ internal TService GetService( where TService : GoogleAdsServiceClientBase { Channel channel = CreateChannel(config); - CallInvoker callInvoker = channel + CallInvoker interceptedInvoker = channel .Intercept(GoogleAdsGrpcInterceptor.GetInstance(config)); + CallInvoker callInvoker = config.EnableProfiling ? + new ProfilingCallInvoker(interceptedInvoker, config) : interceptedInvoker; + // Build a service context to bind the service, configuration and CallSettings. GoogleAdsServiceContext serviceContext = new GoogleAdsServiceContext(); diff --git a/src/Profiling/ProfileTracker.cs b/src/Profiling/ProfileTracker.cs new file mode 100644 index 000000000..b8597a720 --- /dev/null +++ b/src/Profiling/ProfileTracker.cs @@ -0,0 +1,87 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// A class to keep track of profiles. + /// + internal class ProfileTracker + { + /// + /// The singleton instance. + /// + private static readonly Lazy instance = + new Lazy(() => new ProfileTracker()); + + /// + /// Prevents a default instance of the class + /// from being created. + /// + private ProfileTracker() + { + } + + /// + /// The synchronize lock. + /// + private static object syncLock = new object(); + + /// + /// The profiles tracked by this instance. + /// + private HashSet profiles = new HashSet(); + + /// + /// Gets the singleton instance. + /// + internal static ProfileTracker Instance + { + get => instance.Value; + } + + /// + /// Adds a profile to track. + /// + /// The profile to track. + internal void AddProfile(TimeProfile profile) + { + lock (syncLock) + { + profiles.Add(profile); + } + } + + /// + /// Gets a debug string. + /// + /// The Debug string. + internal string DebugString() + { + StringBuilder builder = new StringBuilder(); + lock (syncLock) + { + foreach (TimeProfile profile in profiles) + { + builder.AppendLine(profile.GetDebugString()); + } + } + return builder.ToString(); + } + } +} diff --git a/src/Profiling/ProfiledMarshaller.cs b/src/Profiling/ProfiledMarshaller.cs new file mode 100644 index 000000000..5d3a67ee3 --- /dev/null +++ b/src/Profiling/ProfiledMarshaller.cs @@ -0,0 +1,134 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Grpc.Core; +using System; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// A marshaller that wraps another marshaller and profiles it. + /// + /// The type that the underlying marshaller can serialize or deserialize. + /// + internal class ProfiledMarshaller + { + /// + /// The marshaller that this instance wraps. + /// + private Marshaller wrappedMarshaller; + + /// + /// Gets the marshaller. + /// + internal Marshaller Marshaller + { + get; + private set; + } + + /// + /// The time profile for recording time taken by the serialization and deserialization + /// process. + /// + private TimeProfile timeProfile; + + /// + /// Initializes a new instance of the class. + /// + /// The marshaller. + /// The time profile. + internal ProfiledMarshaller(Marshaller marshaller, TimeProfile timeProfile) + { + this.timeProfile = timeProfile; + Wrap(marshaller); + } + + /// + /// Wraps the specified marshaller. + /// + /// The marshaller. + /// + private void Wrap(Marshaller marshaller) + { + this.wrappedMarshaller = marshaller; + this.Marshaller = new Marshaller(SerializeWithContext, DeserializeWithContext); + } + + /// + /// Deserializes the specified object. + /// + /// The deserialization context. + /// The deserialized object. + private T DeserializeWithContext(DeserializationContext ctx) + { + T retval = default; + TimeInterval timeTaken = Timer.Benchmark(() => { + retval = this.wrappedMarshaller.ContextualDeserializer(ctx); + }); + this.timeProfile.Deserialize.Add(timeTaken); + return retval; + } + + /// + /// Deserializes the specified object. + /// + /// The serialized bytes. + /// The deserialized object. + private T Deserialize(byte[] arg) + { + T retval = default; + TimeInterval timeTaken = Timer.Benchmark(() => { + retval = this.wrappedMarshaller.Deserializer(arg); + }); + this.timeProfile.Deserialize.Add(timeTaken); + return retval; + } + + /// + /// Serializes the specified object. + /// + /// The object to serialize. + /// The serialization context. + private void SerializeWithContext(T arg, SerializationContext ctx) + { + TimeInterval timeTaken = Timer.Benchmark(() => + { + this.wrappedMarshaller.ContextualSerializer(arg, ctx); + }); + + this.timeProfile.Serialize.Start = timeTaken.Start; + this.timeProfile.Serialize.End = timeTaken.End; + } + + /// + /// Serializes the specified object. + /// + /// The object to serialize. + /// The serialized bytes. + private byte[] Serialize(T arg) + { + byte[] retval = null; + + TimeInterval timeTaken = Timer.Benchmark(() => + { + retval = this.wrappedMarshaller.Serializer(arg); + }); + + this.timeProfile.Serialize.Start = timeTaken.Start; + this.timeProfile.Serialize.End = timeTaken.End; + return retval; + } + } +} diff --git a/src/Profiling/ProfiledMethod.cs b/src/Profiling/ProfiledMethod.cs new file mode 100644 index 000000000..3aaa16665 --- /dev/null +++ b/src/Profiling/ProfiledMethod.cs @@ -0,0 +1,115 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Grpc.Core; +using System; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// A method that wraps over another method for profiling purposes. + /// + /// The type of the request. + /// The type of the response. + internal class ProfiledMethod + { + /// + /// The profiled request marshaller. + /// + private ProfiledMarshaller requestMarshaller; + + /// + /// The profiled response marshaller. + /// + private ProfiledMarshaller responseMarshaller; + + /// + /// The wrapped method. + /// + private Method wrappedMethod; + + /// + /// The time profile for recording time taken by the serialization and deserialization + /// process, as well as the method details. + /// + private TimeProfile timeProfile = new TimeProfile(); + + /// + /// Gets the method. + /// + internal Method Method + { + get; + private set; + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// The method to wrap over. + /// A flag to determine whether to enable profiling or not. + /// + internal ProfiledMethod(Method method, bool enableProfiling) + { + if (enableProfiling) + { + Wrap(method); + } + else + { + this.Method = method; + } + } + + internal bool IsAsync + { + get; + } + + /// + /// Sets the status checker, that can be used to check if the method call is complete + /// or not. + /// + /// The status callback. + internal void SetStatusChecker(Func statusCallback) + { + timeProfile.SetStatusChecker(statusCallback); + } + + internal void SetMetadataChecker(Func getTrailers) + { + timeProfile.SetMetadataChecker(getTrailers); + } + + /// + /// Wraps the specified method. + /// + /// The method to be wrapped. + private void Wrap(Method method) + { + this.wrappedMethod = method; + this.requestMarshaller = new ProfiledMarshaller( + method.RequestMarshaller, timeProfile); + this.responseMarshaller = new ProfiledMarshaller( + method.ResponseMarshaller, timeProfile); + timeProfile.ServiceName = method.ServiceName; + timeProfile.MethodName = method.Name; + this.Method = new Method(method.Type, method.ServiceName, + method.Name, this.requestMarshaller.Marshaller, + this.responseMarshaller.Marshaller); + ProfileTracker.Instance.AddProfile(timeProfile); + } + } +} diff --git a/src/Profiling/ProfilingCallInvoker.cs b/src/Profiling/ProfilingCallInvoker.cs new file mode 100644 index 000000000..e083485a4 --- /dev/null +++ b/src/Profiling/ProfilingCallInvoker.cs @@ -0,0 +1,168 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Ads.GoogleAds.Config; +using Grpc.Core; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// A call invoker that profiles the call timing. + /// + /// + internal class ProfilingCallInvoker : CallInvoker + { + /// + /// The invoker that this instance wraps. + /// + private CallInvoker invoker; + + /// + /// The configuration. + /// + private GoogleAdsConfig config; + + /// + /// Initializes a new instance of the class. + /// + /// The invoker. + /// The library configuration. + internal ProfilingCallInvoker(CallInvoker invoker, GoogleAdsConfig config) + { + this.invoker = invoker; + this.config = config; + } + + /// + /// Invokes a client streaming call asynchronously. + /// In client streaming scenario, client sends a stream of requests and server responds + /// with a single response. + /// + /// Request message type for this call. + /// Request message type for this call. + /// The method details such as service and method names, marshallers + /// for request and response, etc. + /// The host to which the call is made. + /// The call options. + /// The client streaming call instance. + /// This method is not profiled and is passthru. + public override AsyncClientStreamingCall + AsyncClientStreamingCall(Method method, + string host, CallOptions options) + { + return invoker.AsyncClientStreamingCall(method, host, options); + } + + /// + /// Invokes a duplex streaming call asynchronously. + /// In duplex streaming scenario, client sends a stream of requests and server responds + /// with a stream of responses. The response stream is completely independent and both side + /// can be sending messages at the same time. + /// + /// Request message type for this call. + /// Request message type for this call. + /// The method details such as service and method names, marshallers + /// for request and response, etc. + /// The host to which the call is made. + /// The call options. + /// The duplex streaming call instance. + /// This method is not profiled and is passthru. + public override AsyncDuplexStreamingCall + AsyncDuplexStreamingCall(Method method, + string host, CallOptions options) + { + return invoker.AsyncDuplexStreamingCall(method, host, options); + } + + /// + /// Invokes a server streaming call asynchronously. + /// In server streaming scenario, client sends on request and server responds with a + /// stream of responses. + /// + /// Request message type for this call. + /// Request message type for this call. + /// The method details such as service and method names, marshallers + /// for request and response, etc. + /// The host to which the call is made. + /// The call options. + /// The request object. + /// The server streaming call instance. + /// This method is profiled. + public override AsyncServerStreamingCall AsyncServerStreamingCall( + Method method, string host, CallOptions options, TRequest request) + { + if (config.EnableProfiling) + { + ProfiledMethod profiledMethod = + new ProfiledMethod(method, config.EnableProfiling); + var retval = invoker.AsyncServerStreamingCall(profiledMethod.Method, host, + options, request); + profiledMethod.SetMetadataChecker(retval.GetTrailers); + profiledMethod.SetStatusChecker(retval.GetStatus); + return retval; + } + return invoker.AsyncServerStreamingCall(method, host, options, request); + } + + /// + /// Invokes a simple remote call asynchronously. + /// + /// Request message type for this call. + /// Request message type for this call. + /// The method details such as service and method names, marshallers + /// for request and response, etc. + /// The host to which the call is made. + /// The call options. + /// The request object. + /// The async unary call instance. + /// This method is profiled. + public override AsyncUnaryCall AsyncUnaryCall( + Method method, string host, CallOptions options, TRequest request) + { + if (config.EnableProfiling) + { + ProfiledMethod profiledMethod = + new ProfiledMethod(method, config.EnableProfiling); + + var retval = invoker.AsyncUnaryCall(profiledMethod.Method, host, options, request); + profiledMethod.SetMetadataChecker(delegate () + { + retval.ResponseHeadersAsync.Wait(); + return retval.ResponseHeadersAsync.Result; + }); + profiledMethod.SetStatusChecker(retval.GetStatus); + return retval; + } + return invoker.AsyncUnaryCall(method, host, options, request); + } + + /// + /// Invokes a simple remote call in a blocking fashion. + /// + /// Request message type for this call. + /// Request message type for this call. + /// The method details such as service and method names, marshallers + /// for request and response, etc. + /// The host to which the call is made. + /// The call options. + /// The request object. + /// The response. + /// This method is not profiled and is passthru. + public override TResponse BlockingUnaryCall( + Method method, string host, CallOptions options, TRequest request) + { + return invoker.BlockingUnaryCall(method, host, options, request); + } + } +} diff --git a/src/Profiling/TimeInterval.cs b/src/Profiling/TimeInterval.cs new file mode 100644 index 000000000..61ead2682 --- /dev/null +++ b/src/Profiling/TimeInterval.cs @@ -0,0 +1,53 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// Represents an interval of time. + /// + internal class TimeInterval + { + /// + /// Gets or sets the start time. + /// + internal DateTime Start + { + get; + set; + } + + /// + /// Gets or sets the end. + /// + internal DateTime End + { + get; + set; + } + + /// + /// Gets the duration of ther interval. + /// + internal TimeSpan Duration + { + get + { + return End - Start; + } + } + } +} diff --git a/src/Profiling/TimeProfile.cs b/src/Profiling/TimeProfile.cs new file mode 100644 index 000000000..445aba56f --- /dev/null +++ b/src/Profiling/TimeProfile.cs @@ -0,0 +1,278 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Ads.GoogleAds.Lib; +using Grpc.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// Represents a time profile. + /// + internal class TimeProfile + { + /// + /// The status checker, to check if the underlying API call is complete. + /// + private Func statusChecker; + + /// + /// The trailign metadata checker, to retrieve the request ID. + /// + private Func getTrailers; + + /// + /// ID of this time profile. + /// + internal Guid ID + { + get; + } = Guid.NewGuid(); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data + /// structures like a hash table. + /// + public override int GetHashCode() + { + return ID.GetHashCode(); + } + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; + /// otherwise, false. + /// + public override bool Equals(object p) + { + // If parameter is null, return false. + if (Object.ReferenceEquals(p, null)) + { + return false; + } + + // Optimization for a common success case. + if (Object.ReferenceEquals(this, p)) + { + return true; + } + + // If run-time types are not exactly the same, return false. + if (this.GetType() != p.GetType()) + { + return false; + } + + return this.ID == (p as TimeProfile).ID; + } + + /// + /// Gets or sets the name of the service. + /// + internal string ServiceName + { + get; + set; + } + + /// + /// Gets or sets the name of the method. + /// + /// + /// The name of the method. + /// + internal string MethodName + { + get; + set; + } + + /// + /// Gets the time taken for the serialization portion of this profile. + /// + internal TimeInterval Serialize + { + get; + } = new TimeInterval(); + + /// + /// Gets the time taken for the deserialization portion of this profile. + /// + internal List Deserialize + { + get; + } = new List(); + + /// + /// Gets the time taken for the first RPC and Network overhead. You can subtract the time + /// taken for the RPC from the server logs. + /// + internal TimeInterval RPCAndNetwork + { + get + { + return new TimeInterval() + { + Start = Serialize.End, + End = Deserialize.First().Start + }; + } + } + + /// + /// Gets the serialize span. + /// + internal TimeSpan PagingNetworkOverhead + { + get + { + TimeSpan span = new TimeSpan(); + for (int i = 0; i < Deserialize.Count - 1; i++) + { + span += (Deserialize[i + 1].Start - Deserialize[i].End); + } + return span; + } + } + + /// + /// Gets the serialization overhead. + /// + internal TimeSpan SerializeOverhead + { + get + { + return Serialize.Duration; + } + } + + /// + /// Gets the deserialization overhead. + /// + internal TimeSpan DeserializeOverhead + { + get + { + TimeSpan span = new TimeSpan(); + for (int i = 0; i < Deserialize.Count; i++) + { + span += Deserialize[i].Duration; + } + return span; + } + } + + /// + /// Gets the time taken for the End-To-End call. + /// + /// This is calculated as End time of last Deserialize entry - Start time of + /// Serialize Entry. + internal TimeInterval E2E + { + get + { + TimeInterval end = (Deserialize.Count > 0) ? Deserialize.Last() : Serialize; + return new TimeInterval() + { + Start = Serialize.Start, + End = end.End + }; + } + } + + /// + /// Sets the status checker. + /// + /// The status checker. + internal void SetStatusChecker(Func statusChecker) + { + this.statusChecker = statusChecker; + } + + /// + /// Sets the metadata checker. + /// + /// The trailing metdata checker. + internal void SetMetadataChecker(Func getTrailers) + { + this.getTrailers = getTrailers; + } + + /// + /// Gets the request identifier associated with this API call. + /// + /// The request ID. + internal string GetRequestId() + { + Metadata trailers = this.getTrailers(); + GoogleAdsResponseMetadata respHeaders = new GoogleAdsResponseMetadata(trailers); + return respHeaders.RequestId; + } + + /// + /// Wait for the API call to complete. + /// + private void WaitForCompletion() + { + if (statusChecker == null) + { + throw new InvalidOperationException("Status checker is not set."); + } + while (true) + { + try + { + statusChecker(); + return; + } + catch + { + Thread.Sleep(100); + } + } + } + + /// + /// Gets a debug string. + /// + /// + /// A Debug string. + /// + internal string GetDebugString() + { + WaitForCompletion(); + StringBuilder builder = new StringBuilder(); + builder.AppendLine($"Method: {ServiceName}::{MethodName}"); + builder.AppendLine($"Request ID: {GetRequestId()}"); + builder.AppendLine($"E2E: Start: {E2E.Start}, End: {E2E.End}, " + + $"Diff: {E2E.Duration.TotalMilliseconds} ms."); + builder.AppendLine($" - RPC+Network: Start: {RPCAndNetwork.Start}, " + + $"End: {RPCAndNetwork.End}, Diff: {RPCAndNetwork.Duration.TotalMilliseconds} ms."); + builder.AppendLine($" - Serialize: {SerializeOverhead.TotalMilliseconds} ms."); + builder.AppendLine($" - Deserialize: {DeserializeOverhead.TotalMilliseconds} ms."); + return builder.ToString(); + } + } +} diff --git a/src/Profiling/Timer.cs b/src/Profiling/Timer.cs new file mode 100644 index 000000000..b06b39676 --- /dev/null +++ b/src/Profiling/Timer.cs @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Diagnostics; + +namespace Google.Ads.GoogleAds.Profiling +{ + /// + /// Utility class for measuring execution times. + /// + internal class Timer + { + /// + /// Benchmarks the specified action. + /// + /// The action to be benchmarked. + /// Time taken as an interval. + internal static TimeInterval Benchmark(Action action) + { + DateTime startTime = DateTime.UtcNow; + Stopwatch stopwatch = Stopwatch.StartNew(); + action(); + stopwatch.Stop(); + DateTime endTime = startTime.AddTicks(stopwatch.Elapsed.Ticks); + return new TimeInterval() + { + Start = startTime, + End = endTime + }; + } + } +} diff --git a/src/Util/Examples/ExampleRunner.cs b/src/Util/Examples/ExampleRunner.cs index ae6315a39..ee2e84ed5 100644 --- a/src/Util/Examples/ExampleRunner.cs +++ b/src/Util/Examples/ExampleRunner.cs @@ -13,6 +13,7 @@ // limitations under the License. using Google.Ads.GoogleAds.Lib; +using Google.Ads.GoogleAds.Profiling; using System; using System.Collections.Generic; using System.IO; @@ -88,6 +89,20 @@ public void PrintAvailableExamples() } } + /// + /// Runs the specified example name with profiling turned on. + /// + /// Name of the example. + /// The session. + /// The arguments. + public void RunWithProfiling(string exampleName, GoogleAdsClient session, + IEnumerable args) + { + session.Config.EnableProfiling = true; + Run(exampleName, session, args.Skip(1)); + DisplayProfileData(); + } + /// /// Runs the specified example name. /// @@ -174,6 +189,14 @@ private static MethodInfo GetRunMethod(ExampleBase codeExample) return codeExample.GetType().GetMethod("Run"); } + /// + /// Displays the profile data. + /// + private static void DisplayProfileData() + { + Console.WriteLine(ProfileTracker.Instance.DebugString()); + } + /// /// Gets the name of the executable. ///