问题
我们在平时的工作和开发中,会经常遇到这个问题:
从nginx的日志access.log中统计getVideoInfo接口的QPS。日志格式为:
2019-07-12T11:16:23 0800|127.0.0.1|-|-|GET|http|127.0.0.1|/getVideoInfo?id=1
日志内容大致为:
代码语言:txt复制2019-07-12T11:16:23 0800|127.0.0.1|-|-|GET|http|127.0.0.1|/getVideoInfo?id=1
2019-07-12T11:16:45 0800|127.0.0.1|-|-|GET|http|127.0.0.1|/getVideoInfo?id=1
2019-07-12T11:16:58 0800|127.0.0.1|-|-|GET|http|127.0.0.1|/getVideoInfo?id=1
2019-07-12T11:17:01 0800|127.0.0.1|-|-|GET|http|127.0.0.1|/getVideoInfo?id=1
2019-07-12T11:17:20 0800|127.0.0.1|-|-|GET|http|127.0.0.1|/getVideoInfo?id=1
解决思路
首先nginx的日志是按照时间顺序的。因此计算QPS,只需要先统计条数,再计算时间差,二者相除就可以得到。
思路一:使用wc
命令
- 第一步: 使用
wc
命令获取条数
wc -l access.log | awk '{print $1}'
- 统计第一条和最后一条的时间并格式化成时间戳
// 第一条日志时间戳
date -d "$(head -n 1 access.log | awk -F "|" '/getVideoInfo/ {print $1}')" %s
// 最后一条日志时间戳
date -d "$(tail -n 1 access.log | awk -F "|" '/getVideoInfo/ {print $1}')" %s
- 计算QPS完整命令
count=$(wc -l access.log | awk '{print $1}'); start=$(date -d "$(head -n 1 access.log | awk -F "|" '/getVideoInfo/ {print $1}')" %s); end=$(date -d "$(tail -n 1 access.log | awk -F "|" '/getVideoInfo/ {print $1}')" %s); t=$(($end-$start)); qps=$(echo "scale=2; $count/$t" | bc); printf "%.2fn" $qps
思路二: 使用awk
命令
- 第一步,遍历统计条数
awk -F "|" '$8=="/getVideoInfo?id=1" {count } END {print count}' access.log
- 加入统计第一条和最后一条时间计算
-v start="$(date -d "$(head -n 1 access.log | grep "getVideoInfo" | cut -d "|" -f 1)" %s)" -v end="$(date -d "$(tail -n 1 access.log | grep "getVideoInfo" | cut -d "|" -f 1)" %s)"
- 计算QPS完整命令
awk -F "|" -v start="$(date -d "$(head -n 1 access.log | grep "getVideoInfo" | cut -d "|" -f 1)" %s)" -v end="$(date -d "$(tail -n 1 access.log | grep "getVideoInfo" | cut -d "|" -f 1)" %s)" '$8=="/getVideoInfo?id=1" {count } END {qps=count/(end-start); print qps}' access.log
使用脚本处理
使用Python脚本
代码语言:python代码运行次数:0复制import re
from datetime import datetime
log_file = "access.log"
target_api = "/getVideoInfo"
# 统计时间范围
start_time = datetime.strptime("2019-07-12T00:00:00 0800", "%Y-%m-%dT%H:%M:%S%z")
end_time = datetime.strptime("2019-07-12T23:59:59 0800", "%Y-%m-%dT%H:%M:%S%z")
# 统计请求数
qps = 0
with open(log_file, "r") as file:
for line in file:
# 解析日志行
parts = line.split("|")
timestamp = datetime.strptime(parts[0], "%Y-%m-%dT%H:%M:%S%z")
request_method = parts[4]
request_url = parts[7]
# 判断是否为目标接口的 GET 请求
if request_method == "GET" and request_url.startswith(target_api):
# 判断时间范围
if start_time <= timestamp <= end_time:
qps = 1
print("QPS for", target_api, ":", qps)
在上述示例中,我们首先定义了日志文件路径(log_file)和目标接口路径(target_api)。然后,我们指定了统计的时间范围(start_time 和 end_time)。
接下来,我们打开日志文件并逐行解析每个日志条目。我们使用 "|" 分隔符将每行日志拆分为不同的字段,并提取时间戳、请求方法和请求URL。
然后,我们检查请求方法是否为 "GET",并且请求URL是否以目标接口路径开头。如果满足条件,我们进一步检查时间戳是否在指定的时间范围内,并将符合条件的请求计数加1。
最后,我们打印出统计结果,即目标接口的 QPS。
使用go来实现
代码语言:go复制package main
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
"strings"
"time"
)
func main() {
logFile := "access.log"
targetAPI := "/getVideoInfo"
// 统计时间范围
startTime, _ := time.Parse("2006-01-02T15:04:05-0700", "2019-07-12T00:00:00 0800")
endTime, _ := time.Parse("2006-01-02T15:04:05-0700", "2019-07-12T23:59:59 0800")
// 统计请求数
qps := 0
file, err := os.Open(logFile)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// 解析日志行
parts := strings.Split(line, "|")
timestamp, _ := time.Parse("2006-01-02T15:04:05-0700", parts[0])
requestMethod := parts[4]
requestURL := parts[7]
// 判断是否为目标接口的 GET 请求
if requestMethod == "GET" && strings.HasPrefix(requestURL, targetAPI) {
// 判断时间范围
if timestamp.After(startTime) && timestamp.Before(endTime) {
qps
}
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
fmt.Println("QPS for", targetAPI, ":", qps)
}
使用shell脚本实现
代码语言:shell复制#!/bin/bash
log_file="access.log"
target_api="/getVideoInfo"
# 统计时间范围
start_time=$(date -d "2019-07-12 00:00:00" %s)
end_time=$(date -d "2019-07-12 23:59:59" %s)
# 统计请求数
qps=0
while IFS= read -r line
do
# 解析日志行
timestamp=$(echo "$line" | awk -F '|' '{print $1}')
request_method=$(echo "$line" | awk -F '|' '{print $5}')
request_url=$(echo "$line" | awk -F '|' '{print $8}')
# 判断是否为目标接口的 GET 请求
if [[ "$request_method" == "GET" && "$request_url" == *"$target_api"* ]]; then
# 判断时间范围
timestamp=$(date -d "$timestamp" %s)
if [[ "$timestamp" -ge "$start_time" && "$timestamp" -le "$end_time" ]]; then
((qps ))
fi
fi
done < "$log_file"
echo "QPS for $target_api: $qps"
使用PHP实现
代码语言:php复制<?php
$logFile = "access.log";
$targetAPI = "/getVideoInfo";
// 统计时间范围
$startTime = strtotime("2019-07-12 00:00:00");
$endTime = strtotime("2019-07-12 23:59:59");
// 统计请求数
$qps = 0;
$file = fopen($logFile, "r");
if ($file) {
while (($line = fgets($file)) !== false) {
// 解析日志行
$parts = explode("|", $line);
$timestamp = strtotime($parts[0]);
$requestMethod = $parts[4];
$requestURL = $parts[7];
// 判断是否为目标接口的 GET 请求
if ($requestMethod == "GET" && strpos($requestURL, $targetAPI) !== false) {
// 判断时间范围
if ($timestamp >= $startTime && $timestamp <= $endTime) {
$qps ;
}
}
}
fclose($file);
} else {
echo "Failed to open the log file.";
exit;
}
echo "QPS for $targetAPI: $qpsn";