Initial checkin of Linux Log Parsers - Dmesg, cloud-init, WaLinuxAgent
This commit is contained in:
Родитель
5b8fb05104
Коммит
9246631ded
|
@ -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 "$(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
|
Загрузка…
Ссылка в новой задаче