Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 问答题报告

### (Q1.1)

在给出的代码框架 `Parser` 中:

* 哪条语句或哪几条语句将日志按逗号进行分割?代码中,我们是如何指定每一行的第几个字段代表何种意义的?
> using var csv = new CsvReader(logFile, config);配合循环使用的csv.GetRecords<LogRecord>();
>在LogRecordMap中一一指定对应

* 在对日志中 JSON 格式的 `message` 字段进行读取时,我们是在哪个方法内用哪几条语句判断这一行日志的种类(Call / Request / Internal)的?
> LineParser 类的 ParseLine 方法,if(root.TryGetProperty("event", out var eventElement))语句,再用swith判断

* 在确定了日志种类后,我们是调用了哪个库方法对 JSON 进行解析的?
> System.Text.Json 库中的 JsonSerializer.Deserialize<T>(...) 方法

* 进一步,我们的框架代码是如何防止日志中有字段缺失的?(例如所给的 Call 日志的 `message` 中缺失 `request_id` 字段)
> 在每个属性前面都强制加上了 [property: JsonRequired] 特性标签

* 更进一步,日志中的 JSON 的键是 `abc-def` 命名法(称为烤串命名法),而我们的解析结果却是放在 `AbcDef` 命名法(称为大驼峰命名法)的属性里,我们的框架代码中是如何告诉 JSON 解析器完成这一命名法转换的?
> 框架代码事先创建了一个名为 options 的配置变量,并在其中设置了 JsonNamingPolicy.KebabCaseLower 这一规则。在调用 JsonSerializer.Deserialize 提取数据时,代码将这个 options 作为参数交给了解析工具。

---

### (Q1.2)

以一个 Call 事件的解析结果为例,当调用 `KeyValueVisitor` 的 `Dump` 方法后,都有哪些方法被调用?请补充完整如下的方法调用链(.NET 内置库无需写出):

+ `Dictionary<string, string> KeyValueVisitor.Dump(LogEntry entry)`
+ > TResult CallLogEntry.Accept<TResult>(ILogEntryVisitor<TResult> visitor)
+ > Dictionary<string, string> KeyValueVisitor.Visit(CallLogEntry entry)


---

### (Q1.3)

本次作业中,你是否使用了 AI?根据你的使用情况,在以下 (Q1.3.a) (Q1.3.b) 两个问题中选择一题作答:


#### (Q1.3.b)
如果使用了 AI,你给予 AI 的提示词是什么?你认为 AI 给出的解答、你完全凭借传统搜索引擎以及自己的能力能够写出的解答之间,AI 的解答比你好在哪?AI 又有哪些解答是存在问题的,或者至少是不如你自己的解答的?给出你的理由。
+ > 提示词上,在完成代码部分,我只是把ai当做搜索引擎使用,去解释一些我看不懂的C#内置函数,然后再用自己能力补充代码,不过在q1.1中第1,4,5个问题,我确实完全不了解c#内置的库是什么,让ai先给出了解答再自己借助ai去了解;
+ > 代码中,ai的解答和我几乎相同;在问答题中,ai的解答比我更具有专业性,对整个项目的把握比我更透彻
+ > 我让只让ai给出了q1.1中1,4,5题超出我能力范围之外的解答,这些解答核实没发现什么大问题,其他的ai解答和我大致相同,我暂时未发现ai明显不如自己解答的情况。
Binary file added docs/02-multithreading/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/02-multithreading/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions docs/02-multithreading/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## 任务 2.3:实现前端交互逻辑 (Console UI)

### 1. 功能介绍
本任务在 `Program.cs` 中实现了控制台交互界面,主要完成了以下三个功能:

* **指定文件分析 (AnalyzeFiles)**:
接收用户输入的以逗号分隔的文件名,自动去除多余空格和空项。通过安全的方式读取用户输入的线程数,如果遇到输入格式错误或者后台任务冲突,会通过循环把用户留在当前步骤要求重新输入,防止程序崩溃。
* **全目录分析 (AnalyzeAll)**:
只需读取用户输入的线程数,在校验输入合法后,直接交给后台并发分析整个文件夹里的所有日志。
* **查询结果 (GetAnalysisResult)**:
获取用户输入的文件名,调用后台查询。如果文件还没有分析,提示未分析;如果分析失败,打印具体的报错信息;如果分析成功,则调用 `KeyValueVisitor.Dump` 方法,把底层的日志数据转换成键值对字典,并逐行清晰地打印到屏幕上。

### 2. 运行效果截图

![alt text](image.png)


![alt text](image-1.png)

### 3. 问答题

#### (Q2.1) 临界区与数据竞争理解

* **`WorkQueue<T>` 类中的共享变量有哪些?是通过什么保护其免于数据竞争(data race)呢?**
> 答:_items和_isCompleted;运用lock()形成互斥锁
>
>

* **`LogFileAnalyzer` 类中的共享变量有哪些?是通过什么保护其免于数据竞争呢?**
> 答:_currentDirectory,_isAnalyzing,_logFiles,_analysisResults;定义 _syncRoot作为互斥锁对象来保护
>
>
>

* **如果条件变量的判断条件使用了 `if` 判断而非 `while` 判断,当出现了虚假唤醒现象时(在类 UNIX 系统中,由于 UNIX 信号等机制,即使没有人调用过 `signal` 或 `broadcast`,处于 `wait` 当中的条件变量也可能被唤醒),会出现什么后果?结合无限仓库容量的生产者消费者问题简单叙述一下。**
> 答:这样的话会导致消费者在被异常唤醒时跳过if检查直接强行尝试从空仓库中取商品,,进而导致程序发生崩溃。
>
>
>

#### (Q2.2) 目录扫描逻辑

* **在给出的代码框架 `LogFileAnalyzer` 中,那一段代码扫描了给定的目录中的全部 `.log` 后缀的日志文件?假使给定的需求是不但要扫描给定目录中的日志文件,还要递归地获取给定的目录的全部子目录、子子目录……内的日志文件,应当如何做(简要回答即可)?**[cite: 6]
> 答:ChangeDirectory 方法中Directory.EnumerateFiles(directoryPath, "*.log", SearchOption.TopDirectoryOnly);
> SearchOption.TopDirectoryOnly 修改为 SearchOption.AllDirectories
>
>

#### (Q2.3) AI 使用情况调查

*本次作业中,你是否使用了 AI?根据你的使用情况,在以下 (Q2.3.a) (Q2.3.b) 两个问题中选择一题作答即可:*

* **(Q2.3.b) 如果使用了 AI,你给予 AI 的提示词是什么?你对 AI 的使用是询问 AI 一些接口的用法或是在某处的写法,还是让 AI 帮你写一部分作业代码,又或是让 AI 给你讲解代码框架?AI 的解答是否出现过错误(如果有,是哪些)?你认为本节的难度是偏低、适中,还是偏高?**
> 答:我给予ai提示词更多是搜索性质与提示讲解性质,并没有让ai直接给出过代码。因为本节难度我认为对于之前从未接触过c#的人来说难度比较高,c#的一堆内置函数单凭自己很难看懂,更何况有时候会一下子牵扯到多个代码文件,没有ai的辅助梳理很容易忘记要干什么。
>
>
>
9 changes: 9 additions & 0 deletions src/LocalCli/LocalCli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.35.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.83.0" />
<PackageReference Include="Grpc.Tools" Version="2.83.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

</Project>
140 changes: 134 additions & 6 deletions src/LocalCli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LogAnalyzer;
using System.Net;
using LogAnalyzer;
using LogParser.Visitors;

namespace LocalCli
Expand Down Expand Up @@ -112,22 +113,149 @@ 6. Exit.

private static void ShowLogFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
var files=analyzer.GetLogFiles();
if (files.Count == 0)
{
Console.WriteLine("No log files found in the current directory.");
return;
}
int index=1;
foreach (var file in files)
{
Console.WriteLine($"{index}.{file}");
index++;
}
}

private static void AnalyzeFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
{ while(true){
Console.WriteLine("Please input log files to analyze (separated by comma):");
var str=Console.ReadLine();
if (str is null)
{
return;
}
var selectedFiles = str.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(f => f.Trim()).ToArray();
if (selectedFiles.Length == 0)
{
Console.WriteLine("No files specified, please try again.");
continue;
}
int degree = 0;
while (true)
{
Console.WriteLine("Please input degree of parallelism:");
var degreeStr = Console.ReadLine();
if (degreeStr is null) return;
if (!int.TryParse(degreeStr, out degree))
{
Console.WriteLine("Invalid number, please try again.");
continue;
}
break;
}
try
{
analyzer.AnalyzeFiles(degree,selectedFiles);
break;
}
catch (InvalidOperationException)
{
Console.WriteLine("Analysis is already running, please wait.");
break;
}
catch(ArgumentException)
{
Console.WriteLine("Invalid files, please try again.");
continue;
}
}

}


private static void AnalyzeAll(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
while (true)
{
Console.WriteLine("Please input degree of parallelism:");
var degreeStr = Console.ReadLine();
if (degreeStr is null)
{
return;
}
if (!int.TryParse(degreeStr, out int degree))
{
Console.WriteLine("Invalid number, please try again.");
continue;
}
try
{
analyzer.AnalyzeAll(degree);
break;
}
catch (InvalidOperationException)
{
Console.WriteLine("Analysis is already running, please wait.");
break;
}
catch (ArgumentException)
{
Console.WriteLine("Invalid degree of parallelism, please try again.");
continue;
}
}
}

private static void GetAnalysisResult(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
while (true)
{
Console.WriteLine("Please input log file name:");
var fileName = Console.ReadLine();
if (fileName is null)
{
return;
}
fileName = fileName.Trim();
if (fileName.Length == 0)
{
Console.WriteLine("Invalid file name, please try again.");
continue;
}
if (!analyzer.TryGetAnalysisResult(fileName, out var result))
{
Console.WriteLine("File not found, please try again.");
continue;
}
if (result is null)
{
Console.WriteLine("File not found, please try again.");
continue;
}
switch (result.State)
{
case AnalysisState.NotAnalyzed:
Console.WriteLine("Not analyzed.");
break;
case AnalysisState.Failed:
Console.WriteLine($"Failed: {result.ErrorMessage}");
break;
case AnalysisState.Succeeded:
var visitor = new KeyValueVisitor();
foreach (var entry in result.Entries)
{
var logDict = visitor.Dump(entry);
foreach (var kvp in logDict)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
break;

}
break;
}
}
}
}
52 changes: 47 additions & 5 deletions src/LogAnalyzer/LogFileAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public void AnalyzeFiles(int degreeOfParallelism, IEnumerable<string> fileNames)
* Set _isAnalyzing
*/
// TODO: T2.2
_isAnalyzing = true;
}

try
Expand All @@ -155,6 +156,10 @@ public void AnalyzeFiles(int degreeOfParallelism, IEnumerable<string> fileNames)
* Remember to lock _syncRoot to prevent data race
*/
// TODO: T2.2
lock (_syncRoot)
{
_isAnalyzing = false;
}
}
}

Expand All @@ -169,7 +174,14 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList<FileInfo> fileLis
* Filter unparsed files.
* If there is an unknown file, throw System.InvalidOperationException.
*/
throw new NotImplementedException("TODO: T2.2");
if (!_analysisResults.ContainsKey(file.Name))
{
throw new InvalidOperationException($"Unknown file: {file.Name}");
}
if (_analysisResults[file.Name].State == AnalysisState.NotAnalyzed)
{
logFilesToParse.Add(file);
}
}
}

Expand All @@ -184,7 +196,11 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList<FileInfo> fileLis
* Enqueue log files
*/
// TODO: T2.2

foreach (var file in logFilesToParse)
{
queue.Enqueue(file);
}
queue.CompleteAdding();
degreeOfParallelism = Math.Max(Math.Min(degreeOfParallelism, logFilesToParse.Count), 1);
var workers = new Thread[degreeOfParallelism];
for (int i = 0; i < degreeOfParallelism; i++)
Expand All @@ -195,12 +211,19 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList<FileInfo> fileLis
* Create and start threads to run `WorkerMain`
*/
// TODO: T2.2
workers[i] = new Thread(() => WorkerMain(workerId, queue));
workers[i].Name = threadName;
workers[i].Start();
}

/*
* Wait for (join) all threads to end
*/
// TODO: T2.2
foreach (var worker in workers)
{
worker.Join();
}
}

private void WorkerMain(int workerId, WorkQueue<FileInfo> queue)
Expand All @@ -213,19 +236,38 @@ private void WorkerMain(int workerId, WorkQueue<FileInfo> queue)
try
{
// Parse file
throw new NotImplementedException("TODO: T2.2");
using var reader = new StreamReader(file.FullName);
var entries = parser.Parse(reader);
result = new AnalysisResult(
FileName: file.Name,
FullName: file.FullName,
State: AnalysisState.Succeeded,
Entries: entries.ToList(),
ErrorMessage: null,
WorkerId: workerId
);
}
catch (Exception ex)
{
// Save exception message to result
throw new NotImplementedException("TODO: T2.2");
result = new AnalysisResult(
FileName: file.Name,
FullName: file.FullName,
State: AnalysisState.Failed,
Entries: Array.Empty<LogEntry>(),
ErrorMessage: ex.Message,
WorkerId: workerId
);
}

/*
* Save parse result.
* [!Important] Remember to lock _syncRoot to prevent data race.
*/
throw new NotImplementedException("TODO: T2.2");
lock (_syncRoot)
{
_analysisResults[file.Name] = result;
}
}
}
}
Expand Down
Loading