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
60 changes: 60 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 报告

## Q1.1 的回答

+ 哪条语句或哪几条语句将日志按逗号进行分割?代码中,我们是如何指定每一行的第几个字段代表何种意义的?
- "分割"的实现位于 `LogFileParser.cs` 的 38 行 `foreach (var logRecord in csv.GetRecords<LogRecord>())` 中实现,该行调用了 csv 库的 `GetRecords` 方法,将日志转化为我们所需要的 `LogRecord` 类。
- 我们通过该文件 20-23 行

```csharp
Map(m => m.LineNo).Index(0);
Map(m => m.Timestamp).Index(1);
Map(m => m.PodName).Index(2);
Map(m => m.Message).Index(3);
```

确定了:第一个字段是 `LineNo`,第二个字段是 `Timestamp`,以此类推。

+ 在对日志中 JSON 格式的 `message` 字段进行读取时,我们是在哪个方法内用哪几条语句判断这一行日志的种类(Call / Request / Internal)的?
- 我们在 `LineParser.cs` 的 `ParseLine` 方法中判断种类,具体来说,我们试图通过

```csharp
if (root.TryGetProperty("event", out var eventElement))
{
return eventElement.GetString() switch
{
"call" => LineParser.CreateCall(logRecord),
"request" => LineParser.CreateRequest(logRecord),
"internal" => LineParser.CreateInternal(logRecord),
_ => throw new FormatException($"Unknown event type: {eventElement.GetString()} in log message: {logRecord.Message}")
};
}
```

检测传入 `LogRecord` 对象的 `message` 属性的 `event` 字段是三者中的哪一个,从而进行判断。

+ 在确定了日志种类后,我们是调用了哪个库方法对 JSON 进行解析的?
+ 进一步,我们的框架代码是如何防止日志中有字段缺失的?(例如所给的 Call 日志的 `message` 中缺失 `request_id` 字段)
+ 更进一步,日志中的 JSON 的键是 `abc-def` 命名法(称为烤串命名法),而我们的解析结果却是放在 `AbcDef` 命名法(称为大驼峰命名法)的属性里,我们的框架代码中是如何告诉 JSON 解析器完成这一命名法转换的?
- 我们调用了 `JsonSerializer` 的 `Deserialize` 方法解析 JSON。
- 我们首先通过 `[property: JsonRequired]` 来确保:如果缺失字段,则抛出 `JsonException` 异常。此外,我们还通过 `??` 运算符检测 `JsonSerializer.Deserialize` 方法返回值是否为 `null`,如果在某些情况下该方法返回了 `null`,则抛出 `FormatException` 异常。
- 我们在 `LineParser.cs` 的 31-34 行将 `options` 设为具有 `PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower` 的 `JsonSerializerOptions`,并在 `Deserialize` 时传入 `options` 参数,从而完成了命名法转换。

## Q1.2 的回答

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

## Q1.3 的回答

+ 本次作业中,你是否使用了 AI?
- 我使用了 AI。

### Q1.3.b 的回答

+ 如果使用了 AI,你给予 AI 的提示词是什么?你认为 AI 给出的解答、你完全凭借传统搜索引擎以及自己的能力能够写出的解答之间,AI 的解答比你好在哪?AI 又有哪些解答是存在问题的,或者至少是不如你自己的解答的?给出你的理由。
- 我主要使用的是 VS Code Copilot 自带的代码补全 AI,故没有给出提示词。
- AI 的解答相比于自己给出的解答更为安全,且可读性更好,例如:`LineParser.cs` 的 72-75 这几行就是 AI 补充的,防止出现没有冒号的情况。
- AI 仅仅看到了 `InternalMessage` 上文的两个格式就开始了 `private record InternalMessage` 的编写,但是它没有考虑到原始文本的 `ExceptionName` 和 `ExceptionMessage` 并不是由 JSON 解析给出的,我在测试未通过后,通过检查解决了该问题。
- 本报告完全由我所写,但是由于我对于markdown的格式并不熟悉,所以我让AI调了一下格式
Binary file added docs/02-multithreading/assets/localcli_func.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions docs/02-multithreading/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Report: LocalCli Console Interface (T2.3)

## 实现功能

根据 [guidance.md](./guidance.md) 中 Task 2.3 (S2.3) 的要求,完成了 `LocalCli/Program.cs` 中的控制台交互界面,包含以下功能:

### 1. `InputDirectory` — 输入日志目录

提示用户输入日志文件所在目录,调用 `LogFileAnalyzer` 构造器扫描 `.log` 文件:

- 目录不存在时调用 `analyzer.ChangeDirectory()` 返回 `false`,提示 "Directory not exists" 并要求重试
- 目录路径非法(如空字符串)时捕获 `ArgumentException`,提示 "Directory illegal" 并要求重试
- 用户输入 `Ctrl+C` / `Ctrl+Z`(`Console.ReadLine()` 返回 `null`)时安全退出

### 2. `ShowLogFiles` — 显示日志文件列表

调用 `analyzer.GetLogFiles()` 获取目录中所有 `.log` 文件,逐行打印文件名。

### 3. `AnalyzeFiles` — 分析指定日志文件

- 调用 `ReadDegreeOfParallelism()` 读取并行度(0 = 自动 / 逻辑处理器数),非数字或负数会提示重新输入
- 调用 `ReadFileNames()` 读取逗号分隔的文件名列表(自动 `Trim` 并去除空项)
- 调用 `analyzer.AnalyzeFiles(degreeOfParallelism, fileNames)` 进行分析
- 异常被捕获并以 "分析失败" 提示,程序不会崩溃

### 4. `AnalyzeAll` — 分析全部日志文件

- 读取并行度后调用 `analyzer.AnalyzeAll(degreeOfParallelism)`
- 同样做了异常捕获以保证鲁棒性

### 5. `GetAnalysisResult` — 获取分析结果

输入文件名,调用 `analyzer.TryGetAnalysisResult()`,分四种情况处理:

| 情况 | 行为 |
|------|------|
| 文件不存在 | 提示 "File 'xxx' not found." |
| 尚未分析 (`NotAnalyzed`) | 提示 "File 'xxx' has not been analyzed." |
| 分析成功 (`Succeeded`) | 调用 `KeyValueVisitor.Dump` 逐行输出键值对 |
| 分析失败 (`Failed`) | 输出 `result.ErrorMessage` |

### 6. 鲁棒性设计

对所有异常输入均有处理,程序不会崩溃:

- 非法目录 → 提示重试
- 非法菜单选项(非数字、超出范围)→ 提示 "Invalid choice/input"
- 非法并行度(负数、非数字)→ 提示重试
- 空文件名列表 → 提示 "No file names input." 并返回主菜单
- 不存在 / 未分析的文件查结果 → 给出明确提示
- 切换目录后重新分析 → 结果正常重置

---

## 功能测试截图

### 完整功能演示

![完整功能演示](./assets/localcli_func.png)

以上截图展示了完整的功能流程:
1. 输入目录 `dataset`
2. 显示日志文件列表(选项 1)
3. 分析指定文件 `basic.log, basic-fail.log`,并行度 2(选项 2)
4. 查看 `basic.log` 解析成功的 3 条记录(选项 4)
5. 查看 `basic-fail.log` 解析失败的错误信息(选项 4)
6. 分析全部文件(选项 3,并行度 0 = auto)
7. 查看 `basic-multiple.log` 200 条解析结果(选项 4)

### 鲁棒性测试

![鲁棒性测试](./assets/localcli_robust.png)

以上截图展示了各种非法输入的处理:
1. 输入不存在的目录 → 提示 "Directory not exists" 并重试
2. 非法菜单选项 `0`, `abc`, `7` → 提示 "Invalid"
3. 非法并行度 `abc`, `-1` → 提示 "Invalid input"
4. 空文件名 → 提示 "No file names input."
5. 查不存在的文件的结果 → "File 'nonexistent.log' not found."
6. 查未分析文件的结果 → "has not been analyzed."
7. 分析全部 → 查看 `basic-fail.log` 的失败信息
8. 切换目录后结果重置 → `basic-fail.log` 变回 "not been analyzed"
9. 重新分析后再次查看失败文件 → 正确输出错误信息

---

## 问答

### Q2.1

**`WorkQueue<T>` 类中的共享变量有哪些?是通过什么保护其免于数据竞争(data race)呢?**

`_items` 和 `_isCompleted` 都是共享变量,访问二者时提前打上 `_items` 的锁使得二者免于数据竞争。

**`LogFileAnalyzer` 类中的共享变量有哪些?是通过什么保护其免于数据竞争呢?**

```csharp
private string? _currentDirectory = null;
private bool _isAnalyzing = false;
private readonly Dictionary<string, FileInfo> _logFiles = new();
private readonly Dictionary<string, AnalysisResult> _analysisResults = new();
```

以上都是共享变量,通过打上 `_syncRoot` 的锁避免数据竞争。

**如果条件变量的判断条件使用了 `if` 判断而非 `while` 判断,当出现了虚假唤醒现象时(在类 UNIX 系统中,由于 UNIX 信号等机制,即使没有人调用过 `signal` 或 `broadcast`,处于 `wait` 当中的条件变量也可能被唤醒),会出现什么后果?结合无限仓库容量的生产者消费者问题简单叙述一下。**

以无限仓库容量的生产者消费者问题为例,如果采用 `if(queue.Count == 0)`,当内部 wait 虚假唤醒,则线程继续执行下方 `Dequeue`,试图出队空队列,从而导致抛出 `InvalidOperationException`。

### Q2.2

**那一段代码扫描了给定的目录中的全部 `.log` 后缀的日志文件?假使给定的需求是不但要扫描给定目录中的日志文件,还要递归地获取给定的目录的全部子目录、子子目录……内的日志文件,应当如何做(简要回答即可)?**

```csharp
var logFiles = Directory.EnumerateFiles(directoryPath, "*.log", SearchOption.TopDirectoryOnly)
.Select(filePath => Path.GetFileName(filePath))
.OrderBy(fileName => fileName);
```

以上代码扫描全部 `.log` 后缀的日志文件。将上述 `TopDirectoryOnly` 改为 `AllDirectories` 即可递归获取所有子目录中的日志文件。

### Q2.3

- 我使用了AI工具辅助
- 第一次:由于我不会写文件的流式读取,导致 `parser.Parse` 参数类型不匹配,因此我在 VS Code 的 CC 插件中询问如下问题:"这段代码中 `result = parser.Parse(file);` 并不正确,`parser.Parse` 需要 `TextReader` 类型,应当如何修改?"
- 第二次:我借助了AI完成CLI:提示词为"根据 `docs/02-multiheading/guidance.md` 中对于 T2.3 的要求,完成 `Program.cs`"
- 本文件的测试部分也由AI生成,经过核对与 `report.md` 中要求相符
Binary file added docs/03-async-grpc/assets/image.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/03-async-grpc/assets/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.
33 changes: 33 additions & 0 deletions docs/03-async-grpc/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 异步 gRPC 实验报告

## T3.2 截图与功能实现

### 输入图片

![输入图片](./assets/image.png)

### 输出图片

![输出图片](./assets/image_1.png)

## Q3.1

我认为,在 gRPC 的帮助下,二者的区别被减小了很多。但是以往我“开发”的应用程序往往都是自己使用或者测试,不太需要考虑特别高度的稳定性与对于输入的鲁棒性,因为即使程序崩掉了,也不过是重启一下,但是在本次的作业里,我发现了大量的异常处理等等保证鲁棒性的措施。此外,以往的开发中,我面对的往往是 CPU 密集型任务,因此不太常用多线程,但是网络中,通信成为了一个障碍,所以我在本次作业中也学着使用了一些异步语法。

我认为,本次的网络开发的主要难点在于,之前我的思维中,“服务”与“客户”是耦合的,但是在本次作业中,我们需要对二者做高度的解耦,这给我的理解带来了困难。

我认为本次主要的复杂之处在于信息流的长度比以前长了很多,以前简单的类型现在可能需要从服务端编码为 Protobuf 再在客户端解码,我认为这是较为复杂的。

## Q3.2

本次我使用了 AI。

### Q3.2.b

**提示词:**

> 这段代码中的最后一个方法应当实现流式返回,但是没有传入流对象,我应当如何处理?

这是因为我不理解为什么 `AgentSession.GetAnalysisResult` 没有传入流对象,但是教学里对于流式 gRPC 要求传入一个流对象,AI 为我解答:这个方法并不是 gRPC 服务方法本身,其本身在 `AgentService` 里,我又查看了 `AgentService` 的代码,之后我理解了其层级。

由于我不会 Markdown 语法,本文的 Markdown 格式由 AI 进行了修改。
Binary file added docs/04-avalonia/assets/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/04-avalonia/assets/image-basic.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/04-avalonia/assets/image-fail.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/04-avalonia/assets/image-minus1.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/04-avalonia/assets/image-multiple.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/04-avalonia/assets/image-null.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/04-avalonia/assets/image-wrongport.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions docs/04-avalonia/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# T4.1 Avalonia 图形界面客户端实现报告

## 实现的功能

本次我 `LogAnalyzerClient` 中所有 `TODO: T4.1` 的功能:

1. **刷新日志文件列表**:`RefreshAsync` 异步调用 `GetLogFiles` RPC,并使用返回的文件名更新 `LogFiles`。如果 Agent 返回失败状态,则通过消息框提示错误。
2. **分析选中的多个文件**:`AnalyzeSelectedFilesAsync` 将 `SelectedFiles` 传入 `AnalyzeFiles` RPC
3. **目前支持分析全部文件**
4. **查看分析结果**,可以通过右键查看结果

## 功能截图

### 完整界面

客户端成功连接到 Agent,界面包含日志目录操作、刷新按钮、并行度输入框、`Selected` 和 `All` 分析按钮,以及分析结果列表。

![客户端完整界面](./assets/image-1.png)

### 分析失败的日志文件

对于内容不符合日志格式的文件,客户端能够显示 Agent 返回的具体解析失败原因。

![日志分析失败结果](./assets/image-fail.png)

### 显示多条日志的分析结果

分析结果中显示文件名、工作线程编号,并按序号展示每条日志的全部字段。

![多条日志分析结果](./assets/image-multiple.png)

### 显示普通日志文件的分析结果

客户端能够正确显示包含 Call、Request 和 Internal 等不同事件类型的日志字段。

![普通日志分析结果](./assets/image-basic.png)

## 鲁棒性测试

### 并行度为负数

输入 `-1` 时,请求不会发送到 Agent,客户端弹出消息框提示并行度必须为非负整数。

![负数并行度测试](./assets/image-minus1.png)

### 并行度不是整数

输入 `null` 等非整数文本时,客户端能够识别非法输入并显示错误信息。

![非整数并行度测试](./assets/image-null.png)

### Agent 地址不可用

当目标地址或端口没有 Agent 监听时,连接异常会被捕获,并通过消息框显示连接失败信息,程序不会崩溃。

![Agent 不可用测试](./assets/image-wrongport.png)

## Q4.1

区别主要在于:GUI应用程序中,采用了MVVM的架构,分离了前端,后端与其中的传输处理,但是这也带来了清晰的好处:能够分别在model,view,veiwmodel中处理后端,前端与传输,所以相比于之前的开发,我认为主要的难点与复杂点是吧MVVM架构学习明白。

## Q4.2
我使用了AI,提示词:详细地讲解MVVM架构,并具体地讲解这个项目中是如何使用这一架构的
此外,我还询问了几处报错(都是因为接口使用错误导致的),并进行了修复。
50 changes: 50 additions & 0 deletions docs/05-advanced/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# T5 高级功能项目介绍

## 程序如何编译运行

本次的程序由提供日志分析服务的 Agent 和 Avalonia 桌面客户端两部分组成,需要分别启动。先在一个终端中启动 Agent:

```shell
dotnet run --project src/LogAnalyzerAgent/LogAnalyzerAgent.csproj -c Release --no-build --launch-profile Server
```

终端显示 `Now listening on: http://localhost:5000` 后,保留该进程,再打开另一个终端启动桌面客户端:

```shell
dotnet run --project src/LogAnalyzerClient/LogAnalyzerClient.Desktop/LogAnalyzerClient.Desktop.csproj -c Release --no-build
```

客户端启动后,按以下步骤完成初始化:

1. 点击菜单栏中的 `File -> Connect...`,输入 Agent 地址 `http://localhost:5000`。如果 Agent 运行在其他主机上,则填写该主机的实际地址和端口。
2. 在 `Directory Path` 中填写 **Agent 所在机器上的** 日志目录,建议使用绝对路径。例如,要使用仓库自带的样例日志,可填写仓库中 `src/dataset` 的绝对路径。
3. 点击 `Change Directory`。文件列表会自动刷新;也可以随时点击 `Refresh` 手动刷新。
4. 设置 `DoP`(并行度),选中一个或多个日志文件后点击 `Analyze Selected`,也可以点击 `Analyze All` 分析目录中的全部日志。后续的结果筛选和拓扑显示都需要相应文件已经分析成功。

## 我实现了哪些功能?每个功能如何使用?

### 1. 控件布局自适应

我为 Avalonia 客户端增加了宽、窄两套方案。窗口宽度大于 640 像素时,日志文件和分析结果左右排列。宽度不大于 640 像素时,目录输入框与操作按钮改为分行显示,日志文件和分析结果改为上下排列。

这避免了当窗口过窄时,文字相互挤压的问题,同时也让比较宽的窗口下也能保持美观。

### 2. 云服务调用拓扑推断与可视化

Agent 会从分析成功的日志中提取 `Call` 类型记录,根据产生日志的 Pod 名称推断源服务,并使用 `TargetService` 作为目标服务。客户端收到拓扑数据后会按层排列节点,并绘制服务节点、有向边和边上的调用次数。

我对调用图含有环的情况进行了测试,能够正常运行。

这也为下一个功能提供了基础,允许每一个服务统计其被调用次数。

### 3. 日志类型筛选与调用次数排序

我在 `Analysis Result` 页签中增加了日志类型筛选器,支持 `All`、`Call`、`Request` 和 `Internal` 四种选项。选择 `Call` 时,每条日志还会显示 `CallCount` 字段,其值表示当前文件中以同一个 `TargetService` 为目标的调用总数。此外,`Call count` 下拉框可以按该数值升序或降序排列;相同调用次数的记录再按原始行号排列,以保证结果稳定。

本次开发中,我在程序整体架构的设计上采用了AI的设计,主要是因为我仍不熟悉如何设计架构是最优的。

我的提示词是:根据guidance.md,设计T5.1.a.d的功能的具体架构,注意:你的架构应尽可能保持简单。

本次开发,我的主要心得是:程序的“服务端-客户端-中间层”架构。具体来说,通过这种方式来实现了代码的高可读性与可维护性。

本markdown的格式由AI调整。
Loading
Loading