mirror of
https://github.com/SunBK201/UA3F.git
synced 2025-12-16 16:57:08 +00:00
feat: record path-through stats
This commit is contained in:
parent
1af71e6a2e
commit
4b4571de2a
@ -1,14 +1,26 @@
|
||||
<%
|
||||
local stats = {}
|
||||
local file = io.open("/var/log/ua3f/stats", "r")
|
||||
if file then
|
||||
for line in file:lines() do
|
||||
local rewrite_stats = {}
|
||||
local rewrite_stats_file = io.open("/var/log/ua3f/rewrite_stats", "r")
|
||||
if rewrite_stats_file then
|
||||
for line in rewrite_stats_file:lines() do
|
||||
local host, count, origin_ua, mocked_ua = line:match("^(%S+)%s+(%d+)%s+(.-)SEQSEQ(.-)%s*$")
|
||||
if host and count then
|
||||
table.insert(stats, {host = host, count = count, origin_ua = origin_ua, mocked_ua = mocked_ua})
|
||||
table.insert(rewrite_stats, {host = host, count = count, origin_ua = origin_ua, mocked_ua = mocked_ua})
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
rewrite_stats_file:close()
|
||||
end
|
||||
|
||||
local pass_stats = {}
|
||||
local pass_stats_file = io.open("/var/log/ua3f/passthrough_stats", "r")
|
||||
if pass_stats_file then
|
||||
for line in pass_stats_file:lines() do
|
||||
local host, count, ua = line:match("^(%S+)%s(%d+)%s(.+)$")
|
||||
if ua and count then
|
||||
table.insert(pass_stats, {ua = ua, count = count, host = host})
|
||||
end
|
||||
end
|
||||
pass_stats_file:close()
|
||||
end
|
||||
|
||||
local function rowstyle(i)
|
||||
@ -17,9 +29,9 @@ end
|
||||
%>
|
||||
|
||||
<h3><%:Statistics%></h3>
|
||||
<div class="cbi-section-descr"><%:User-Agent Rewrite Statistics%></div>
|
||||
|
||||
<table id="stats-table" class="table cbi-section-table">
|
||||
<div class="cbi-section-descr"><%:User-Agent Rewrite Statistics%></div>
|
||||
<table id="rewrite-stats-table" class="table cbi-section-table">
|
||||
<tr class="tr table-titles">
|
||||
<th class="th"><%:Host%></th>
|
||||
<th class="th"><%:Rewrite Count%></th>
|
||||
@ -27,7 +39,7 @@ end
|
||||
<th class="th"><%:Modified User-Agent%></th>
|
||||
</tr>
|
||||
|
||||
<% for i, item in ipairs(stats) do %>
|
||||
<% for i, item in ipairs(rewrite_stats) do %>
|
||||
<tr class="tr <%= rowstyle(i) %>">
|
||||
<td class="td" data-title="<%:Host%>"><span><%= item.host %></span></td>
|
||||
<td class="td" data-title="<%:Rewrite Count%>"><%= item.count %></td>
|
||||
@ -37,6 +49,24 @@ end
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<div class="cbi-section-descr"><%:User-Agent Pass-Through Statistics%></div>
|
||||
<table id="pass-stats-table" class="table cbi-section-table">
|
||||
<tr class="tr table-titles">
|
||||
<th class="th"><%:User-Agent%></th>
|
||||
<th class="th"><%:Pass-Through Count%></th>
|
||||
<th class="th"><%:Last Host%></th>
|
||||
</tr>
|
||||
|
||||
<% for i, item in ipairs(pass_stats) do %>
|
||||
<tr class="tr <%= rowstyle(i) %>">
|
||||
<td class="td" data-title="<%:User-Agent%>"><span><%= item.ua %></span></td>
|
||||
<td class="td" data-title="<%:Pass-Through Count%>"><%= item.count %></td>
|
||||
<td class="td" data-title="<%:Last Host%>"><span><%= item.host %></span></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
async function updateStats() {
|
||||
try {
|
||||
@ -45,10 +75,14 @@ end
|
||||
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, "text/html");
|
||||
const newTable = doc.querySelector("#stats-table");
|
||||
const newRewriteTable = doc.querySelector("#rewrite-stats-table");
|
||||
const newPassTable = doc.querySelector("#pass-stats-table");
|
||||
|
||||
if (newTable) {
|
||||
document.querySelector("#stats-table").innerHTML = newTable.innerHTML;
|
||||
if (newRewriteTable) {
|
||||
document.querySelector("#rewrite-stats-table").innerHTML = newRewriteTable.innerHTML;
|
||||
}
|
||||
if (newPassTable) {
|
||||
document.querySelector("#pass-stats-table").innerHTML = newPassTable.innerHTML;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("update stats error:", err);
|
||||
@ -58,25 +92,40 @@ end
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#stats-table th:nth-child(1),
|
||||
#stats-table td:nth-child(1) {
|
||||
#rewrite-stats-table th:nth-child(1),
|
||||
#rewrite-stats-table td:nth-child(1) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
#stats-table th:nth-child(2),
|
||||
#stats-table td:nth-child(2) {
|
||||
#rewrite-stats-table th:nth-child(2),
|
||||
#rewrite-stats-table td:nth-child(2) {
|
||||
width: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#stats-table th:nth-child(3),
|
||||
#stats-table td:nth-child(3) {
|
||||
#rewrite-stats-table th:nth-child(3),
|
||||
#rewrite-stats-table td:nth-child(3) {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
#stats-table th:nth-child(4),
|
||||
#stats-table td:nth-child(4) {
|
||||
#rewrite-stats-table th:nth-child(4),
|
||||
#rewrite-stats-table td:nth-child(4) {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
#pass-stats-table th:nth-child(1),
|
||||
#pass-stats-table td:nth-child(1) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#pass-stats-table th:nth-child(2),
|
||||
#pass-stats-table td:nth-child(2) {
|
||||
width: 20%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#pass-stats-table th:nth-child(3),
|
||||
#pass-stats-table td:nth-child(3) {
|
||||
width: 30%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -64,3 +64,12 @@ msgstr "原始 User-Agent"
|
||||
|
||||
msgid "Modified User-Agent"
|
||||
msgstr "重写后 User-Agent"
|
||||
|
||||
msgid "User-Agent Pass-Through Statistics"
|
||||
msgstr "User-Agent 放行次数实时统计"
|
||||
|
||||
msgid "Pass-Through Count"
|
||||
msgstr "放行次数"
|
||||
|
||||
msgid "Last Host"
|
||||
msgstr "最后访问地址"
|
||||
|
||||
@ -148,6 +148,10 @@ func (r *Rewriter) ProxyHTTPOrRaw(dst net.Conn, src net.Conn, destAddrPort strin
|
||||
destAddrPort, srcAddr, originalUA, r.cache.Len())
|
||||
r.cache.Add(destAddrPort, destAddrPort)
|
||||
}
|
||||
statistics.AddPassThroughRecord(&statistics.PassThroughRecord{
|
||||
Host: destAddrPort,
|
||||
UA: originalUA,
|
||||
})
|
||||
if err := req.Write(dst); err != nil {
|
||||
logrus.Errorf("[%s][%s] write error: %s", destAddrPort, srcAddr, err.Error())
|
||||
return err
|
||||
@ -166,9 +170,9 @@ func (r *Rewriter) ProxyHTTPOrRaw(dst net.Conn, src net.Conn, destAddrPort strin
|
||||
return err
|
||||
}
|
||||
|
||||
statistics.AddStat(&statistics.StatRecord{
|
||||
statistics.AddRewriteRecord(&statistics.RewriteRecord{
|
||||
Host: destAddrPort,
|
||||
OriginUA: originalUA,
|
||||
OriginalUA: originalUA,
|
||||
MockedUA: mockedUA,
|
||||
})
|
||||
}
|
||||
@ -196,7 +200,7 @@ func (r *Rewriter) isHTTP(reader *bufio.Reader) (bool, error) {
|
||||
|
||||
// buildNewUA returns either a partial replacement (regex) or full overwrite.
|
||||
func (r *Rewriter) buildNewUA(originUA string) string {
|
||||
if r.enablePartialReplace && r.uaRegex != nil {
|
||||
if r.enablePartialReplace && r.uaRegex != nil && r.pattern != "" {
|
||||
newUA, err := r.uaRegex.Replace(originUA, r.payloadUA, -1, -1)
|
||||
if err != nil {
|
||||
logrus.Errorf("User-Agent Replace Error: %s, use full overwrite", err.Error())
|
||||
|
||||
@ -56,7 +56,7 @@ func (s *Server) Start() (err error) {
|
||||
}
|
||||
|
||||
// Start statistics worker
|
||||
go statistics.StartStatWorker()
|
||||
go statistics.StartRecorder()
|
||||
|
||||
var client net.Conn
|
||||
for {
|
||||
|
||||
48
src/internal/statistics/pass.go
Normal file
48
src/internal/statistics/pass.go
Normal file
@ -0,0 +1,48 @@
|
||||
package statistics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const passthroughStatsFile = "/var/log/ua3f/passthrough_stats"
|
||||
|
||||
type PassThroughRecord struct {
|
||||
Host string
|
||||
UA string
|
||||
Count int
|
||||
}
|
||||
|
||||
var passThroughRecords = make(map[string]*PassThroughRecord)
|
||||
|
||||
func AddPassThroughRecord(record *PassThroughRecord) {
|
||||
select {
|
||||
case passThroughRecordChan <- *record:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func dumpPassThroughRecords() {
|
||||
f, err := os.Create(passthroughStatsFile)
|
||||
if err != nil {
|
||||
logrus.Errorf("create stats file error: %v", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var statList []PassThroughRecord
|
||||
for _, record := range passThroughRecords {
|
||||
statList = append(statList, *record)
|
||||
}
|
||||
sort.SliceStable(statList, func(i, j int) bool {
|
||||
return statList[i].Count > statList[j].Count
|
||||
})
|
||||
|
||||
for _, record := range statList {
|
||||
line := fmt.Sprintf("%s %d %s\n", record.Host, record.Count, record.UA)
|
||||
f.WriteString(line)
|
||||
}
|
||||
}
|
||||
49
src/internal/statistics/rewrite.go
Normal file
49
src/internal/statistics/rewrite.go
Normal file
@ -0,0 +1,49 @@
|
||||
package statistics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const rewriteStatsFile = "/var/log/ua3f/rewrite_stats"
|
||||
|
||||
type RewriteRecord struct {
|
||||
Host string
|
||||
Count int
|
||||
OriginalUA string
|
||||
MockedUA string
|
||||
}
|
||||
|
||||
var rewriteRecords = make(map[string]*RewriteRecord)
|
||||
|
||||
func AddRewriteRecord(record *RewriteRecord) {
|
||||
select {
|
||||
case rewriteRecordChan <- *record:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func dumpRewriteRecords() {
|
||||
f, err := os.Create(rewriteStatsFile)
|
||||
if err != nil {
|
||||
logrus.Errorf("create stats file error: %v", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var statList []RewriteRecord
|
||||
for _, record := range rewriteRecords {
|
||||
statList = append(statList, *record)
|
||||
}
|
||||
sort.SliceStable(statList, func(i, j int) bool {
|
||||
return statList[i].Count > statList[j].Count
|
||||
})
|
||||
|
||||
for _, record := range statList {
|
||||
line := fmt.Sprintf("%s %d %sSEQSEQ%s\n", record.Host, record.Count, record.OriginalUA, record.MockedUA)
|
||||
f.WriteString(line)
|
||||
}
|
||||
}
|
||||
@ -1,78 +1,47 @@
|
||||
package statistics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type StatRecord struct {
|
||||
Host string
|
||||
Count int
|
||||
OriginUA string
|
||||
MockedUA string
|
||||
}
|
||||
|
||||
var (
|
||||
statChan = make(chan StatRecord, 3000)
|
||||
stats = make(map[string]*StatRecord)
|
||||
rewriteRecordChan = make(chan RewriteRecord, 2000)
|
||||
passThroughRecordChan = make(chan PassThroughRecord, 2000)
|
||||
)
|
||||
|
||||
const statsFile = "/var/log/ua3f/stats"
|
||||
|
||||
func StartStatWorker() {
|
||||
func StartRecorder() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case dest := <-statChan:
|
||||
if record, exists := stats[dest.Host]; exists {
|
||||
record.Count++
|
||||
record.OriginUA = dest.OriginUA
|
||||
record.MockedUA = dest.MockedUA
|
||||
case record := <-rewriteRecordChan:
|
||||
if r, exists := rewriteRecords[record.Host]; exists {
|
||||
r.Count++
|
||||
r.OriginalUA = record.OriginalUA
|
||||
r.MockedUA = record.MockedUA
|
||||
} else {
|
||||
stats[dest.Host] = &StatRecord{
|
||||
Host: dest.Host,
|
||||
rewriteRecords[record.Host] = &RewriteRecord{
|
||||
Host: record.Host,
|
||||
Count: 1,
|
||||
OriginalUA: record.OriginalUA,
|
||||
MockedUA: record.MockedUA,
|
||||
}
|
||||
}
|
||||
case record := <-passThroughRecordChan:
|
||||
if r, exists := passThroughRecords[record.UA]; exists {
|
||||
r.Count++
|
||||
r.Host = record.Host
|
||||
} else {
|
||||
passThroughRecords[record.UA] = &PassThroughRecord{
|
||||
Host: record.Host,
|
||||
UA: record.UA,
|
||||
Count: 1,
|
||||
OriginUA: dest.OriginUA,
|
||||
MockedUA: dest.MockedUA,
|
||||
}
|
||||
}
|
||||
case <-ticker.C:
|
||||
dumpStatsToFile()
|
||||
dumpRewriteRecords()
|
||||
dumpPassThroughRecords()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AddStat(dest *StatRecord) {
|
||||
select {
|
||||
case statChan <- *dest:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func dumpStatsToFile() {
|
||||
f, err := os.Create(statsFile)
|
||||
if err != nil {
|
||||
logrus.Errorf("create stats file error: %v", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var statList []StatRecord
|
||||
for _, record := range stats {
|
||||
statList = append(statList, *record)
|
||||
}
|
||||
sort.SliceStable(statList, func(i, j int) bool {
|
||||
return statList[i].Count > statList[j].Count
|
||||
})
|
||||
|
||||
for _, record := range statList {
|
||||
line := fmt.Sprintf("%s %d %sSEQSEQ%s\n", record.Host, record.Count, record.OriginUA, record.MockedUA)
|
||||
f.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user