Initial checkin of Linux Log Parsers - Dmesg, cloud-init, WaLinuxAgent

This commit is contained in:
ivberg 2020-12-10 13:25:42 -08:00
Родитель 5b8fb05104
Коммит 9246631ded
36 изменённых файлов: 2093 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
namespace LinuxLogParser.CloudInitLog
{
public enum LogParsedDataKey
{
GeneralLog
}
public class CloudInitLogParsedEntry : IKeyedDataType<LogParsedDataKey>
{
private readonly LogParsedDataKey key;
public CloudInitLogParsedEntry(LogParsedDataKey key)
{
this.key = key;
}
public int CompareTo(LogParsedDataKey other)
{
return key.CompareTo(other);
}
public LogParsedDataKey GetKey()
{
return key;
}
}
public class LogEntry: CloudInitLogParsedEntry
{
public string FilePath { get; private set; }
public ulong LineNumber { get; private set; }
public Timestamp EventTimestamp { get; private set; }
public string PythonFile { get; private set; }
public string LogLevel { get; private set; }
public string Log { get; private set; }
public LogEntry(string filePath, ulong lineNumber, Timestamp eventTimestamp, string pythonFile,
string logLevel, string log):
base(LogParsedDataKey.GeneralLog)
{
FilePath = filePath;
LineNumber = lineNumber;
EventTimestamp = eventTimestamp;
PythonFile = pythonFile;
LogLevel = logLevel;
Log = log;
}
}
}

Просмотреть файл

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParserCore;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
namespace LinuxLogParser.CloudInitLog
{
public class CloudInitLogParser : LogParserBase<CloudInitLogParsedEntry, LogParsedDataKey>
{
public override string Id => SourceParserIds.CloudInitLog;
public override DataSourceInfo DataSourceInfo => dataSourceInfo;
private static Regex CloudInitRegex =
new Regex(@"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (.*)\[(\w+)\]: (.*)$",
RegexOptions.Compiled);
private DataSourceInfo dataSourceInfo;
public CloudInitLogParser(string[] filePaths) : base(filePaths)
{
}
public override void ProcessSource(
ISourceDataProcessor<CloudInitLogParsedEntry, LogContext, LogParsedDataKey> dataProcessor,
ILogger logger, IProgress<int> progress, CancellationToken cancellationToken)
{
if (FilePaths.Length == 0)
{
return;
}
Timestamp startTime = Timestamp.MaxValue;
Timestamp endTime = Timestamp.MinValue;
DateTime fileStartTime = default(DateTime);
long startNanoSeconds = 0;
foreach (var path in FilePaths)
{
ulong lineNumber = 0;
string line;
var file = new StreamReader(path);
var logEntries = new List<LogEntry>();
while ((line = file.ReadLine()) != null)
{
lineNumber++;
var cloudInitMatch = CloudInitRegex.Match(line);
if (cloudInitMatch.Success)
{
DateTime time;
if (!DateTime.TryParse(cloudInitMatch.Groups[1].Value.Replace(",", "."), out time))
{
throw new InvalidOperationException("Time cannot be parsed to DateTime format");
}
var utcTime = DateTime.FromFileTimeUtc(time.ToFileTimeUtc()); // Need to explicitly say log time is in UTC, otherwise it will be interpreted as local
var timeStamp = Timestamp.FromNanoseconds(utcTime.Ticks * 100);
if (timeStamp < startTime)
{
startTime = timeStamp;
fileStartTime = utcTime;
startNanoSeconds = startTime.ToNanoseconds;
}
if (timeStamp > endTime)
{
endTime = timeStamp;
}
var offsetEventTimestamp = new Timestamp(timeStamp.ToNanoseconds - startNanoSeconds);
LogEntry entry = new LogEntry(path, lineNumber, offsetEventTimestamp, cloudInitMatch.Groups[2].Value,
cloudInitMatch.Groups[3].Value, cloudInitMatch.Groups[4].Value);
dataProcessor.ProcessDataElement(entry, Context, cancellationToken);
}
else
{
Debugger.Break();
}
}
var offsetEndTimestamp = new Timestamp(endTime.ToNanoseconds - startNanoSeconds);
this.dataSourceInfo = new DataSourceInfo(0, offsetEndTimestamp.ToNanoseconds, fileStartTime);
Context.UpdateFileMetadata(path, new FileMetadata(lineNumber));
}
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
namespace LinuxLogParser.DmesgIsoLog
{
public enum LogParsedDataKey
{
GeneralLog
}
public class DmesgIsoLogParsedEntry : IKeyedDataType<LogParsedDataKey>
{
private readonly LogParsedDataKey key;
public DmesgIsoLogParsedEntry(LogParsedDataKey key)
{
this.key = key;
}
public int CompareTo(LogParsedDataKey other)
{
return key.CompareTo(other);
}
public LogParsedDataKey GetKey()
{
return key;
}
}
public class LogEntry: DmesgIsoLogParsedEntry
{
public Timestamp timestamp;
public string filePath;
public ulong lineNumber;
public string entity;
public string topic;
public string message;
public string metadata;
public string rawLog;
public LogEntry(): base(LogParsedDataKey.GeneralLog)
{
}
}
}

Просмотреть файл

@ -0,0 +1,242 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParserCore;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
namespace LinuxLogParser.DmesgIsoLog
{
public class DmesgIsoLogParser : LogParserBase<DmesgIsoLogParsedEntry, LogParsedDataKey>
{
public override string Id => SourceParserIds.DmesgIsoLog;
public override DataSourceInfo DataSourceInfo => dataSourceInfo;
private DataSourceInfo dataSourceInfo;
public DmesgIsoLogParser(string[] filePaths) : base(filePaths)
{
}
private bool IsNumberFormat(string s)
{
foreach (var c in s)
{
if (!Char.IsNumber(c) && c != '.')
{
return false;
}
}
return true;
}
private bool IsPCIinfo(string s)
{
return s.Length == 6 &&
s[0] == ' ' &&
s[1] == '[' &&
Char.IsNumber(s[2]) &&
Char.IsNumber(s[3]) &&
Char.IsNumber(s[4]) &&
Char.IsNumber(s[5]);
}
public override void ProcessSource(
ISourceDataProcessor<DmesgIsoLogParsedEntry, LogContext, LogParsedDataKey> dataProcessor,
ILogger logger, IProgress<int> progress, CancellationToken cancellationToken)
{
var contentDictionary = new Dictionary<string, IReadOnlyList<LogEntry>>();
string[] lineContent;
string[] firstSlice;
StringBuilder builder = new StringBuilder();
Timestamp oldestTimestamp = new Timestamp(long.MaxValue);
Timestamp newestTImestamp = new Timestamp(long.MinValue);
long startNanoSeconds = 0;
DateTime fileStartTime = default(DateTime);
DateTime parsedTime = default(DateTime);
var dateTimeCultureInfo = new CultureInfo("en-US");
foreach (var path in FilePaths)
{
ulong currentLineNumber = 1;
var file = new System.IO.StreamReader(path);
string line = string.Empty;
var entriesList = new List<LogEntry>();
LogEntry lastEntry = null;
while ((line = file.ReadLine()) != null)
{
//First, we check if the line is a new log entry by trying to parse its timestamp
if (line.Length >= 31 && DateTime.TryParseExact(line.Substring(0, 31), "yyyy-MM-ddTHH:mm:ss,ffffffK", dateTimeCultureInfo, DateTimeStyles.None, out parsedTime))
{
if (lastEntry != null)
{
dataProcessor.ProcessDataElement(lastEntry, Context, cancellationToken);
}
lastEntry = new LogEntry();
lastEntry.filePath = path;
lastEntry.lineNumber = currentLineNumber;
lastEntry.rawLog = line.Substring(32);
lineContent = lastEntry.rawLog.Split(':');
if (lineContent.Length == 1 || lineContent[0].Length > 20)
{
// Character ':' is not present in the beginning of the message, therefore there is no entity nor topic nor metadata
lastEntry.entity = string.Empty;
lastEntry.metadata = string.Empty;
lastEntry.topic = string.Empty;
lastEntry.message = lineContent[0];
}
else if (lineContent.Length == 2)
{
// Character ':' occurs once in the message and in the beginning, therefore there is entity but no topic nor metadata
lastEntry.entity = lineContent[0];
lastEntry.metadata = string.Empty;
lastEntry.topic = string.Empty;
lastEntry.message = lineContent[1];
}
else
{
// Character ':' occurs multiple times in the message, and at least once in the beginning
// We proceed to try to infer entity, metadata and topic
firstSlice = lineContent[0].Split(' ');
var lastSubstring = firstSlice[firstSlice.Length - 1];
int contentIndex = 2;
if (firstSlice.Length > 1 && this.IsNumberFormat(lastSubstring) && this.IsNumberFormat(lineContent[1]))
{
//There is metadata and entity
builder.Clear();
builder.Append(lastSubstring);
builder.Append(':');
builder.Append(lineContent[1]);
while (contentIndex < lineContent.Length && this.IsNumberFormat(lineContent[contentIndex]))
{
builder.Append(':');
builder.Append(lineContent[contentIndex]);
++contentIndex;
}
lastEntry.metadata = builder.ToString();
lastEntry.entity = lineContent[0].Substring(0, lineContent[0].Length - lastSubstring.Length - 1);
if ((contentIndex < lineContent.Length - 1) && !IsPCIinfo(lineContent[contentIndex]))
{
//We check for topic after the metadata
lastEntry.topic = lineContent[contentIndex];
++contentIndex;
}
}
else if (this.IsNumberFormat(lineContent[0]))
{
//There is metadata but no entity
builder.Clear();
builder.Append(lineContent[0]);
contentIndex = 1;
while (contentIndex < lineContent.Length && this.IsNumberFormat(lineContent[contentIndex]))
{
builder.Append(':');
builder.Append(lineContent[contentIndex]);
++contentIndex;
}
lastEntry.metadata = builder.ToString();
lastEntry.entity = string.Empty;
lastEntry.topic = string.Empty;
}
else
{
//There is entity but no metadata
if (lineContent[1].StartsWith(" type="))
{
//We check for topic after the entity
lastEntry.topic = string.Empty;
contentIndex = 1;
}
else
{
lastEntry.topic = lineContent[1];
}
lastEntry.entity = lineContent[0];
lastEntry.metadata = string.Empty;
}
builder.Clear();
//Remainder of the log is the message
if (contentIndex < lineContent.Length)
{
builder.Append(lineContent[contentIndex]);
++contentIndex;
}
while (contentIndex < lineContent.Length)
{
builder.Append(':');
builder.Append(lineContent[contentIndex]);
++contentIndex;
}
lastEntry.message = builder.ToString();
}
parsedTime = DateTime.FromFileTimeUtc(parsedTime.ToFileTimeUtc()); // Need to explicitly say log time is in UTC, otherwise it will be interpreted as local
var timeStamp = Timestamp.FromNanoseconds(parsedTime.Ticks * 100);
if (timeStamp < oldestTimestamp)
{
oldestTimestamp = timeStamp;
fileStartTime = parsedTime;
startNanoSeconds = oldestTimestamp.ToNanoseconds;
}
if (timeStamp > newestTImestamp)
{
newestTImestamp = timeStamp;
}
lastEntry.timestamp = new Timestamp(timeStamp.ToNanoseconds - startNanoSeconds);
entriesList.Add(lastEntry);
}
else if (entriesList.Count > 0)
{
if (lastEntry == null)
{
throw new InvalidOperationException("Can't parse the log.");
}
lastEntry.message = lastEntry.message + "\n" + line;
lastEntry.rawLog = lastEntry.rawLog + "\n" + line;
}
++currentLineNumber;
}
if (lastEntry != null)
{
dataProcessor.ProcessDataElement(lastEntry, Context, cancellationToken);
}
contentDictionary[path] = entriesList.AsReadOnly();
file.Close();
--currentLineNumber;
Context.UpdateFileMetadata(path, new FileMetadata(currentLineNumber));
}
var offsetEndTimestamp = new Timestamp(newestTImestamp.ToNanoseconds - startNanoSeconds);
dataSourceInfo = new DataSourceInfo(0, offsetEndTimestamp.ToNanoseconds, fileStartTime);
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Performance.SDK" Version="0.108.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LinuxLogParserCore\LinuxLogParserCore.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace LinuxLogParser
{
public static class SourceParserIds
{
public const string CloudInitLog = "CloudInitLog";
public const string WaLinuxAgentLog = "WaLinuxAgentLog";
public const string DmesgIsoLog = "DmesgIsoLog";
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
namespace LinuxLogParser.WaLinuxAgentLog
{
public enum LogParsedDataKey
{
GeneralLog
}
public class WaLinuxAgentLogParsedEntry : IKeyedDataType<LogParsedDataKey>
{
private readonly LogParsedDataKey key;
public WaLinuxAgentLogParsedEntry(LogParsedDataKey key)
{
this.key = key;
}
public int CompareTo(LogParsedDataKey other)
{
return key.CompareTo(other);
}
public LogParsedDataKey GetKey()
{
return key;
}
}
public class LogEntry: WaLinuxAgentLogParsedEntry
{
public string FilePath { get; private set; }
public ulong LineNumber { get; private set; }
public Timestamp EventTimestamp { get; private set; }
public string LogLevel { get; private set; }
public string Log { get; set; }
public LogEntry(string filePath, ulong lineNumber, Timestamp eventTimestamp, string logLevel, string log):
base(LogParsedDataKey.GeneralLog)
{
FilePath = filePath;
LineNumber = lineNumber;
EventTimestamp = eventTimestamp;
LogLevel = logLevel;
Log = log;
}
}
}

Просмотреть файл

@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParserCore;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
namespace LinuxLogParser.WaLinuxAgentLog
{
public class WaLinuxAgentLogParser : LogParserBase<WaLinuxAgentLogParsedEntry, LogParsedDataKey>
{
public override string Id => SourceParserIds.WaLinuxAgentLog;
public override DataSourceInfo DataSourceInfo => dataSourceInfo;
private static Regex WaLinuxAgentRegex =
new Regex(@"^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) (\w+) (.*)$",
RegexOptions.Compiled);
private DataSourceInfo dataSourceInfo;
public WaLinuxAgentLogParser(string[] filePaths) : base(filePaths)
{
}
public override void ProcessSource(
ISourceDataProcessor<WaLinuxAgentLogParsedEntry, LogContext, LogParsedDataKey> dataProcessor,
ILogger logger, IProgress<int> progress, CancellationToken cancellationToken)
{
if (FilePaths.Length == 0)
{
return;
}
Timestamp startTime = Timestamp.MaxValue;
Timestamp endTime = Timestamp.MinValue;
DateTime fileStartTime = default(DateTime);
long startNanoSeconds = 0;
foreach (var path in FilePaths)
{
ulong lineNumber = 0;
string line;
var file = new StreamReader(path);
var logEntries = new List<LogEntry>();
LogEntry lastLogEntry = null;
while ((line = file.ReadLine()) != null)
{
lineNumber++;
var WaLinuxAgentMatch = WaLinuxAgentRegex.Match(line);
if (!WaLinuxAgentMatch.Success)
{
// Continuation of the last line.
if (lastLogEntry == null)
{
throw new InvalidOperationException("Can't find the timestamp of the log.");
}
lastLogEntry.Log += '\n' + line;
continue;
}
// We successfully match the format of the log, which means the last log entry is completed.
if (lastLogEntry != null)
{
dataProcessor.ProcessDataElement(lastLogEntry, Context, cancellationToken);
}
DateTime time;
if (!DateTime.TryParse(WaLinuxAgentMatch.Groups[1].Value, out time))
{
throw new InvalidOperationException("Time cannot be parsed to DateTime format");
}
var utcTime = DateTime.FromFileTimeUtc(time.ToFileTimeUtc()); // Need to explicitly say log time is in UTC, otherwise it will be interpreted as local
var timeStamp = Timestamp.FromNanoseconds(utcTime.Ticks * 100);
if (timeStamp < startTime)
{
startTime = timeStamp;
fileStartTime = utcTime;
startNanoSeconds = startTime.ToNanoseconds;
}
if (timeStamp > endTime)
{
endTime = timeStamp;
}
var offsetEventTimestamp = new Timestamp(timeStamp.ToNanoseconds - startNanoSeconds);
lastLogEntry = new LogEntry(path, lineNumber, offsetEventTimestamp,
WaLinuxAgentMatch.Groups[2].Value, WaLinuxAgentMatch.Groups[3].Value);
}
if (lastLogEntry != null)
{
dataProcessor.ProcessDataElement(lastLogEntry, Context, cancellationToken);
}
Context.UpdateFileMetadata(path, new FileMetadata(lineNumber));
}
var offsetEndTimestamp = new Timestamp(endTime.ToNanoseconds - startNanoSeconds);
this.dataSourceInfo = new DataSourceInfo(0, offsetEndTimestamp.ToNanoseconds, fileStartTime);
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace LinuxLogParserCore
{
public class FileMetadata
{
public ulong LineCount { get; private set; }
public FileMetadata(ulong lineCount)
{
LineCount = lineCount;
}
}
}

Просмотреть файл

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
namespace LinuxLogParserCore
{
public static class FileTimestampSort
{
public delegate bool TryExtractTimeUtcDelegate(string line, out DateTime time);
public static SortResult FilterAndSort(string[] filePaths, TryExtractTimeUtcDelegate tryExtractTimeUtc)
{
int count = filePaths.Length;
if (count == 0)
{
return null;
}
DateTime fileStartTimeUtc = DateTime.MaxValue;
List<Tuple<string, DateTime>> fileTimeTuples = new List<Tuple<string, DateTime>>();
for (int index = 0; index < count; index++)
{
string filePath = filePaths[index];
DateTime utcTime;
if (!tryExtractTimeUtc(filePath, out utcTime))
{
// Unable to extract a timestamp in this entire file. Filter it.
continue;
}
if (utcTime < fileStartTimeUtc)
{
fileStartTimeUtc = utcTime;
}
fileTimeTuples.Add(Tuple.Create(filePath, utcTime));
}
int filteredCount = fileTimeTuples.Count;
if (filteredCount == 0)
{
// Unable to extract timestamp from all files.
return null;
}
fileTimeTuples.Sort((tuple1, tuple2) => tuple1.Item2.CompareTo(tuple2.Item2));
string[] filePathsSorted = new string[filteredCount];
for (int index = 0; index < filteredCount; index++)
{
filePathsSorted[index] = fileTimeTuples[index].Item1;
}
return new SortResult(filePathsSorted, fileStartTimeUtc);
}
public class SortResult
{
public string[] FilePathsSorted { get; }
public DateTime FileStartTimeUtc { get; }
public SortResult(string[] filePathsSorted, DateTime fileStartTimeUtc)
{
FilePathsSorted = filePathsSorted;
FileStartTimeUtc = fileStartTimeUtc;
}
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Performance.SDK" Version="0.108.2" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
namespace LinuxLogParserCore
{
public class LogContext
{
public Dictionary<string, FileMetadata> FileToMetadata { get; } = new Dictionary<string, FileMetadata>();
public void UpdateFileMetadata(string filePath, FileMetadata metadata)
{
FileToMetadata[filePath] = metadata;
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
namespace LinuxLogParserCore
{
public abstract class LogParserBase<TLogEntry, TKey>: SourceParserBase<TLogEntry, LogContext, TKey>
where TLogEntry: IKeyedDataType<TKey>
{
public LogParserBase(string[] filePaths)
{
FilePaths = filePaths;
}
protected LogContext Context { get; private set; } = new LogContext();
protected string[] FilePaths { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>Cloud-InitMPTAddin</AssemblyName>
<RootNamespace>Cloud-InitMPTAddin</RootNamespace>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Performance.SDK" Version="0.108.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LinuxLogParser\LinuxLogParser.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="DeployStagedFiles">
<Exec Command="CALL XCOPY /dickherfy $(SolutionDir)Linux\WaLinuxAgent\$(OutDir) $(ProjectDir)$(OutDir)WaLinuxAgent" />
<Exec Command="CALL XCOPY /dickherfy &quot;$(SolutionDir)Linux\DmesgIsoLog\$(OutDir)\Dmesg $(ProjectDir)$(OutDir)Dmesg" />
<Exec Command="CALL XCOPY /dickherfy $(SolutionDir)\..\..\LTTNG.CTF\LttngDataExtensions\$(OutDir) $(ProjectDir)$(OutDir)LttngDataExtensions" />
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using LinuxLogParser.CloudInitLog;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using LinuxLogParserCore;
namespace CloudInitMPTAddin
{
public sealed class CloudInitCustomDataProcessor
: CustomDataProcessorBaseWithSourceParser<CloudInitLogParsedEntry, LogContext, LogParsedDataKey>
{
public CloudInitCustomDataProcessor(
ISourceParser<CloudInitLogParsedEntry, LogContext, LogParsedDataKey> sourceParser,
ProcessorOptions options,
IApplicationEnvironment applicationEnvironment,
IProcessorEnvironment processorEnvironment,
IReadOnlyDictionary<TableDescriptor, Action<ITableBuilder, IDataExtensionRetrieval>> allTablesMapping,
IEnumerable<TableDescriptor> metadataTables)
: base(sourceParser, options, applicationEnvironment, processorEnvironment, allTablesMapping, metadataTables)
{
}
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser.CloudInitLog;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CloudInitMPTAddin
{
//
// This is a sample Custom Data Source (CDS) that understands files with the .txt extension
//
// In order for a CDS to be recognized, it MUST satisfy the following:
// a) Be a public type
// b) Have a public parameterless constructor
// c) Implement the ICustomDataSource interface
// d) Be decorated with the CustomDataSourceAttribute attribute
// e) Be decorated with at least one of the derivatives of the DataSourceAttribute attribute
//
[CustomDataSource(
"{a7752cda-d80f-49f6-8022-2129ab041cd2}", // The GUID must be unique for your Custom Data Source. You can use Visual Studio's Tools -> Create Guid… tool to create a new GUID
"Cloud-Init", // The Custom Data Source MUST have a name
@"Linux Cloud-Init log parser")] // The Custom Data Source MUST have a description
[FileDataSource(
".log", // A file extension is REQUIRED
"Linux Cloud-Init Cloud Provisioning Log")] // A description is OPTIONAL. The description is what appears in the file open menu to help users understand what the file type actually is.
//
// There are two methods to creating a Custom Data Source that is recognized by the SDK:
// 1. Using the helper abstract base classes
// 2. Implementing the raw interfaces
// This sample demonstrates method 1 where the CustomDataSourceBase abstract class
// helps provide a public parameterless constructor and implement the ICustomDataSource interface
//
public class CloudInitCustomDataSource
: CustomDataSourceBase
{
private IApplicationEnvironment applicationEnvironment;
protected override void SetApplicationEnvironmentCore(IApplicationEnvironment applicationEnvironment)
{
//
// Saves the given application environment into this instance
//
this.applicationEnvironment = applicationEnvironment;
}
protected override bool IsFileSupportedCore(string path)
{
return StringComparer.OrdinalIgnoreCase.Equals(
"cloud-init.log",
Path.GetFileName(path));
}
protected override ICustomDataProcessor CreateProcessorCore(
IEnumerable<IDataSource> dataSources,
IProcessorEnvironment processorEnvironment,
ProcessorOptions options)
{
string[] filePaths = dataSources.Select(x => x.GetUri().LocalPath).ToArray();
var sourceParser = new CloudInitLogParser(filePaths);
return new CloudInitCustomDataProcessor(
sourceParser,
options,
this.applicationEnvironment,
processorEnvironment,
this.AllTables,
this.MetadataTables);
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using LinuxLogParser.CloudInitLog;
using LinuxLogParserCore;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using System.Collections.Generic;
using System.Threading;
namespace CloudInitMPTAddin
{
public class CloudInitDataCooker :
CookedDataReflector,
ISourceDataCooker<CloudInitLogParsedEntry, LogContext, LogParsedDataKey>
{
public const string CookerId = "CloudInitCooker";
public ReadOnlyHashSet<LogParsedDataKey> DataKeys => new ReadOnlyHashSet<LogParsedDataKey>(
new HashSet<LogParsedDataKey>(
new[] {
LogParsedDataKey.GeneralLog
}
)
);
public SourceDataCookerOptions Options => SourceDataCookerOptions.None;
public string DataCookerId => Path.CookerPath;
public string Description => "Parsed information of CloudInit log.";
public string SourceParserId => Path.SourceParserId;
public DataCookerPath Path { get; }
public IReadOnlyDictionary<DataCookerPath, DataCookerDependencyType> DependencyTypes =>
new Dictionary<DataCookerPath, DataCookerDependencyType>();
public IReadOnlyCollection<DataCookerPath> RequiredDataCookers => new HashSet<DataCookerPath>();
public DataProductionStrategy DataProductionStrategy => DataProductionStrategy.PostSourceParsing;
[DataOutput]
public CloudInitLogParsedResult ParsedResult { get; private set; }
private List<LogEntry> logEntries;
private LogContext context;
public CloudInitDataCooker() : this(new DataCookerPath(SourceParserIds.CloudInitLog, CookerId))
{
}
private CloudInitDataCooker(DataCookerPath dataCookerPath) : base(dataCookerPath)
{
Path = dataCookerPath;
}
public void BeginDataCooking(ICookedDataRetrieval dependencyRetrieval, CancellationToken cancellationToken)
{
logEntries = new List<LogEntry>();
}
public DataProcessingResult CookDataElement(CloudInitLogParsedEntry data, LogContext context,
CancellationToken cancellationToken)
{
DataProcessingResult result = DataProcessingResult.Processed;
if (data is LogEntry logEntry)
{
logEntries.Add(logEntry);
this.context = context;
}
else
{
result = DataProcessingResult.Ignored;
}
return result;
}
public void EndDataCooking(CancellationToken cancellationToken)
{
ParsedResult = new CloudInitLogParsedResult(logEntries, context.FileToMetadata);
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using LinuxLogParser.CloudInitLog;
using LinuxLogParserCore;
using System.Collections.Generic;
namespace CloudInitMPTAddin
{
public class CloudInitLogParsedResult
{
public List<LogEntry> LogEntries { get; }
public IReadOnlyDictionary<string, FileMetadata> FileToMetadata { get; }
public CloudInitLogParsedResult(List<LogEntry> logEntries, Dictionary<string, FileMetadata> fileToMetadata)
{
LogEntries = logEntries;
FileToMetadata = fileToMetadata;
}
}
}

Просмотреть файл

@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
namespace CloudInitMPTAddin.Tables
{
[Table]
public static class CloudInitTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{D12FF3CB-515D-406C-A000-7CA8C8ED2DAB}"),
"Cloud-Init",
"Cloud-Init Log",
category: "Linux",
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.CloudInitLog, CloudInitDataCooker.CookerId)
});
//
// Declare columns here. You can do this using the ColumnConfiguration class.
// It is possible to declaratively describe the table configuration as well. Please refer to our Advanced Topics Wiki page for more information.
//
// The Column metadata describes each column in the table.
// Each column must have a unique GUID and a unique name. The GUID must be unique globally; the name only unique within the table.
//
// The UIHints provides some hints to UI on how to render the column.
// In this sample, we are simply saying to allocate at least 80 units of width.
//
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{69BC9313-CFC9-4D12-BA2D-D520126E89A8}"), "FileName", "File Name"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LineNumberColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{82f4cceb-5044-4f92-bc34-00ab9d0d22cb}"), "LineNumber", "Log Line Number"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration EventTimestampColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{4ff97511-d34f-4fd9-bf3d-adba27869332}"), "Event Time", "The timestamp of the log entry"),
new UIHints { Width = 80, SortPriority = 0, SortOrder = SortOrder.Ascending });
// todo: needs to be changed by user manually to DateTime UTC format. SDK doesn't yet support specifying this <DateTimeTimestampOptionsParameter DateTimeEnabled="true" />
private static readonly ColumnConfiguration EventTimestampDateTimeColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{4ff97511-d34f-4fd9-bf3d-adba27869332}"), "Event Time", "The timestamp of the log entry"),
new UIHints { Width = 130 });
private static readonly ColumnConfiguration PythonFileColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{70dfb005-2aeb-492b-a693-38c841437864}"), "PythonFile", "Python file logged from"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LogLevelColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{59941146-702b-4042-8ef9-f8ad38a7a495}"), "LogLevel", "Log level"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LogColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{4facff7c-2e32-44d5-83db-9d3eaed43621}"), "Log", "Log Entry"),
new UIHints { Width = 1200 });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
CloudInitLogParsedResult parsedResult = tableData.QueryOutput<CloudInitLogParsedResult>(
DataOutputPath.Create(SourceParserIds.CloudInitLog, CloudInitDataCooker.CookerId, "ParsedResult"));
var logEntries = parsedResult.LogEntries;
var baseProjection = Projection.Index(logEntries);
var fileNameProjection = baseProjection.Compose(x => x.FilePath);
var lineNumberProjection = baseProjection.Compose(x => x.LineNumber);
var eventTimeProjection = baseProjection.Compose(x => x.EventTimestamp);
var pythonFileProjection = baseProjection.Compose(x => x.PythonFile);
var logLevelProjection = baseProjection.Compose(x => x.LogLevel);
var logProjection = baseProjection.Compose(x => x.Log);
var config = new TableConfiguration("Default")
{
Columns = new[]
{
FileNameColumn,
LogLevelColumn,
TableConfiguration.PivotColumn,
PythonFileColumn,
LineNumberColumn,
LogColumn,
EventTimestampDateTimeColumn,
TableConfiguration.GraphColumn,
EventTimestampColumn,
},
Layout = TableLayoutStyle.GraphAndTable,
};
config.AddColumnRole(ColumnRole.StartTime, EventTimestampColumn);
//
//
// Use the table builder to build the table.
// Add and set table configuration if applicable.
// Then set the row count (we have one row per file) and then add the columns using AddColumn.
//
tableBuilder
.AddTableConfiguration(config)
.SetDefaultTableConfiguration(config)
.SetRowCount(logEntries.Count)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(LineNumberColumn, lineNumberProjection)
.AddColumn(EventTimestampColumn, eventTimeProjection)
.AddColumn(PythonFileColumn, pythonFileProjection)
.AddColumn(LogLevelColumn, logLevelProjection)
.AddColumn(LogColumn, logProjection)
;
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CloudInitMPTAddin.Tables.Metadata
{
[Table]
public sealed class FileStatsMetadataTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{40AF86E5-0DF8-47B1-9A01-1D6C3529B75B}"),
"File Stats",
"Statistics for text files",
isMetadataTable: true,
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.CloudInitLog, CloudInitDataCooker.CookerId)
});
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{2604E009-F47D-4A22-AA4F-B148D1C26553}"), "File Name", "The name of the file."),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LineCountColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{C499AF57-64D1-47A9-8550-CF24D6C9615D}"), "Line Count", "The number of lines in the file."),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration WordCountColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{3669E90A-DC8F-4972-A5D3-3E13AFDF5DB7}"), "Word Count", "The number of words in the file."),
new UIHints { Width = 80 });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
CloudInitLogParsedResult parsedResult = tableData.QueryOutput<CloudInitLogParsedResult>(
DataOutputPath.Create(SourceParserIds.CloudInitLog, CloudInitDataCooker.CookerId, "ParsedResult"));
var fileNames = parsedResult.FileToMetadata.Keys.ToArray();
var fileNameProjection = Projection.Index(fileNames.AsReadOnly());
var lineCountProjection = fileNameProjection.Compose(
fileName => parsedResult.FileToMetadata[fileName].LineCount);
tableBuilder.SetRowCount(fileNames.Length)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(LineCountColumn, lineCountProjection);
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>DMesg</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Performance.SDK" Version="0.108.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LinuxLogParser\LinuxLogParser.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using LinuxLogParser.DmesgIsoLog;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using LinuxLogParserCore;
namespace DmesgIsoMPTAddin
{
public sealed class DmesgIsoCustomDataProcessor
: CustomDataProcessorBaseWithSourceParser<DmesgIsoLogParsedEntry, LogContext, LogParsedDataKey>
{
public DmesgIsoCustomDataProcessor(
ISourceParser<DmesgIsoLogParsedEntry, LogContext, LogParsedDataKey> sourceParser,
ProcessorOptions options,
IApplicationEnvironment applicationEnvironment,
IProcessorEnvironment processorEnvironment,
IReadOnlyDictionary<TableDescriptor, Action<ITableBuilder, IDataExtensionRetrieval>> allTablesMapping,
IEnumerable<TableDescriptor> metadataTables)
: base(sourceParser, options, applicationEnvironment, processorEnvironment, allTablesMapping, metadataTables)
{
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using LinuxLogParser.DmesgIsoLog;
using LinuxLogParserCore;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using System.Collections.Generic;
using System.Threading;
namespace DmesgIsoMPTAddin
{
public class DmesgIsoDataCooker :
CookedDataReflector,
ISourceDataCooker<DmesgIsoLogParsedEntry, LogContext, LogParsedDataKey>
{
public const string CookerId = "DmesgIsoCooker";
public ReadOnlyHashSet<LogParsedDataKey> DataKeys => new ReadOnlyHashSet<LogParsedDataKey>(
new HashSet<LogParsedDataKey>(
new[] {
LogParsedDataKey.GeneralLog
}
)
);
public SourceDataCookerOptions Options => SourceDataCookerOptions.None;
public string DataCookerId => Path.CookerPath;
public string Description => "Parsed information of Dmesg log.";
public string SourceParserId => Path.SourceParserId;
public DataCookerPath Path { get; }
public IReadOnlyDictionary<DataCookerPath, DataCookerDependencyType> DependencyTypes =>
new Dictionary<DataCookerPath, DataCookerDependencyType>();
public IReadOnlyCollection<DataCookerPath> RequiredDataCookers => new HashSet<DataCookerPath>();
public DataProductionStrategy DataProductionStrategy => DataProductionStrategy.PostSourceParsing;
[DataOutput]
public DmesgIsoLogParsedResult ParsedResult { get; private set; }
private List<LogEntry> logEntries;
private LogContext context;
public DmesgIsoDataCooker() : this(new DataCookerPath(SourceParserIds.DmesgIsoLog, CookerId))
{
}
private DmesgIsoDataCooker(DataCookerPath dataCookerPath) : base(dataCookerPath)
{
Path = dataCookerPath;
}
public void BeginDataCooking(ICookedDataRetrieval dependencyRetrieval, CancellationToken cancellationToken)
{
logEntries = new List<LogEntry>();
}
public DataProcessingResult CookDataElement(DmesgIsoLogParsedEntry data, LogContext context,
CancellationToken cancellationToken)
{
DataProcessingResult result = DataProcessingResult.Processed;
if (data is LogEntry logEntry)
{
logEntries.Add(logEntry);
this.context = context;
}
else
{
result = DataProcessingResult.Ignored;
}
return result;
}
public void EndDataCooking(CancellationToken cancellationToken)
{
ParsedResult = new DmesgIsoLogParsedResult(logEntries, context.FileToMetadata);
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using LinuxLogParser.DmesgIsoLog;
using Microsoft.Performance.SDK.Processing;
namespace DmesgIsoMPTAddin
{
[CustomDataSource(
"{D1440EE2-DD94-4141-953A-82131BA3C91D}",
"DmesgIsoLog",
"A data source to parse dmesg.iso.log file")]
[FileDataSource(
"log",
"Log files")]
public class DmesgIsoDataSource : CustomDataSourceBase
{
private IApplicationEnvironment applicationEnvironment;
protected override ICustomDataProcessor CreateProcessorCore(IEnumerable<IDataSource> dataSources, IProcessorEnvironment processorEnvironment, ProcessorOptions options)
{
string[] filePaths = dataSources.Select(x => x.GetUri().LocalPath).ToArray();
var sourceParser = new DmesgIsoLogParser(filePaths);
return new DmesgIsoCustomDataProcessor(
sourceParser,
options,
this.applicationEnvironment,
processorEnvironment,
this.AllTables,
this.MetadataTables);
}
protected override bool IsFileSupportedCore(string path)
{
return path.ToLower().EndsWith("dmesg.iso.log");
}
protected override void SetApplicationEnvironmentCore(IApplicationEnvironment applicationEnvironment)
{
this.applicationEnvironment = applicationEnvironment;
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser.DmesgIsoLog;
using LinuxLogParserCore;
using System.Collections.Generic;
namespace DmesgIsoMPTAddin
{
public class DmesgIsoLogParsedResult
{
public List<LogEntry> LogEntries { get; }
public IReadOnlyDictionary<string, FileMetadata> FileToMetadata { get; }
public DmesgIsoLogParsedResult(List<LogEntry> logEntries, Dictionary<string, FileMetadata> fileToMetadata)
{
LogEntries = logEntries;
FileToMetadata = fileToMetadata;
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using LinuxLogParser;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
namespace DmesgIsoMPTAddin.Tables
{
[Table]
public static class ParsedTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{E3EEEA4F-6F67-4FB7-9F3B-0F62FB7400B9}"),
"parsedDmesgLogInfo",
"Dmesg Parsed Log",
category: "Linux",
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.DmesgIsoLog, DmesgIsoDataCooker.CookerId)
});
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{5B4ADD38-41EF-4155-AD04-04D0D51DEC87}"), "FileName", "File Name"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration MessageNumberColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{C26B5860-2B6C-48F9-8DCE-B5EF5C3FB371}"), "Line Number", "Ordering of the lines in the file"),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration EntityColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{9D79FFBC-01CE-4581-BC19-F7339125AD9F}"), "Entity", "The entity that produced the message."),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration TopicColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{E40F2307-75D2-479F-9C97-787E22465E8A}"), "Topic", "The topic of the message."),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration TimestampColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{5481E4AC-C12E-4614-957F-9C1ED49401D3}"), "Timestamp", "Timestamp when the message was created."),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration MetadataColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{4C0E9263-C371-4785-B689-0704389E4872}"), "Metadata Info", "Time metadata recorded in the message (often irrelevant)."),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration MessageColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{8B925E73-06B4-47DD-94BE-CE1D2A33B5DB}"), "Message", "Logged message."),
new UIHints { Width = 140, });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
DmesgIsoLogParsedResult parsedResult = tableData.QueryOutput<DmesgIsoLogParsedResult>(
DataOutputPath.Create(SourceParserIds.DmesgIsoLog, DmesgIsoDataCooker.CookerId, "ParsedResult"));
var logEntries = parsedResult.LogEntries;
var baseProjection = Projection.Index(logEntries);
var fileNameProjection = baseProjection.Compose(x => x.filePath);
var lineNumberProjection = baseProjection.Compose(x => x.lineNumber);
var entityProjection = baseProjection.Compose(x => x.entity);
var topicProjection = baseProjection.Compose(x => x.topic);
var timestampProjection = baseProjection.Compose(x => x.timestamp);
var metadataProjection = baseProjection.Compose(x => x.metadata);
var messageProjection = baseProjection.Compose(x => x.message);
var config = new TableConfiguration("Default")
{
Columns = new[]
{
FileNameColumn,
EntityColumn,
TableConfiguration.PivotColumn,
MessageNumberColumn,
TopicColumn,
MessageColumn,
MetadataColumn,
TableConfiguration.GraphColumn,
TimestampColumn
},
Layout = TableLayoutStyle.GraphAndTable,
};
config.AddColumnRole(ColumnRole.StartTime, TimestampColumn);
config.AddColumnRole(ColumnRole.EndTime, TimestampColumn);
tableBuilder.AddTableConfiguration(config)
.SetDefaultTableConfiguration(config)
.SetRowCount(logEntries.Count)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(MessageNumberColumn, lineNumberProjection)
.AddColumn(EntityColumn, entityProjection)
.AddColumn(TopicColumn, topicProjection)
.AddColumn(MessageColumn, messageProjection)
.AddColumn(TimestampColumn, timestampProjection)
.AddColumn(MetadataColumn, metadataProjection);
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using LinuxLogParser;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
namespace DmesgIsoMPTAddin.Tables.Metadata
{
[Table]
public sealed class FileStatsMetadataTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{F720776E-329B-4C2E-927F-88BD5720D0E9}"),
"File Stats",
"Statistics for dmesg.iso.log files",
isMetadataTable: true,
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.DmesgIsoLog, DmesgIsoDataCooker.CookerId)
});
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{64697A4A-68D9-4114-95E0-374E46AF0C87}"), "File Name", "The name of the file."),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration LineCountColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{4FE35802-2D09-4EB1-ACE9-4D72F5A1E274}"), "Line Count", "The number of lines in the file."),
new UIHints { Width = 80, });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
DmesgIsoLogParsedResult parsedResult = tableData.QueryOutput<DmesgIsoLogParsedResult>(
DataOutputPath.Create(SourceParserIds.DmesgIsoLog, DmesgIsoDataCooker.CookerId, "ParsedResult"));
var fileNames = parsedResult.FileToMetadata.Keys.ToArray();
var fileNameProjection = Projection.Index(fileNames.AsReadOnly());
var lineCountProjection = fileNameProjection.Compose(
fileName => parsedResult.FileToMetadata[fileName].LineCount);
tableBuilder.SetRowCount(fileNames.Length)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(LineCountColumn, lineCountProjection);
}
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using LinuxLogParser;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
namespace DmesgIsoMPTAddin.Tables
{
[Table]
public static class RawTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{71819F7E-9CA4-4B72-8836-3794DC62A73F}"),
"rawDmesgLogInfo",
"Dmesg Raw Log",
category: "Linux",
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.DmesgIsoLog, DmesgIsoDataCooker.CookerId)
});
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{C7310E9A-2B1F-4C1C-B578-6BAA7FC25CED}"), "FileName", "File Name"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LogNumberColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{72B06227-4D01-4086-A746-9BB15A342465}"), "Log Number", "Number of log in the file"),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration LogTimestampColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{67F3A9FC-41D4-4B70-9155-7FCC5F036808}"), "Log Timestamp", "Moment when the log was created."),
new UIHints { Width = 80, });
private static readonly ColumnConfiguration LogColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{B265567A-E0F9-4564-9853-328C3A591191}"), "Log", "Logged message."),
new UIHints { Width = 140, });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
DmesgIsoLogParsedResult parsedResult = tableData.QueryOutput<DmesgIsoLogParsedResult>(
DataOutputPath.Create(SourceParserIds.DmesgIsoLog, DmesgIsoDataCooker.CookerId, "ParsedResult"));
var logEntries = parsedResult.LogEntries;
var baseProjection = Projection.Index(logEntries);
var fileNameProjection = baseProjection.Compose(x => x.filePath);
var logNumberProjection = baseProjection.Compose(x => x.lineNumber);
var timestampProjection = baseProjection.Compose(x => x.timestamp);
var rawLogProjection = baseProjection.Compose(x => x.rawLog);
var columnsConfig = new TableConfiguration("Default")
{
Columns = new[]
{
FileNameColumn,
TableConfiguration.PivotColumn,
LogNumberColumn,
LogColumn,
TableConfiguration.GraphColumn,
LogTimestampColumn
},
Layout = TableLayoutStyle.Table,
};
columnsConfig.AddColumnRole(ColumnRole.StartTime, LogTimestampColumn);
columnsConfig.AddColumnRole(ColumnRole.EndTime, LogTimestampColumn);
tableBuilder.AddTableConfiguration(columnsConfig)
.SetDefaultTableConfiguration(columnsConfig)
.SetRowCount(logEntries.Count)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(LogNumberColumn, logNumberProjection)
.AddColumn(LogColumn, rawLogProjection)
.AddColumn(LogTimestampColumn, timestampProjection);
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Linq;
namespace WaLinuxAgentMPTAddin.Tables.Metadata
{
//
// This is a sample Metadata table for .txt files
// Metadata tables are used to expose information about the data being processed, not the actual data being processed.
// Metadata would be something like "number of events in the file" or "file size" or any other number of things that describes the data being processed.
// In this sample table, we expose three columns: File Name, Line Count and Word Count.
//
//
// In order for the CustomDataSourceBase to understand your metadata table,
// add a MetadataTable attribute which denotes this table as metadata.
//
[Table]
//
// Have the MetadataTable inherit the TableBase class
//
public static class FileStatsMetadataTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{40AF86E5-0DF8-47B1-9A01-1D6C3529B75B}"),
"File Stats",
"Statistics for text files",
isMetadataTable: true,
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.WaLinuxAgentLog, WaLinuxAgentDataCooker.CookerId)
});
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{2604E009-F47D-4A22-AA4F-B148D1C26553}"), "File Name", "The name of the file."),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LineCountColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{C499AF57-64D1-47A9-8550-CF24D6C9615D}"), "Line Count", "The number of lines in the file."),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration WordCountColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{3669E90A-DC8F-4972-A5D3-3E13AFDF5DB7}"), "Word Count", "The number of words in the file."),
new UIHints { Width = 80 });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
WaLinuxAgentLogParsedResult parsedResult = tableData.QueryOutput<WaLinuxAgentLogParsedResult>(
DataOutputPath.Create(SourceParserIds.WaLinuxAgentLog, WaLinuxAgentDataCooker.CookerId, "ParsedResult"));
var fileNames = parsedResult.FileToMetadata.Keys.ToArray();
var fileNameProjection = Projection.Index(fileNames.AsReadOnly());
var lineCountProjection = fileNameProjection.Compose(
fileName => parsedResult.FileToMetadata[fileName].LineCount);
tableBuilder.SetRowCount(fileNames.Length)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(LineCountColumn, lineCountProjection);
}
}
}

Просмотреть файл

@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Linq;
namespace WaLinuxAgentMPTAddin.Tables
{
//
// Add a Table attribute in order for the CustomDataSourceBase to understand your table.
//
[Table]
//
// Have the MetadataTable inherit the TableBase class
//
public sealed class WaLinuxAgentTable
{
public static readonly TableDescriptor TableDescriptor = new TableDescriptor(
Guid.Parse("{31bccac5-f015-4e15-a0a8-bcf93194a850}"),
"WaLinuxAgent",
"waagent Log",
category: "Linux",
requiredDataCookers: new List<DataCookerPath> {
new DataCookerPath(SourceParserIds.WaLinuxAgentLog, WaLinuxAgentDataCooker.CookerId)
});
private static readonly ColumnConfiguration FileNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{9F1FF010-868A-441B-983F-32FD7B92B6D1}"), "FileName", "File Name"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LineNumberColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{ae17deac-1774-41fd-b435-6aa463a44191}"), "LineNumber", "Log Line Number"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration EventTimestampColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{ef0430ff-3ba6-42ab-ba36-6145a17e3879}"), "Event Time", "The timestamp of the log entry"),
new UIHints { Width = 80, SortPriority = 0, SortOrder = SortOrder.Ascending });
// todo: needs to be changed by user manually to DateTime UTC format. SDK doesn't yet support specifying this <DateTimeTimestampOptionsParameter DateTimeEnabled="true" />
private static readonly ColumnConfiguration EventTimestampDateTimeColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{574aa62a-fc79-47ba-a43e-e65d5db7572a}"), "Event Time", "The timestamp of the log entry"),
new UIHints { Width = 130 });
private static readonly ColumnConfiguration LogLevelColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{c617df4d-0eaf-4b31-9af2-90b5205fde5f}"), "LogLevel", "Log level"),
new UIHints { Width = 80 });
private static readonly ColumnConfiguration LogColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{b0bcd818-58e1-4d93-8309-a1cff3da5976}"), "Log", "Log Entry"),
new UIHints { Width = 1200 });
public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
WaLinuxAgentLogParsedResult parsedResult = tableData.QueryOutput<WaLinuxAgentLogParsedResult>(
DataOutputPath.Create(SourceParserIds.WaLinuxAgentLog, WaLinuxAgentDataCooker.CookerId, "ParsedResult"));
var logEntries = parsedResult.LogEntries;
var baseProjection = Projection.Index(logEntries);
var fileNameProjection = baseProjection.Compose(x => x.FilePath);
var lineNumberProjection = baseProjection.Compose(x => x.LineNumber);
var eventTimeProjection = baseProjection.Compose(x => x.EventTimestamp);
var logLevelProjection = baseProjection.Compose(x => x.LogLevel);
var logProjection = baseProjection.Compose(x => x.Log);
//
// Table Configurations describe how your table should be presented to the user:
// the columns to show, what order to show them, which columns to aggregate, and which columns to graph.
// You may provide a number of columns in your table, but only want to show a subset of them by default so as not to overwhelm the user.
// The user can still open the table properties in UI to turn on or off columns.
// The table configuration class also exposes four (4) columns that UI explicitly recognizes: Pivot Column, Graph Column, Left Freeze Column, Right Freeze Column
// For more information about what these columns do, go to "Advanced Topics" -> "Table Configuration" in our Wiki. Link can be found in README.md
//
var config = new TableConfiguration("Default")
{
Columns = new[]
{
LogLevelColumn,
TableConfiguration.PivotColumn,
LineNumberColumn,
LogColumn,
EventTimestampDateTimeColumn,
TableConfiguration.GraphColumn,
EventTimestampColumn,
},
Layout = TableLayoutStyle.GraphAndTable,
};
config.AddColumnRole(ColumnRole.StartTime, EventTimestampColumn);
//
//
// Use the table builder to build the table.
// Add and set table configuration if applicable.
// Then set the row count (we have one row per file) and then add the columns using AddColumn.
//
tableBuilder
.AddTableConfiguration(config)
.SetDefaultTableConfiguration(config)
.SetRowCount(logEntries.Count)
.AddColumn(FileNameColumn, fileNameProjection)
.AddColumn(LineNumberColumn, lineNumberProjection)
.AddColumn(EventTimestampColumn, eventTimeProjection)
.AddColumn(LogLevelColumn, logLevelProjection)
.AddColumn(LogColumn, logProjection);
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>WaLinuxAgentSDKAddin</AssemblyName>
<RootNamespace>WaLinuxAgentSDKAddin</RootNamespace>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Performance.SDK" Version="0.108.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LinuxLogParser\LinuxLogParser.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using Microsoft.Performance.SDK.Extensibility;
using LinuxLogParser.WaLinuxAgentLog;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using LinuxLogParserCore;
namespace WaLinuxAgentMPTAddin
{
public sealed class WaLinuxAgentCustomDataProcessor
: CustomDataProcessorBaseWithSourceParser<WaLinuxAgentLogParsedEntry, LogContext, LogParsedDataKey>
{
public WaLinuxAgentCustomDataProcessor(
ISourceParser<WaLinuxAgentLogParsedEntry, LogContext, LogParsedDataKey> sourceParser,
ProcessorOptions options,
IApplicationEnvironment applicationEnvironment,
IProcessorEnvironment processorEnvironment,
IReadOnlyDictionary<TableDescriptor, Action<ITableBuilder, IDataExtensionRetrieval>> allTablesMapping,
IEnumerable<TableDescriptor> metadataTables)
: base(sourceParser, options, applicationEnvironment, processorEnvironment, allTablesMapping, metadataTables)
{
}
}
}

Просмотреть файл

@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser.WaLinuxAgentLog;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace WaLinuxAgentMPTAddin
{
//
// This is a sample Custom Data Source (CDS) that understands files with the .txt extension
//
// In order for a CDS to be recognized, it MUST satisfy the following:
// a) Be a public type
// b) Have a public parameterless constructor
// c) Implement the ICustomDataSource interface
// d) Be decorated with the CustomDataSourceAttribute attribute
// e) Be decorated with at least one of the derivatives of the DataSourceAttribute attribute
//
[CustomDataSource(
"{a9ac39bc-2d07-4a01-b9b5-13a02611f5f2}", // The GUID must be unique for your Custom Data Source. You can use Visual Studio's Tools -> Create Guid… tool to create a new GUID
"WaLinuxAgent", // The Custom Data Source MUST have a name
@"WaLinuxAgent log parser")] // The Custom Data Source MUST have a description
[FileDataSource(
".log", // A file extension is REQUIRED
"Linux WaLinuxAgent Cloud Provisioning Log")] // A description is OPTIONAL. The description is what appears in the file open menu to help users understand what the file type actually is.
//
// There are two methods to creating a Custom Data Source that is recognized by the SDK:
// 1. Using the helper abstract base classes
// 2. Implementing the raw interfaces
// This sample demonstrates method 1 where the CustomDataSourceBase abstract class
// helps provide a public parameterless constructor and implement the ICustomDataSource interface
//
public class WaLinuxAgentCustomDataSource
: CustomDataSourceBase
{
private IApplicationEnvironment applicationEnvironment;
protected override void SetApplicationEnvironmentCore(IApplicationEnvironment applicationEnvironment)
{
//
// Saves the given application environment into this instance
//
this.applicationEnvironment = applicationEnvironment;
}
protected override bool IsFileSupportedCore(string path)
{
return StringComparer.OrdinalIgnoreCase.Equals(
"waagent.log",
Path.GetFileName(path));
}
protected override ICustomDataProcessor CreateProcessorCore(
IEnumerable<IDataSource> dataSources,
IProcessorEnvironment processorEnvironment,
ProcessorOptions options)
{
string[] filePaths = dataSources.Select(x => x.GetUri().LocalPath).ToArray();
var sourceParser = new WaLinuxAgentLogParser(filePaths);
return new WaLinuxAgentCustomDataProcessor(
sourceParser,
options,
this.applicationEnvironment,
processorEnvironment,
this.AllTables,
this.MetadataTables);
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser;
using LinuxLogParser.WaLinuxAgentLog;
using LinuxLogParserCore;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using System.Collections.Generic;
using System.Threading;
namespace WaLinuxAgentMPTAddin
{
public class WaLinuxAgentDataCooker :
CookedDataReflector,
ISourceDataCooker<WaLinuxAgentLogParsedEntry, LogContext, LogParsedDataKey>
{
public const string CookerId = "WaLinuxAgentCooker";
public ReadOnlyHashSet<LogParsedDataKey> DataKeys => new ReadOnlyHashSet<LogParsedDataKey>(
new HashSet<LogParsedDataKey>(
new[] {
LogParsedDataKey.GeneralLog
}
)
);
public SourceDataCookerOptions Options => SourceDataCookerOptions.None;
public string DataCookerId => Path.CookerPath;
public string Description => "Parsed information of WaLinuxAgent log.";
public string SourceParserId => Path.SourceParserId;
public DataCookerPath Path { get; }
public IReadOnlyDictionary<DataCookerPath, DataCookerDependencyType> DependencyTypes =>
new Dictionary<DataCookerPath, DataCookerDependencyType>();
public IReadOnlyCollection<DataCookerPath> RequiredDataCookers => new HashSet<DataCookerPath>();
public DataProductionStrategy DataProductionStrategy => DataProductionStrategy.PostSourceParsing;
[DataOutput]
public WaLinuxAgentLogParsedResult ParsedResult { get; private set; }
private List<LogEntry> logEntries;
private LogContext context;
public WaLinuxAgentDataCooker() : this(new DataCookerPath(SourceParserIds.WaLinuxAgentLog, CookerId))
{
}
private WaLinuxAgentDataCooker(DataCookerPath dataCookerPath) : base(dataCookerPath)
{
Path = dataCookerPath;
}
public void BeginDataCooking(ICookedDataRetrieval dependencyRetrieval, CancellationToken cancellationToken)
{
logEntries = new List<LogEntry>();
}
public DataProcessingResult CookDataElement(WaLinuxAgentLogParsedEntry data, LogContext context,
CancellationToken cancellationToken)
{
DataProcessingResult result = DataProcessingResult.Processed;
if (data is LogEntry logEntry)
{
logEntries.Add(logEntry);
this.context = context;
}
else
{
result = DataProcessingResult.Ignored;
}
return result;
}
public void EndDataCooking(CancellationToken cancellationToken)
{
ParsedResult = new WaLinuxAgentLogParsedResult(logEntries, context.FileToMetadata);
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using LinuxLogParser.WaLinuxAgentLog;
using LinuxLogParserCore;
using System.Collections.Generic;
namespace WaLinuxAgentMPTAddin
{
public class WaLinuxAgentLogParsedResult
{
public List<LogEntry> LogEntries { get; }
public IReadOnlyDictionary<string, FileMetadata> FileToMetadata { get; }
public WaLinuxAgentLogParsedResult(List<LogEntry> logEntries, Dictionary<string, FileMetadata> fileToMetadata)
{
LogEntries = logEntries;
FileToMetadata = fileToMetadata;
}
}
}

Просмотреть файл

@ -17,6 +17,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfCds", "PerfCds\PerfCds.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfDataExtensions", "PerfDataExtensions\PerfDataExtensions.csproj", "{B381F72E-335C-4842-9DB2-615D5C18A943}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LinuxLogParsers", "LinuxLogParsers", "{9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParserCore", "LinuxLogParsers\LinuxLogParserCore\LinuxLogParserCore.csproj", "{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParser", "LinuxLogParsers\LinuxLogParser\LinuxLogParser.csproj", "{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloud-Init", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\Cloud-init\Cloud-Init.csproj", "{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dmesg", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\DmesgIsoLog\Dmesg.csproj", "{04455C36-F127-4D40-BCB3-78E7DE21E70E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaLinuxAgent", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\WaLinuxAgent\WaLinuxAgent.csproj", "{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -51,10 +63,37 @@ Global
{B381F72E-335C-4842-9DB2-615D5C18A943}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B381F72E-335C-4842-9DB2-615D5C18A943}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B381F72E-335C-4842-9DB2-615D5C18A943}.Release|Any CPU.Build.0 = Release|Any CPU
{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Release|Any CPU.Build.0 = Release|Any CPU
{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Release|Any CPU.Build.0 = Release|Any CPU
{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Release|Any CPU.Build.0 = Release|Any CPU
{04455C36-F127-4D40-BCB3-78E7DE21E70E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04455C36-F127-4D40-BCB3-78E7DE21E70E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04455C36-F127-4D40-BCB3-78E7DE21E70E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04455C36-F127-4D40-BCB3-78E7DE21E70E}.Release|Any CPU.Build.0 = Release|Any CPU
{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}
{03DBB240-C4DD-4FF4-9405-F50CBF0A960D} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}
{7CBFAE62-0D0D-4075-A426-99945DD4E9A8} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}
{04455C36-F127-4D40-BCB3-78E7DE21E70E} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}
{75AF82A4-AF0E-425F-8CF5-62F4F54380B9} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E96603EA-8E1D-4AA9-A474-D267A116C316}
EndGlobalSection