diff --git a/src/Microsoft.DotNet.Interactive.AIUtilities/GptFunction.cs b/src/Microsoft.DotNet.Interactive.AIUtilities/GptFunction.cs index 9f9d4326c6..df3ddc4813 100644 --- a/src/Microsoft.DotNet.Interactive.AIUtilities/GptFunction.cs +++ b/src/Microsoft.DotNet.Interactive.AIUtilities/GptFunction.cs @@ -5,16 +5,12 @@ using System.Text.Json; using System.Text.Json.Serialization; - namespace Microsoft.DotNet.Interactive.AIUtilities; public class GptFunction { private static readonly JsonSerializerOptions SerializerOptions; - public string Name { get; } private readonly Delegate _function; - public string JsonSignature { get; } - public string? Description { get; } static GptFunction() { @@ -31,6 +27,10 @@ internal GptFunction(string name, string jsonSignature, Delegate function, strin Description = description; } + public string? Description { get; } + public string JsonSignature { get; } + public string Name { get; } + public object? Execute(string parameterJson) { // parameters extraction @@ -52,6 +52,7 @@ internal GptFunction(string name, string jsonSignature, Delegate function, strin return Execute(json); } + public object? Execute(JsonElement json) { // parameters extraction @@ -70,8 +71,6 @@ internal GptFunction(string name, string jsonSignature, Delegate function, strin } return parameters; - - throw new ArgumentException("arguments property is not found."); } private object? Deserialize(ParameterInfo parameterInfo, JsonElement jsonArgs) diff --git a/src/Microsoft.DotNet.Interactive.AIUtilities/InteractiveExtension.cs b/src/Microsoft.DotNet.Interactive.AIUtilities/InteractiveExtension.cs new file mode 100644 index 0000000000..8322bdafe1 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.AIUtilities/InteractiveExtension.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel; +using Pocket; + +namespace Microsoft.DotNet.Interactive.AIUtilities; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static class InteractiveExtension +{ + public static void Load() + { + Logger.Log.Event(); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.AIUtilities/Microsoft.DotNet.Interactive.AIUtilities.csproj b/src/Microsoft.DotNet.Interactive.AIUtilities/Microsoft.DotNet.Interactive.AIUtilities.csproj index a8bb3b7a20..f7c7064d4f 100644 --- a/src/Microsoft.DotNet.Interactive.AIUtilities/Microsoft.DotNet.Interactive.AIUtilities.csproj +++ b/src/Microsoft.DotNet.Interactive.AIUtilities/Microsoft.DotNet.Interactive.AIUtilities.csproj @@ -16,6 +16,17 @@ + + all + + + + + + + + + diff --git a/src/Microsoft.DotNet.Interactive.AIUtilities/Text.cs b/src/Microsoft.DotNet.Interactive.AIUtilities/Text.cs index 29c4509624..852e14cff3 100644 --- a/src/Microsoft.DotNet.Interactive.AIUtilities/Text.cs +++ b/src/Microsoft.DotNet.Interactive.AIUtilities/Text.cs @@ -31,7 +31,7 @@ public static IObservable ChunkByTokenCountWithOverlap(this IObservable< return source.SelectMany(text => { - return System.Reactive.Linq.Observable.Create(o => + return Observable.Create(o => { var chunks = tokenizer.ChunkByTokenCountWithOverlap(text, maxTokenCount, overlapTokenCount); foreach (var chunk in chunks) diff --git a/src/Microsoft.DotNet.Interactive.AIUtilities/Tokenizer.cs b/src/Microsoft.DotNet.Interactive.AIUtilities/Tokenizer.cs index 5c887f0ac4..84ce2c5834 100644 --- a/src/Microsoft.DotNet.Interactive.AIUtilities/Tokenizer.cs +++ b/src/Microsoft.DotNet.Interactive.AIUtilities/Tokenizer.cs @@ -1,12 +1,11 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. - using Microsoft.DeepDev; +using Pocket; namespace Microsoft.DotNet.Interactive.AIUtilities; - public static class Tokenizer { public static int GetTokenCount(this ITokenizer tokenizer, string text) @@ -18,6 +17,8 @@ public static int GetTokenCount(this ITokenizer tokenizer, string text) public static async Task CreateAsync(TokenizerModel model) { + Logger.Log.Event(properties: ("model", model)); + var tokenizer = model switch { TokenizerModel.ada2 => await TokenizerBuilder.CreateByModelNameAsync("text-embedding-ada-002"), diff --git a/src/Microsoft.DotNet.Interactive.AIUtilities/extension.dib b/src/Microsoft.DotNet.Interactive.AIUtilities/extension.dib new file mode 100644 index 0000000000..6555455dc2 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.AIUtilities/extension.dib @@ -0,0 +1,3 @@ +#!csharp + +await Microsoft.DotNet.Interactive.AIUtilities.InteractiveExtension.Load(); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/JupyterKernel.cs b/src/Microsoft.DotNet.Interactive.Jupyter/JupyterKernel.cs index abb469228b..95f92f1b4a 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/JupyterKernel.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/JupyterKernel.cs @@ -62,7 +62,7 @@ private static async Task RequestKernelInfo(IMessageSender send return kernelInfoReply; } - private async static Task RunOnKernelAsync( + private static async Task RunOnKernelAsync( RequestMessage content, IMessageSender sender, IMessageReceiver receiver, diff --git a/src/Microsoft.DotNet.Interactive.Telemetry/ITelemetrySender.cs b/src/Microsoft.DotNet.Interactive.Telemetry/ITelemetrySender.cs deleted file mode 100644 index 7f1684fb47..0000000000 --- a/src/Microsoft.DotNet.Interactive.Telemetry/ITelemetrySender.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -namespace Microsoft.DotNet.Interactive.Telemetry; - -public interface ITelemetrySender -{ - bool Enabled { get; } - - void TrackEvent(string eventName, IDictionary properties, IDictionary measurements); -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.Telemetry/TelemetrySender.cs b/src/Microsoft.DotNet.Interactive.Telemetry/TelemetrySender.cs index 13af6980fa..ae8a74cdf1 100644 --- a/src/Microsoft.DotNet.Interactive.Telemetry/TelemetrySender.cs +++ b/src/Microsoft.DotNet.Interactive.Telemetry/TelemetrySender.cs @@ -12,26 +12,27 @@ namespace Microsoft.DotNet.Interactive.Telemetry; -public class TelemetrySender : ITelemetrySender +public class TelemetrySender { private readonly IFirstTimeUseNoticeSentinel _firstTimeUseNoticeSentinel; private readonly string _eventsNamespace; - private readonly string _appInsightsConnectionString; - private readonly string _currentSessionId = null; private TelemetryClient _client = null; private Dictionary _commonProperties = null; private Dictionary _commonMetrics = null; private Task _trackEventTask = null; + private readonly bool _enabled; - private const string DefaultAppInsightsConnectionString = @"InstrumentationKey=b0dafad5-1430-4852-bc61-95c836b3e612;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/"; + private const string DefaultAppInsightsConnectionString = "InstrumentationKey=b0dafad5-1430-4852-bc61-95c836b3e612;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/"; public const string TelemetryOptOutEnvironmentVariableName = "DOTNET_INTERACTIVE_CLI_TELEMETRY_OPTOUT"; - public const string WelcomeMessage = @"Welcome to .NET Interactive! ---------------------- -Telemetry ---------- -The .NET Core tools collect usage data in order to help us improve your experience.The data is anonymous and doesn't include command-line arguments. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_INTERACTIVE_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. -"; + public const string WelcomeMessage = $""" + Welcome to .NET Interactive! + --------------------- + Telemetry + --------- + The .NET tools collect usage data in order to help us improve your experience. The data is anonymous and doesn't include command-line arguments. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the {TelemetryOptOutEnvironmentVariableName} environment variable to '1' or 'true' using your favorite shell. + + """; public TelemetrySender( string productVersion, @@ -46,47 +47,58 @@ public TelemetrySender( _firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel ?? throw new ArgumentNullException(nameof(firstTimeUseNoticeSentinel)); _eventsNamespace = eventsNamespace; - _appInsightsConnectionString = appInsightsConnectionString ?? DefaultAppInsightsConnectionString; + var s = appInsightsConnectionString ?? DefaultAppInsightsConnectionString; - Enabled = !GetEnvironmentVariableAsBool(TelemetryOptOutEnvironmentVariableName) && - firstTimeUseNoticeSentinel.Exists(); + _enabled = !GetEnvironmentVariableAsBool(TelemetryOptOutEnvironmentVariableName) && + firstTimeUseNoticeSentinel.Exists(); - if (Enabled) + if (_enabled) { - // Store the session ID in a static field so that it can be reused - _currentSessionId = Guid.NewGuid().ToString(); - //initialize in task to offload to parallel thread - _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry(productVersion)); + _trackEventTask = Task.Factory.StartNew(Initialize); } - } - public bool Enabled { get; } + void Initialize() + { + try + { + var config = new TelemetryConfiguration(); + config.ConnectionString = s; + _client = new TelemetryClient(config); + _client.Context.Session.Id = Guid.NewGuid().ToString(); + _client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem; + + _commonProperties = new TelemetryCommonProperties(productVersion).GetTelemetryCommonProperties(); + _commonMetrics = new Dictionary(); + } + catch (Exception e) + { + _client = null; + // we don't want to fail the tool if telemetry fails. + Debug.Fail(e.ToString()); + } + } + } public static bool SkipFirstTimeExperience => GetEnvironmentVariableAsBool(FirstTimeUseNoticeSentinel.SkipFirstTimeExperienceEnvironmentVariableName); public static bool IsRunningInDockerContainer => GetEnvironmentVariableAsBool("DOTNET_RUNNING_IN_CONTAINER"); - private static bool GetEnvironmentVariableAsBool(string name) - { - switch (Environment.GetEnvironmentVariable(name)?.ToLowerInvariant()) + private static bool GetEnvironmentVariableAsBool(string name) => + Environment.GetEnvironmentVariable(name)?.ToLowerInvariant() switch { - case "true": - case "1": - case "yes": - return true; - - default: - return false; - } - } + "true" => true, + "1" => true, + "yes" => true, + _ => false + }; public void TrackEvent( string eventName, IDictionary properties = null, IDictionary measurements = null) { - if (!Enabled) + if (!_enabled) { return; } @@ -100,38 +112,12 @@ public void TrackEvent( public void TrackStartupEvent(ParseResult parseResult, StartupTelemetryEventBuilder eventBuilder) { - if (parseResult is null || !Enabled || eventBuilder is null) - { - return; - } - foreach (var entry in eventBuilder.GetTelemetryEventsFrom(parseResult)) { TrackEvent(entry.EventName, entry.Properties, entry.Metrics); } } - protected virtual void InitializeTelemetry(string productVersion) - { - try - { - var config = new TelemetryConfiguration(); - config.ConnectionString = _appInsightsConnectionString; - _client = new TelemetryClient(config); - _client.Context.Session.Id = _currentSessionId; - _client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem; - - _commonProperties = new TelemetryCommonProperties(productVersion).GetTelemetryCommonProperties(); - _commonMetrics = new Dictionary(); - } - catch (Exception e) - { - _client = null; - // we don't want to fail the tool if telemetry fails. - Debug.Fail(e.ToString()); - } - } - protected virtual void DoTrackEvent( string eventName, IDictionary properties = null, diff --git a/src/Microsoft.DotNet.Interactive/KernelInvocationContext.cs b/src/Microsoft.DotNet.Interactive/KernelInvocationContext.cs index 192a5d3712..6e28d7fa2e 100644 --- a/src/Microsoft.DotNet.Interactive/KernelInvocationContext.cs +++ b/src/Microsoft.DotNet.Interactive/KernelInvocationContext.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; diff --git a/src/dotnet-interactive.Tests/FakeTelemetrySender.cs b/src/dotnet-interactive.Tests/FakeTelemetrySender.cs index 1af7b8333b..9d2c8207a5 100644 --- a/src/dotnet-interactive.Tests/FakeTelemetrySender.cs +++ b/src/dotnet-interactive.Tests/FakeTelemetrySender.cs @@ -32,7 +32,7 @@ public FakeTelemetrySender(IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentine private static string GetInstrumentationKey() => _configuredConnectionString ?? - $@"InstrumentationKey={Guid.NewGuid()};IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/"; + $"InstrumentationKey={Guid.NewGuid()};IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/"; protected override void DoTrackEvent( string eventName, diff --git a/src/dotnet-interactive.Tests/MagicCommandTests.about.cs b/src/dotnet-interactive.Tests/MagicCommandTests.about.cs index ed4e25c2b0..b81675ea15 100644 --- a/src/dotnet-interactive.Tests/MagicCommandTests.about.cs +++ b/src/dotnet-interactive.Tests/MagicCommandTests.about.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.DotNet.Interactive.Commands; @@ -24,11 +22,10 @@ public async Task it_shows_the_product_name_and_version_information() { using var kernel = new CompositeKernel() .UseAboutMagicCommand(); - + var result = await kernel.SendAsync(new SubmitCode("#!about")); - result.Events - .Should() + result.Events.Should() .ContainSingle() .Which .FormattedValues diff --git a/src/dotnet-interactive.Tests/RuntimeTelemetryTests.cs b/src/dotnet-interactive.Tests/RuntimeTelemetryTests.cs index 85c4f46378..4b6b6055bc 100644 --- a/src/dotnet-interactive.Tests/RuntimeTelemetryTests.cs +++ b/src/dotnet-interactive.Tests/RuntimeTelemetryTests.cs @@ -29,7 +29,8 @@ public RuntimeTelemetryTests() { new CSharpKernel().UseNugetDirective(false) } - .UseTelemetrySender(_telemetrySender); + .UseTelemetrySender(_telemetrySender) + .UseNuGetExtensions(_telemetrySender); } public void Dispose() => _kernel.Dispose(); @@ -164,4 +165,17 @@ public async Task Package_and_version_number_are_sent_on_successful_package_load new KeyValuePair("PackageName", "nodatime".ToSha256HashWithNormalizedCasing()), new KeyValuePair("PackageVersion", "3.1.9".ToSha256Hash())); } + + [Fact(Skip = "Package version with extension telemetry needs to be published.")] + public async Task Extensions_can_send_telemetry_using_PocketLogger() + { + var results = await _kernel.SendAsync(new SubmitCode(""" + #i "nuget:c:\temp\packages" + #r "nuget:Microsoft.DotNet.Interactive.AIUtilities,*-*" + """)); + + results.Events.Should().NotContainErrors(); + + _telemetrySender.TelemetryEvents.Should().Contain(e => e.EventName == "Microsoft.DotNet.Interactive.AIUtilities.Load"); + } } \ No newline at end of file diff --git a/src/dotnet-interactive/CommandLine/CommandLineParser.cs b/src/dotnet-interactive/CommandLine/CommandLineParser.cs index 48fd2a3a9a..6716eb536d 100644 --- a/src/dotnet-interactive/CommandLine/CommandLineParser.cs +++ b/src/dotnet-interactive/CommandLine/CommandLineParser.cs @@ -136,7 +136,7 @@ public static Parser Create( rootCommand.AddCommand(StdIO()); rootCommand.AddCommand(NotebookParser()); - var filter = new StartupTelemetryEventBuilder(Sha256Hasher.ToSha256HashWithNormalizedCasing); + var eventBuilder = new StartupTelemetryEventBuilder(Sha256Hasher.ToSha256HashWithNormalizedCasing); return new CommandLineBuilder(rootCommand) .UseDefaults() @@ -145,7 +145,7 @@ public static Parser Create( { if (context.ParseResult.Errors.Count == 0) { - telemetrySender.TrackStartupEvent(context.ParseResult, filter); + telemetrySender.TrackStartupEvent(context.ParseResult, eventBuilder); } // If sentinel does not exist, print the welcome message showing the telemetry notification. @@ -505,7 +505,7 @@ private static CompositeKernel CreateKernel( .UseDefaultMagicCommands() .UseAboutMagicCommand() .UseImportMagicCommand() - .UseNuGetExtensions(); + .UseNuGetExtensions(telemetrySender); kernel.AddKernelConnector(new ConnectNamedPipeCommand()); kernel.AddKernelConnector(new ConnectSignalRCommand()); diff --git a/src/dotnet-interactive/KernelExtensionLoader.cs b/src/dotnet-interactive/KernelExtensionLoader.cs index 01e27d7b8e..3cb25dc6eb 100644 --- a/src/dotnet-interactive/KernelExtensionLoader.cs +++ b/src/dotnet-interactive/KernelExtensionLoader.cs @@ -2,17 +2,23 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Reactive.Linq; +using System.Runtime.Loader; using System.Threading.Tasks; using Microsoft.DotNet.Interactive.Events; +using Microsoft.DotNet.Interactive.Telemetry; +using Pocket; namespace Microsoft.DotNet.Interactive.App; public static class KernelExtensionLoader { - public static CompositeKernel UseNuGetExtensions(this CompositeKernel kernel) + public static CompositeKernel UseNuGetExtensions( + this CompositeKernel kernel, + TelemetrySender telemetrySender = null) { var packagesToCheckForExtensions = new ConcurrentQueue(); @@ -33,7 +39,46 @@ public static CompositeKernel UseNuGetExtensions(this CompositeKernel kernel) if (extensionDir.Exists) { - await LoadExtensionsFromDirectoryAsync(kernel, extensionDir, context); + if (telemetrySender is not null && + packageAdded.PackageReference is { } resolved) + { + if (resolved.AssemblyPaths.Count == 1) + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(resolved.AssemblyPaths[0]); + + var logSubscription = LogEvents.Subscribe( + e => + { + var eventName = resolved.PackageName + "." + e.OperationName; + + Dictionary properties = null; + + if (e.Properties is { } props) + { + properties = new(); + + foreach (var tuple in props) + { + properties.TryAdd(tuple.Name, tuple.Value?.ToString()); + } + } + + telemetrySender.TrackEvent( + eventName, + properties + ); + }, + new[] { assembly }); + + kernel.RegisterForDisposal(logSubscription); + } + } + + await LoadExtensionsFromDirectoryAsync( + kernel, + extensionDir, + context, + telemetrySender); } } }); @@ -48,9 +93,13 @@ public static CompositeKernel UseNuGetExtensions(this CompositeKernel kernel) return kernel; } - public static async Task LoadExtensionsFromDirectoryAsync(this CompositeKernel kernel, DirectoryInfo extensionDir, KernelInvocationContext context) + public static async Task LoadExtensionsFromDirectoryAsync( + this CompositeKernel kernel, + DirectoryInfo extensionDir, + KernelInvocationContext context, + TelemetrySender telemetrySender = null) { - await new PackageDirectoryExtensionLoader().LoadFromDirectoryAsync( + await new PackageDirectoryExtensionLoader(telemetrySender).LoadFromDirectoryAsync( extensionDir, kernel, context); diff --git a/src/dotnet-interactive/KernelExtensions.cs b/src/dotnet-interactive/KernelExtensions.cs index 1fb8ce406d..ca681d5ee3 100644 --- a/src/dotnet-interactive/KernelExtensions.cs +++ b/src/dotnet-interactive/KernelExtensions.cs @@ -47,8 +47,8 @@ public static FSharpKernel UseNugetDirective(this FSharpKernel kernel, bool forc var packageRoots = resolvedPackageReference .Select(r => r.PackageRoot); - k.AddAssemblyReferencesAndPackageRoots(resolvedAssemblies, packageRoots); + return Task.CompletedTask; }, forceRestore); @@ -58,7 +58,6 @@ public static FSharpKernel UseNugetDirective(this FSharpKernel kernel, bool forc public static T UseAboutMagicCommand(this T kernel) where T : Kernel { - var about = new Command("#!about", LocalizationResources.Magics_about_Description()) { Handler = CommandHandler.Create((InvocationContext ctx) => @@ -122,7 +121,8 @@ public static CompositeKernel UseTelemetrySender( kernel.AddMiddleware(async (command, context, next) => { await next(command, context); - if (command is SubmitCode submitCode) + + if (command is SubmitCode) { var properties = GetStandardPropertiesFromCommand(command); @@ -130,7 +130,6 @@ public static CompositeKernel UseTelemetrySender( "CodeSubmitted", properties: properties); } - }); kernel.RegisterForDisposal(subscription); @@ -181,12 +180,12 @@ Dictionary GetStandardPropertiesFromCommand(KernelCommand kernel return properties; } - Dictionary GetStandardMeasurementsFromEvent(KernelEvent event1) + Dictionary GetStandardMeasurementsFromEvent(KernelEvent @event) { return new Dictionary { ["ExecutionOrder"] = ++executionOrder, - ["Succeeded"] = event1 is CommandSucceeded ? 1 : 0 + ["Succeeded"] = @event is CommandSucceeded ? 1 : 0 }; } } diff --git a/src/dotnet-interactive/PackageDirectoryExtensionLoader.cs b/src/dotnet-interactive/PackageDirectoryExtensionLoader.cs index a828b94c70..7531dea0cf 100644 --- a/src/dotnet-interactive/PackageDirectoryExtensionLoader.cs +++ b/src/dotnet-interactive/PackageDirectoryExtensionLoader.cs @@ -9,6 +9,7 @@ using System.Runtime.Loader; using System.Threading.Tasks; using Microsoft.DotNet.Interactive.Events; +using Microsoft.DotNet.Interactive.Telemetry; using Pocket; using static Pocket.Logger; @@ -16,11 +17,17 @@ namespace Microsoft.DotNet.Interactive.App; internal class PackageDirectoryExtensionLoader { + private readonly TelemetrySender _telemetrySender; private const string ExtensionScriptName = "extension.dib"; private readonly HashSet _loadedAssemblies = new(); private readonly object _lock = new(); + public PackageDirectoryExtensionLoader(TelemetrySender telemetrySender = null) + { + _telemetrySender = telemetrySender; + } + public async Task LoadFromDirectoryAsync( DirectoryInfo directory, Kernel kernel,