Skip to content

Commit 99cae5b

Browse files
authored
Refactor (#2)
* handle errors * use DialContext * refactor dev env * server: use context for http request * server: refactor * server: create Agent client when needed * server: rename * server: rename * server: log * server: return err * server: do request with context * server: rename * server: do sequential requests to mysql and proxy * sever: define rawprocess * server: get raw processes inside a function * server: rename * server: log * add detail info * server: add cant connect case * server: omitempty * server: re-order the table * server: make detail a struct * server: define agent address early * server: save multiple details * server: create kimo processes inside server file * static: sort by id * server: convert combine method to function * config: delete unused configs * server: set metrics after process generation * server: consider tcpproxy absence * ui: group columns * server: log * server: use context * agent: move polling logic to poll.go * agent: use context * agent: convert method to func * server: remove force param * server: do polling in another file * server: define context above * server: use context * use buffered channels * agent: check only tcp connections * server: simplify metrics * server: format cmdline * use yaml config * pass only necessary config * delete unused flag * agent returns cmdline as string for the sake of simplicity * server: anonymize unknown cmdline * agent: delete unused field * show connection status * handle agent errors * server: check tcp proxy existence * server: pass IPPort to agent client * agent: rename * use mutex * server: rename * server: rename * server: define getters for simplicity * doc & organize * metric: rename * agent: create agent.go file to organize better * agent: organize
1 parent 4f39924 commit 99cae5b

27 files changed

+1012
-740
lines changed

Dockerfile-kimo

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
FROM golang:1.23
22

3-
RUN apt-get update && apt-get -y install default-mysql-client
3+
# Install system dependencies
4+
RUN apt-get update && \
5+
apt-get -y install default-mysql-client && \
6+
rm -rf /var/lib/apt/lists/*
47

58
ENV GOPATH=/go
69
ENV PATH=$PATH:$GOPATH/bin
710

8-
RUN mkdir /go/src/kimo
9-
COPY go.mod go.sum /go/src/kimo/
10-
COPY ./server/static/ /go/src/kimo/server/static
11+
WORKDIR /go/src/kimo
1112

12-
RUN cd /go/src/kimo && go install github.com/rakyll/statik
13-
RUN cd /go/src/kimo && /go/bin/statik -src=./server/static -include='*.html'
13+
# Copy dependency files first to leverage cache
14+
COPY go.mod go.sum ./
1415

15-
COPY . /go/src/kimo
16-
RUN cd /go/src/kimo && go install
16+
# Download dependencies separately
17+
RUN go mod download
1718

18-
ADD config.toml /etc/kimo.toml
19+
# Install statik before copying other files
20+
RUN go install github.com/rakyll/statik
21+
22+
# Copy only static files needed for statik
23+
COPY ./server/static/ ./server/static/
24+
RUN /go/bin/statik -src=./server/static -include='*.html'
25+
26+
# Copy remaining source code
27+
COPY . .
28+
29+
# Build the application
30+
RUN go build -o /go/bin/kimo
31+
32+
# Add config file
33+
COPY config.yaml /etc/kimo.yaml

Dockerfile-tcpproxy

Lines changed: 0 additions & 16 deletions
This file was deleted.

Makefile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ build:
77
go get github.com/rakyll/statik
88
$(GOPATH)/bin/statik -src=./server/static -include='*.html'
99
go install
10-
up:
10+
build-dependencies:
1111
docker-compose stop
1212
docker-compose rm -fsv
1313
docker-compose build mysql
1414
docker-compose build kimo
15-
docker-compose build kimo-agent
16-
docker-compose build kimo-server
17-
docker-compose build tcpproxy
18-
docker-compose up --scale kimo-agent=5
19-
15+
up:
16+
docker-compose stop
17+
docker-compose rm -fsv
18+
docker-compose build kimo
19+
docker-compose up --build kimo-server kimo-agent
2020
lint:
2121
golangci-lint run

agent/agent.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package agent
2+
3+
import (
4+
"context"
5+
"kimo/config"
6+
"net/http"
7+
"os"
8+
"sync"
9+
10+
"github.com/cenkalti/log"
11+
gopsutilNet "github.com/shirou/gopsutil/v4/net"
12+
)
13+
14+
// Agent is type for handling agent operations
15+
type Agent struct {
16+
Config *config.AgentConfig
17+
conns []gopsutilNet.ConnectionStat
18+
Hostname string
19+
mu sync.RWMutex // protects conns
20+
}
21+
22+
// NewAgent creates an returns a new Agent
23+
func NewAgent(cfg *config.AgentConfig) *Agent {
24+
d := new(Agent)
25+
d.Config = cfg
26+
d.Hostname = getHostname()
27+
return d
28+
}
29+
30+
// SetConns sets connections with lock.
31+
func (a *Agent) SetConns(conns []gopsutilNet.ConnectionStat) {
32+
a.mu.Lock()
33+
a.conns = conns
34+
a.mu.Unlock()
35+
}
36+
37+
// GetConns gets connections with lock.
38+
func (a *Agent) GetConns() []gopsutilNet.ConnectionStat {
39+
a.mu.RLock()
40+
defer a.mu.RUnlock()
41+
return a.conns
42+
}
43+
44+
// getHostname returns hostname.
45+
func getHostname() string {
46+
hostname, err := os.Hostname()
47+
if err != nil {
48+
log.Errorf("Hostname could not found")
49+
hostname = "UNKNOWN"
50+
}
51+
return hostname
52+
}
53+
54+
// Run starts the http server and begins listening for HTTP requests.
55+
func (a *Agent) Run() error {
56+
ctx, cancel := context.WithCancel(context.Background())
57+
defer cancel()
58+
59+
go a.pollConns(ctx)
60+
61+
http.HandleFunc("/proc", a.Process)
62+
err := http.ListenAndServe(a.Config.ListenAddress, nil)
63+
if err != nil {
64+
log.Errorln(err.Error())
65+
return err
66+
}
67+
return nil
68+
}

agent/http.go

Lines changed: 35 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,29 @@ package agent
22

33
import (
44
"encoding/json"
5-
"kimo/config"
6-
"kimo/types"
75
"net/http"
8-
"os"
96
"strconv"
10-
"time"
117

128
"github.com/cenkalti/log"
139
gopsutilNet "github.com/shirou/gopsutil/v4/net"
1410
gopsutilProcess "github.com/shirou/gopsutil/v4/process"
1511
)
1612

17-
// Agent is type for handling agent operations
18-
type Agent struct {
19-
Config *config.Agent
20-
Conns []gopsutilNet.ConnectionStat
21-
Hostname string
13+
// Response contains basic process information for API responses.
14+
type Response struct {
15+
Status string `json:"status"`
16+
Pid int32 `json:"pid"`
17+
Name string `json:"name"`
18+
CmdLine string `json:"cmdline"`
2219
}
2320

24-
// NewAgent is constuctor function for Agent type
25-
func NewAgent(cfg *config.Config) *Agent {
26-
d := new(Agent)
27-
d.Config = &cfg.Agent
28-
d.Hostname = getHostname()
29-
return d
30-
}
31-
32-
func getHostname() string {
33-
hostname, err := os.Hostname()
34-
if err != nil {
35-
log.Errorf("Hostname could not found")
36-
hostname = "UNKNOWN"
37-
}
38-
return hostname
21+
// NetworkProcess represents process with its network connection.
22+
type NetworkProcess struct {
23+
process *gopsutilProcess.Process
24+
conn gopsutilNet.ConnectionStat
3925
}
4026

27+
// parsePortParam parses and returns port number from the request.
4128
func parsePortParam(w http.ResponseWriter, req *http.Request) (uint32, error) {
4229
portParam, ok := req.URL.Query()["port"]
4330
log.Debugf("Looking for process of port: %s\n", portParam)
@@ -55,13 +42,9 @@ func parsePortParam(w http.ResponseWriter, req *http.Request) (uint32, error) {
5542
return uint32(p), nil
5643
}
5744

58-
type hostProc struct {
59-
process *gopsutilProcess.Process
60-
conn gopsutilNet.ConnectionStat
61-
}
62-
63-
func (a *Agent) findProc(port uint32) *hostProc {
64-
for _, conn := range a.Conns {
45+
// findProcess finds process from connections by given port.
46+
func findProcess(port uint32, conns []gopsutilNet.ConnectionStat) *NetworkProcess {
47+
for _, conn := range conns {
6548
if conn.Laddr.Port != port {
6649
continue
6750
}
@@ -77,93 +60,55 @@ func (a *Agent) findProc(port uint32) *hostProc {
7760
return nil
7861
}
7962

80-
return &hostProc{
63+
return &NetworkProcess{
8164
process: process,
8265
conn: conn,
8366
}
8467
}
8568
return nil
8669
}
8770

88-
func (a *Agent) createAgentProcess(proc *hostProc) *types.AgentProcess {
89-
if proc == nil {
71+
// createResponse creates Response from given NetworkProcess parameter.
72+
func createResponse(np *NetworkProcess) *Response {
73+
if np == nil {
9074
return nil
9175
}
92-
name, err := proc.process.Name()
76+
name, err := np.process.Name()
9377
if err != nil {
9478
name = ""
9579
}
96-
cl, err := proc.process.CmdlineSlice()
80+
cmdline, err := np.process.Cmdline()
9781
if err != nil {
98-
log.Debugf("Cmdline could not found for %d\n", proc.process.Pid)
82+
log.Debugf("Cmdline could not found for %d\n", np.process.Pid)
9983
}
100-
return &types.AgentProcess{
101-
Laddr: types.IPPort{IP: proc.conn.Laddr.IP, Port: proc.conn.Laddr.Port},
102-
Status: proc.conn.Status,
103-
Pid: proc.conn.Pid,
104-
Name: name,
105-
CmdLine: cl,
106-
Hostname: a.Hostname,
84+
return &Response{
85+
Status: np.conn.Status,
86+
Pid: np.conn.Pid,
87+
Name: name,
88+
CmdLine: cmdline,
10789
}
10890
}
10991

11092
// Process is handler for serving process info
11193
func (a *Agent) Process(w http.ResponseWriter, req *http.Request) {
94+
w.Header().Set("X-Kimo-Hostname", a.Hostname)
95+
11296
// todo: cache result for a short period (10s? 30s?)
11397
port, err := parsePortParam(w, req)
11498
if err != nil {
11599
http.Error(w, "port param is required", http.StatusBadRequest)
116100
return
117101
}
118-
p := a.findProc(port)
119-
ap := a.createAgentProcess(p)
120-
121-
w.Header().Set("Content-Type", "application/json")
122-
if ap == nil {
123-
json.NewEncoder(w).Encode(&types.AgentProcess{
124-
Hostname: a.Hostname,
125-
})
102+
p := findProcess(port, a.GetConns())
103+
if p == nil {
104+
http.Error(w, "Connection not found", http.StatusNotFound)
126105
return
127106
}
128-
json.NewEncoder(w).Encode(&ap)
129-
return
130-
}
131107

132-
func (a *Agent) pollConns() {
133-
// todo: run with context
134-
log.Debugln("Polling...")
135-
ticker := time.NewTicker(a.Config.PollDuration * time.Second)
136-
137-
for {
138-
a.getConns() // poll immediately at the initialization
139-
select {
140-
// todo: add return case
141-
case <-ticker.C:
142-
a.getConns()
143-
}
144-
}
145-
146-
}
147-
func (a *Agent) getConns() {
148-
// This is an expensive operation.
149-
// So, we need to call it infrequent to prevent high load on servers those run kimo agents.
150-
conns, err := gopsutilNet.Connections("all")
151-
if err != nil {
152-
log.Errorln(err.Error())
153-
return
154-
}
155-
a.Conns = conns
156-
}
157-
158-
// Run is main function to run http server
159-
func (a *Agent) Run() error {
160-
go a.pollConns()
161-
162-
http.HandleFunc("/proc", a.Process)
163-
err := http.ListenAndServe(a.Config.ListenAddress, nil)
108+
w.Header().Set("Content-Type", "application/json")
109+
ap := createResponse(p)
110+
err = json.NewEncoder(w).Encode(&ap)
164111
if err != nil {
165-
log.Errorln(err.Error())
166-
return err
112+
http.Error(w, "Can not encode agent process", http.StatusInternalServerError)
167113
}
168-
return nil
169114
}

0 commit comments

Comments
 (0)