Commit f7c2cd6e authored by Lemon's avatar Lemon Committed by 吴晟 Wu Sheng

Refactor SkyWalking.AspNetCore (#20)

* Refactor SkyWalking.Diagnostics module

* Init SkyWalking.Extensions.DependencyInjection project

* Add dependency package

* Refactor SkyWalking.AspNetCore

* Rename AddSkyWalkingCore to AddSkyWalking
parent 491e78db
...@@ -19,9 +19,9 @@ namespace SkyWalking.Sample.Backend.Controllers ...@@ -19,9 +19,9 @@ namespace SkyWalking.Sample.Backend.Controllers
// GET api/values/5 // GET api/values/5
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<string> Get(int id, [FromServices] IHttpClientFactory httpClientFactory) public async Task<string> Get(int id)
{ {
var httpClient = httpClientFactory.CreateClient("tracing"); var httpClient = new HttpClient();
return await httpClient.GetStringAsync("http://www.baidu.com"); return await httpClient.GetStringAsync("http://www.baidu.com");
} }
......
...@@ -38,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkyWalking.Sample.Frontend" ...@@ -38,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkyWalking.Sample.Frontend"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkyWalking.Diagnostics.HttpClient", "src\SkyWalking.Diagnostics.HttpClient\SkyWalking.Diagnostics.HttpClient.csproj", "{49DEFCA8-4289-4875-B6A5-35D84B3D2290}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkyWalking.Diagnostics.HttpClient", "src\SkyWalking.Diagnostics.HttpClient\SkyWalking.Diagnostics.HttpClient.csproj", "{49DEFCA8-4289-4875-B6A5-35D84B3D2290}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkyWalking.Extensions.DependencyInjection", "src\SkyWalking.Extensions.DependencyInjection\SkyWalking.Extensions.DependencyInjection.csproj", "{BF5579ED-113C-4EE6-AE03-9A9CA590C924}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -80,6 +82,10 @@ Global ...@@ -80,6 +82,10 @@ Global
{49DEFCA8-4289-4875-B6A5-35D84B3D2290}.Debug|Any CPU.Build.0 = Debug|Any CPU {49DEFCA8-4289-4875-B6A5-35D84B3D2290}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49DEFCA8-4289-4875-B6A5-35D84B3D2290}.Release|Any CPU.ActiveCfg = Release|Any CPU {49DEFCA8-4289-4875-B6A5-35D84B3D2290}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49DEFCA8-4289-4875-B6A5-35D84B3D2290}.Release|Any CPU.Build.0 = Release|Any CPU {49DEFCA8-4289-4875-B6A5-35D84B3D2290}.Release|Any CPU.Build.0 = Release|Any CPU
{BF5579ED-113C-4EE6-AE03-9A9CA590C924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF5579ED-113C-4EE6-AE03-9A9CA590C924}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF5579ED-113C-4EE6-AE03-9A9CA590C924}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF5579ED-113C-4EE6-AE03-9A9CA590C924}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -96,6 +102,7 @@ Global ...@@ -96,6 +102,7 @@ Global
{80A67B09-83F2-477F-907F-95FFF5B8FEAF} = {844CEACD-4C85-4B15-9E2B-892B01FDA4BB} {80A67B09-83F2-477F-907F-95FFF5B8FEAF} = {844CEACD-4C85-4B15-9E2B-892B01FDA4BB}
{2B4E350E-A1E5-4B4A-A642-BCA6D3887E5A} = {844CEACD-4C85-4B15-9E2B-892B01FDA4BB} {2B4E350E-A1E5-4B4A-A642-BCA6D3887E5A} = {844CEACD-4C85-4B15-9E2B-892B01FDA4BB}
{49DEFCA8-4289-4875-B6A5-35D84B3D2290} = {79ED86A5-E9B9-49B2-9354-C911C079D03E} {49DEFCA8-4289-4875-B6A5-35D84B3D2290} = {79ED86A5-E9B9-49B2-9354-C911C079D03E}
{BF5579ED-113C-4EE6-AE03-9A9CA590C924} = {79ED86A5-E9B9-49B2-9354-C911C079D03E}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {94C0DA2C-CCCB-4314-93A2-9809B5DD0583} SolutionGuid = {94C0DA2C-CCCB-4314-93A2-9809B5DD0583}
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
namespace SkyWalking.Diagnostics namespace SkyWalking.Diagnostics
{ {
public interface ITracingDiagnosticListener public interface ITracingDiagnosticProcessor
{ {
string ListenerName { get; } string ListenerName { get; }
} }
......
/*
* Licensed to the OpenSkywalking under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Collections.Generic;
using System.Diagnostics;
using SkyWalking.Diagnostics;
namespace SkyWalking.AspNetCore.Diagnostics
{
public class AdatpeTracingDiagnosticObserver : TracingDiagnosticObserver
{
public AdatpeTracingDiagnosticObserver(IEnumerable<ITracingDiagnosticProcessor> tracingDiagnosticProcessors)
: base(tracingDiagnosticProcessors)
{
}
protected override void OnNext(DiagnosticListener listener, ITracingDiagnosticProcessor diagnosticProcessor)
{
listener.SubscribeWithAdapter(diagnosticProcessor);
}
}
}
\ No newline at end of file
...@@ -27,12 +27,8 @@ using SkyWalking.NetworkProtocol.Trace; ...@@ -27,12 +27,8 @@ using SkyWalking.NetworkProtocol.Trace;
namespace SkyWalking.AspNetCore.Diagnostics namespace SkyWalking.AspNetCore.Diagnostics
{ {
public class HostingDiagnosticListener : ITracingDiagnosticListener public class HostingDiagnosticProcessor : ITracingDiagnosticProcessor
{ {
public HostingDiagnosticListener()
{
}
public string ListenerName { get; } = "Microsoft.AspNetCore"; public string ListenerName { get; } = "Microsoft.AspNetCore";
[DiagnosticName("Microsoft.AspNetCore.Hosting.HttpRequestIn")] [DiagnosticName("Microsoft.AspNetCore.Hosting.HttpRequestIn")]
......
...@@ -19,17 +19,14 @@ ...@@ -19,17 +19,14 @@
using System; using System;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using SkyWalking.Diagnostics.HttpClient;
using Microsoft.Extensions.Hosting; using SkyWalking.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using SkyWalking.AspNetCore.Diagnostics;
using SkyWalking.Diagnostics;
namespace SkyWalking.AspNetCore namespace SkyWalking.AspNetCore
{ {
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddSkyWalking(this IServiceCollection services, public static SkyWalkingBuilder AddSkyWalking(this IServiceCollection services,
Action<SkyWalkingOptions> options) Action<SkyWalkingOptions> options)
{ {
if (options == null) if (options == null)
...@@ -37,29 +34,27 @@ namespace SkyWalking.AspNetCore ...@@ -37,29 +34,27 @@ namespace SkyWalking.AspNetCore
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
return services.AddSkyWalking().Configure(options); return services.Configure(options).AddSkyWalkingCore();
} }
public static IServiceCollection AddSkyWalking(this IServiceCollection services, public static SkyWalkingBuilder AddSkyWalking(this IServiceCollection services,
IConfiguration configuration) IConfiguration configuration)
{ {
return services.AddSkyWalking().Configure<SkyWalkingOptions>(configuration); return services.Configure<SkyWalkingOptions>(configuration).AddSkyWalkingCore();
} }
private static IServiceCollection AddSkyWalking(this IServiceCollection services) private static SkyWalkingBuilder AddSkyWalkingCore(this IServiceCollection services)
{ {
if (services == null) if (services == null)
{ {
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
} }
services.AddHttpClient<TracingHttpClient>("sw-tracing"); var builder = new SkyWalkingBuilder(services);
services.AddSingleton<IHostedService, SkyWalkingHostedService>(); builder.AddHosting().AddDiagnostics().AddHttpClient();
services.AddSingleton<ITracingDiagnosticListener, HostingDiagnosticListener>();
services.AddTransient<HttpMessageHandlerBuilder, TracingHttpMessageHandlerBuilder>();
return services; return builder;
} }
} }
} }
\ No newline at end of file
/*
* Licensed to the OpenSkywalking under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Http;
using SkyWalking.AspNetCore.Diagnostics;
using SkyWalking.Diagnostics;
using SkyWalking.Extensions.DependencyInjection;
namespace SkyWalking.AspNetCore
{
internal static class SkyWalkingBuilderExtensions
{
public static SkyWalkingBuilder AddHosting(this SkyWalkingBuilder builder)
{
builder.Services.AddSingleton<IHostedService, SkyWalkingHostedService>();
builder.Services.AddSingleton<ITracingDiagnosticProcessor, HostingDiagnosticProcessor>();
return builder;
}
public static SkyWalkingBuilder AddDiagnostics(this SkyWalkingBuilder builder)
{
builder.Services.AddSingleton<TracingDiagnosticObserver, AdatpeTracingDiagnosticObserver>();
return builder;
}
public static SkyWalkingBuilder AddHttpClientFactory(this SkyWalkingBuilder builder)
{
builder.Services.AddHttpClient<TracingHttpClient>();
builder.Services.AddTransient<HttpMessageHandlerBuilder, TracingHttpMessageHandlerBuilder>();
return builder;
}
}
}
\ No newline at end of file
...@@ -23,5 +23,10 @@ ...@@ -23,5 +23,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SkyWalking.Core\SkyWalking.Core.csproj" /> <ProjectReference Include="..\SkyWalking.Core\SkyWalking.Core.csproj" />
<ProjectReference Include="..\SkyWalking.Diagnostics.HttpClient\SkyWalking.Diagnostics.HttpClient.csproj" />
<ProjectReference Include="..\SkyWalking.Extensions.DependencyInjection\SkyWalking.Extensions.DependencyInjection.csproj">
<Project>{BF5579ED-113C-4EE6-AE03-9A9CA590C924}</Project>
<Name>SkyWalking.Extensions.DependencyInjection</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
*/ */
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -32,11 +31,9 @@ namespace SkyWalking.AspNetCore ...@@ -32,11 +31,9 @@ namespace SkyWalking.AspNetCore
{ {
public class SkyWalkingHostedService : IHostedService public class SkyWalkingHostedService : IHostedService
{ {
private readonly IEnumerable<ITracingDiagnosticListener> _tracingDiagnosticListeners; private readonly TracingDiagnosticObserver _diagnosticObserver;
private readonly DiagnosticListener _diagnosticListener;
public SkyWalkingHostedService(IOptions<SkyWalkingOptions> options, IHostingEnvironment hostingEnvironment, public SkyWalkingHostedService(IOptions<SkyWalkingOptions> options, IHostingEnvironment hostingEnvironment, TracingDiagnosticObserver diagnosticObserver)
IEnumerable<ITracingDiagnosticListener> tracingDiagnosticListeners, DiagnosticListener diagnosticListener)
{ {
if (string.IsNullOrEmpty(options.Value.DirectServers)) if (string.IsNullOrEmpty(options.Value.DirectServers))
{ {
...@@ -50,17 +47,14 @@ namespace SkyWalking.AspNetCore ...@@ -50,17 +47,14 @@ namespace SkyWalking.AspNetCore
AgentConfig.ApplicationCode = options.Value.ApplicationCode; AgentConfig.ApplicationCode = options.Value.ApplicationCode;
CollectorConfig.DirectServers = options.Value.DirectServers; CollectorConfig.DirectServers = options.Value.DirectServers;
_diagnosticObserver = diagnosticObserver;
_tracingDiagnosticListeners = tracingDiagnosticListeners;
_diagnosticListener = diagnosticListener;
} }
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
await GrpcChannelManager.Instance.ConnectAsync(); await GrpcChannelManager.Instance.ConnectAsync();
await ServiceManager.Instance.Initialize(); await ServiceManager.Instance.Initialize();
foreach (var tracingDiagnosticListener in _tracingDiagnosticListeners) DiagnosticListener.AllListeners.Subscribe(_diagnosticObserver);
_diagnosticListener.SubscribeWithAdapter(tracingDiagnosticListener);
} }
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)
......
...@@ -31,15 +31,7 @@ namespace SkyWalking.AspNetCore ...@@ -31,15 +31,7 @@ namespace SkyWalking.AspNetCore
public override string Name public override string Name
{ {
get => _name; get => _name;
set set => _name = value ?? throw new ArgumentNullException(nameof(value));
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_name = value;
}
} }
public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>(); public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>();
......
/*
* Licensed to the OpenSkywalking under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Diagnostics;
using SkyWalking.Utils;
namespace SkyWalking.Diagnostics
{
public abstract class TracingDiagnosticObserver : IObserver<DiagnosticListener>
{
private readonly IEnumerable<ITracingDiagnosticProcessor> _tracingDiagnosticProcessors;
public TracingDiagnosticObserver(IEnumerable<ITracingDiagnosticProcessor> tracingDiagnosticProcessors)
{
_tracingDiagnosticProcessors = tracingDiagnosticProcessors ??
throw new ArgumentNullException(nameof(tracingDiagnosticProcessors));
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(DiagnosticListener listener)
{
foreach (var diagnosticProcessor in _tracingDiagnosticProcessors.Distinct(x => x.ListenerName))
{
if (listener.Name == diagnosticProcessor.ListenerName)
{
OnNext(listener, diagnosticProcessor);
}
}
}
protected abstract void OnNext(DiagnosticListener listener,
ITracingDiagnosticProcessor diagnosticProcessor);
}
}
\ No newline at end of file
...@@ -14,4 +14,7 @@ ...@@ -14,4 +14,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SkyWalking.Abstractions\SkyWalking.Abstractions.csproj" /> <ProjectReference Include="..\SkyWalking.Abstractions\SkyWalking.Abstractions.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.4.1" />
</ItemGroup>
</Project> </Project>
\ No newline at end of file
using System;
using System.Collections.Generic;
namespace SkyWalking.Utils
{
internal static class EnumerableExtensions
{
public static IEnumerable<T> Distinct<T,K>(this IEnumerable<T> source, Func<T,K> predicate)
{
HashSet<K> sets = new HashSet<K>();
foreach (var item in source)
{
if (sets.Add(predicate(item)))
{
yield return item;
}
}
}
}
}
\ No newline at end of file
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
* *
*/ */
using System.Diagnostics;
using System.Net.Http; using System.Net.Http;
using Microsoft.Extensions.DiagnosticAdapter; using Microsoft.Extensions.DiagnosticAdapter;
using SkyWalking.Context; using SkyWalking.Context;
...@@ -25,7 +26,7 @@ using SkyWalking.NetworkProtocol.Trace; ...@@ -25,7 +26,7 @@ using SkyWalking.NetworkProtocol.Trace;
namespace SkyWalking.Diagnostics.HttpClient namespace SkyWalking.Diagnostics.HttpClient
{ {
public class HttpClientDiagnosticListener : ITracingDiagnosticListener public class HttpClientDiagnosticProcessor : ITracingDiagnosticProcessor
{ {
public string ListenerName { get; } = "HttpHandlerDiagnosticListener"; public string ListenerName { get; } = "HttpHandlerDiagnosticListener";
......
...@@ -17,5 +17,6 @@ ...@@ -17,5 +17,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SkyWalking.Abstractions\SkyWalking.Abstractions.csproj" /> <ProjectReference Include="..\SkyWalking.Abstractions\SkyWalking.Abstractions.csproj" />
<ProjectReference Include="..\SkyWalking.Core\SkyWalking.Core.csproj" /> <ProjectReference Include="..\SkyWalking.Core\SkyWalking.Core.csproj" />
<ProjectReference Include="..\SkyWalking.Extensions.DependencyInjection\SkyWalking.Extensions.DependencyInjection.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
/*
* Licensed to the OpenSkywalking under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 Microsoft.Extensions.DependencyInjection;
using SkyWalking.Extensions.DependencyInjection;
namespace SkyWalking.Diagnostics.HttpClient
{
public static class SkyWalkingBuilderExtensions
{
public static SkyWalkingBuilder AddHttpClient(this SkyWalkingBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.AddSingleton<ITracingDiagnosticProcessor, HttpClientDiagnosticProcessor>();
return builder;
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<Description>Microsoft.Extensions.DependencyInjection (IServiceCollection) support for SkyWalking.</Description>
<AssemblyTitle>SkyWalking.Extensions.DependencyInjection</AssemblyTitle>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>SkyWalking.Extensions.DependencyInjection</AssemblyName>
<PackageId>SkyWalking.Extensions.DependencyInjection</PackageId>
<PackageTags>SkyWalking;APM;Diagnostics</PackageTags>
<PackageReleaseNotes>
</PackageReleaseNotes>
<RootNamespace>SkyWalking.Extensions.DependencyInjection</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SkyWalking.Core\SkyWalking.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.0-preview2-final" />
</ItemGroup>
</Project>
\ No newline at end of file
/*
* Licensed to the OpenSkywalking under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 Microsoft.Extensions.DependencyInjection;
namespace SkyWalking.Extensions.DependencyInjection
{
public class SkyWalkingBuilder
{
public IServiceCollection Services { get; }
public SkyWalkingBuilder(IServiceCollection services)
{
Services = services;
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment