👨💻
syslog 負荷試験用アプリを Python で作成する
はじめに
syslog サーバの負荷試験用に大量の syslog を送信する必要があるのですが、ちょうどよいフリーのアプリがないため、ChatGPT を活用して syslog 負荷試験用のアプリを作りたいと思います。
事前準備
Windows クライアントのローカルで検証するため、事前に PowerShell で syslog サーバ相当を動作させ、ログ受信テストをします。
syslog サーバ相当 Powershell はこちらを参考にさせていただき、ChatGPT に指示して Close 処理を追加しました。(ありがとうございます)
Run-Syslogserver.ps1
$Udp = New-Object Net.Sockets.UdpClient -ArgumentList 514
$Sender = $null
Add-Type -TypeDefinition @"
public enum Syslog_Facility
{
kern,
user,
mail,
system,
security,
syslog,
lpr,
news,
uucp,
clock,
authpriv,
ftp,
ntp,
logaudit,
logalert,
cron,
local0,
local1,
local2,
local3,
local4,
local5,
local6,
local7,
}
"@
Add-Type -TypeDefinition @"
public enum Syslog_Severity
{
Emergency,
Alert,
Critical,
Error,
Warning,
Notice,
Informational,
Debug
}
"@
try {
while($true)
{
if($Udp.Available)
{
$Buffer = $Udp.Receive([ref]$Sender)
$MessageString = [Text.Encoding]::UTF8.GetString($Buffer)
$Priority = [Int]($MessageString -Replace "<|>.*")
[int]$FacilityInt = [Math]::truncate([decimal]($Priority / 8))
$Facility = [Enum]::ToObject([Syslog_Facility], $FacilityInt)
[int]$SeverityInt = $Priority - ($FacilityInt * 8 )
$Severity = [Enum]::ToObject([Syslog_Severity], $SeverityInt)
$MessageString = "$MessageString $Facility $Severity"
$MessageString = $MessageString -Replace "<.*>",""
#Write-Host $MessageString
$MessageString >> c:\tmp\syslog.log
}
[Threading.Thread]::Sleep(500)
}
}
finally {
$Udp.Close()
}
こちらを実行して起動し、以下コマンドで syslog を送信します。
$syslogServer = "127.0.0.1"
$syslogPort = 514
$udpClient = New-Object System.Net.Sockets.UdpClient
$udpClient.Connect($syslogServer, $syslogPort)
$bytes = [Text.Encoding]::ASCII.GetBytes("<134>Oct 29 23:59:59 MyHost MyApp: Test message")
$udpClient.Send($bytes, $bytes.Length)
$udpClient.Close()
以下のように記録されていることを確認できました。
ChatGPT でアプリ作成
さまざま追加で指示しましたが、要件は以下の通りです。
- 大量に syslog を送信する Web アプリを Python で作成する
- Web UI で以下を指定する
- ログ送信先
- TCP or UDP
- ポート番号
- 1 秒間当たりのログ送信件数
- 合計ログ送信件数
- Facility
- Severity
- 送信するメッセージ
- 送信メッセージに $msgId を使用することで一意の ID を埋め込む
- メッセージ ID のプレフィックスを指定
- ログの送信状況を確認できる
- ログ送信を中断できる
一旦の完成形は以下になります。
app.py
from flask import Flask, render_template, request, jsonify
import socket
import threading
import time
app = Flask(__name__)
# グローバル変数
send_count = 0
cancel_flag = False
@app.route('/')
def index():
return render_template('index.html')
@app.route('/send_logs', methods=['POST'])
def send_logs():
global send_count
global cancel_flag
send_count = 0
cancel_flag = False
# フォームからのデータを取得
log_destination = request.form.get('log_destination')
protocol = request.form.get('protocol')
port = int(request.form.get('port'))
logs_per_second = int(request.form.get('logs_per_second'))
total_logs = int(request.form.get('total_logs'))
facility = int(request.form.get('facility'))
severity = int(request.form.get('severity'))
message = request.form.get('message')
msg_id_prefix = request.form.get('msg_id_prefix')
# Priorityの計算
priority = (facility * 8) + severity
# ログの送信を別スレッドで実行
def worker():
global send_count
sock_type = socket.SOCK_DGRAM if protocol == 'UDP' else socket.SOCK_STREAM
with socket.socket(socket.AF_INET, sock_type) as s:
if protocol == 'TCP':
s.connect((log_destination, port))
for i in range(total_logs):
if send_count >= total_logs or cancel_flag:
break
unique_id = f"{msg_id_prefix}{i}"
msg = f"<{priority}>{message.replace('$msgId', unique_id)}"
if protocol == 'UDP':
s.sendto(msg.encode(), (log_destination, port))
else:
s.send(msg.encode())
send_count += 1
time.sleep(1.0 / logs_per_second)
thread = threading.Thread(target=worker)
thread.start()
return jsonify(success=True)
@app.route('/cancel_send', methods=['POST'])
def cancel_send():
global cancel_flag
cancel_flag = True
return jsonify(success=True)
@app.route('/status', methods=['GET'])
def status():
return jsonify(count=send_count)
if __name__ == "__main__":
app.run(debug=True)
index.html
<!DOCTYPE html>
<html>
<head>
<title>Syslog Load Tester</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h2>Syslog Load Tester</h2>
<form id="logForm">
Log Destination: <input type="text" name="log_destination"><br><br>
Protocol:
<select name="protocol">
<option value="UDP">UDP</option>
<option value="TCP">TCP</option>
</select><br><br>
Port: <input type="text" name="port" value="514"><br><br>
Logs per second: <input type="number" name="logs_per_second" value="1"><br><br>
Total logs: <input type="number" name="total_logs" value="10"><br><br>
Facility:
<select name="facility">
<option value="0">kern</option>
<option value="1">user</option>
<option value="2">mail</option>
<option value="3">system</option>
<option value="4">security</option>
<option value="5">syslog</option>
<option value="6">lpr</option>
<option value="7">news</option>
<option value="8">uucp</option>
<option value="9">clock</option>
<option value="10">authpriv</option>
<option value="11">ftp</option>
<option value="12">ntp</option>
<option value="13">logaudit</option>
<option value="14">logalert</option>
<option value="15">cron</option>
<option value="16">local0</option>
<option value="17">local1</option>
<option value="18">local2</option>
<option value="19">local3</option>
<option value="20">local4</option>
<option value="21">local5</option>
<option value="22">local6</option>
<option value="23">local7</option>
</select><br><br>
Severity:
<select name="severity">
<option value="0">Emergency</option>
<option value="1">Alert</option>
<option value="2">Critical</option>
<option value="3">Error</option>
<option value="4">Warning</option>
<option value="5">Notice</option>
<option value="6">Informational</option>
<option value="7">Debug</option>
</select><br><br>
Message: <input type="text" name="message" value="Test log $msgId"><br><br>
Message ID Prefix: <input type="text" name="msg_id_prefix" value="log-"><br><br>
<button type="button" onclick="sendLogs()">Send Logs</button>
<button type="button" onclick="cancelSend()">Cancel</button><br><br>
</form>
<div id="status">
Logs sent: 0
</div>
<script>
function sendLogs() {
$.post("/send_logs", $("#logForm").serialize()).done(function() {
updateStatus();
});
}
function updateStatus() {
$.get("/status", function(data) {
$("#status").text("Logs sent: " + data.count);
if (data.count < parseInt($("input[name='total_logs']").val())) {
setTimeout(updateStatus, 1000);
}
});
}
</script>
<script>
function cancelSend() {
fetch('/cancel_send', {method: 'POST'})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Log sending canceled.');
}
});
}
</script>
</body>
</html>
実行イメージは以下の通りです。
課題など
実際に負荷試験用として使用してみると、time.sleep の影響でぴったりと件数/秒が実現できず、また 1,000 件/秒あたりが限界かと思います。time.sleep の箇所をコメントアウトすれば、秒間の送信件数は制御できないですが、大量の送信 (10,000 件以上) の動作はしました。このあたりが利用するにあたっての留意点や課題かと思います。
Discussion