first commit
This commit is contained in:
146
apis/docker/docker.go
Normal file
146
apis/docker/docker.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"ArinDash/util"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type ContainerMetrics struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Status string
|
||||
CPUPercent float64
|
||||
MemUsage uint64
|
||||
MemLimit uint64
|
||||
MemPercent float64
|
||||
NetRx uint64
|
||||
NetTx uint64
|
||||
BlockRead uint64
|
||||
BlockWrite uint64
|
||||
PidsCurrent uint64
|
||||
}
|
||||
|
||||
func newDockerClient() (*client.Client, error) {
|
||||
return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
}
|
||||
|
||||
func FetchDockerMetrics(ctx context.Context) ([]ContainerMetrics, error) {
|
||||
cli, err := newDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
_, err = cli.Ping(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]ContainerMetrics, 0, len(containers))
|
||||
for _, c := range containers {
|
||||
m := ContainerMetrics{
|
||||
ID: c.ID,
|
||||
Image: c.Image,
|
||||
Name: containerName(c.Names),
|
||||
Status: c.Status,
|
||||
}
|
||||
|
||||
stats, err := cli.ContainerStats(ctx, c.ID, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
func() {
|
||||
defer stats.Body.Close()
|
||||
var sj containertypes.StatsResponse
|
||||
if err := util.DecodeJSON(stats.Body, &sj); err != nil {
|
||||
return
|
||||
}
|
||||
m.CPUPercent = cpuPercentFromStats(sj)
|
||||
m.MemUsage, m.MemLimit, m.MemPercent = memoryFromStats(sj)
|
||||
m.NetRx, m.NetTx = networkTotals(sj)
|
||||
m.BlockRead, m.BlockWrite = blockIOTotals(sj)
|
||||
m.PidsCurrent = sj.PidsStats.Current
|
||||
}()
|
||||
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func containerName(names []string) string {
|
||||
for _, n := range names {
|
||||
n = strings.TrimSpace(n)
|
||||
n = strings.TrimPrefix(n, "/")
|
||||
if n != "" {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func cpuPercentFromStats(s containertypes.StatsResponse) float64 {
|
||||
// Protect against zero or missing data.
|
||||
if s.PreCPUStats.SystemUsage == 0 || s.CPUStats.SystemUsage == 0 {
|
||||
return 0
|
||||
}
|
||||
cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage - s.PreCPUStats.CPUUsage.TotalUsage)
|
||||
systemDelta := float64(s.CPUStats.SystemUsage - s.PreCPUStats.SystemUsage)
|
||||
if systemDelta <= 0 || cpuDelta < 0 {
|
||||
return 0
|
||||
}
|
||||
// Number of CPUs available to the container.
|
||||
numCPU := float64(len(s.CPUStats.CPUUsage.PercpuUsage))
|
||||
if numCPU == 0 {
|
||||
numCPU = 1
|
||||
}
|
||||
return (cpuDelta / systemDelta) * numCPU * 100.0
|
||||
}
|
||||
|
||||
func memoryFromStats(s containertypes.StatsResponse) (usage, limit uint64, percent float64) {
|
||||
usage = s.MemoryStats.Usage
|
||||
limit = s.MemoryStats.Limit
|
||||
// Optionally discount cached memory if stats present.
|
||||
if stats := s.MemoryStats.Stats; stats != nil {
|
||||
// The common Linux approach: usage - cache
|
||||
if cache, ok := stats["cache"]; ok && cache <= usage {
|
||||
usage -= cache
|
||||
}
|
||||
}
|
||||
if limit > 0 {
|
||||
percent = (float64(usage) / float64(limit)) * 100
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func networkTotals(s containertypes.StatsResponse) (rx, tx uint64) {
|
||||
if s.Networks == nil {
|
||||
return 0, 0
|
||||
}
|
||||
for _, v := range s.Networks {
|
||||
rx += v.RxBytes
|
||||
tx += v.TxBytes
|
||||
}
|
||||
return rx, tx
|
||||
}
|
||||
|
||||
func blockIOTotals(s containertypes.StatsResponse) (read, write uint64) {
|
||||
for _, e := range s.BlkioStats.IoServiceBytesRecursive {
|
||||
switch strings.ToLower(e.Op) {
|
||||
case "read":
|
||||
read += e.Value
|
||||
case "write":
|
||||
write += e.Value
|
||||
}
|
||||
}
|
||||
return read, write
|
||||
}
|
||||
44
apis/pihole/info.go
Normal file
44
apis/pihole/info.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package pihole
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Version Versions `json:"version"`
|
||||
}
|
||||
|
||||
type Versions struct {
|
||||
Core ModuleVersion `json:"core"`
|
||||
Web ModuleVersion `json:"web"`
|
||||
FTL ModuleVersion `json:"FTL"`
|
||||
Docker DockerVersion `json:"docker"`
|
||||
}
|
||||
|
||||
type ModuleVersion struct {
|
||||
Local LocalVersion `json:"local"`
|
||||
Remote LocalVersion `json:"remote"`
|
||||
}
|
||||
|
||||
type LocalVersion struct {
|
||||
Branch string `json:"branch"`
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type DockerVersion struct {
|
||||
Local string `json:"local"`
|
||||
Remote string `json:"remote"`
|
||||
}
|
||||
|
||||
func (ph *PiHConnector) Version() Version {
|
||||
version := &Version{}
|
||||
|
||||
err := json.Unmarshal(ph.get("info/version"), version)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return *version
|
||||
}
|
||||
100
apis/pihole/pihole.go
Normal file
100
apis/pihole/pihole.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package pihole
|
||||
|
||||
import (
|
||||
"ArinDash/config"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type configFile struct {
|
||||
Pihole piholeConfig
|
||||
}
|
||||
|
||||
type piholeConfig struct {
|
||||
Host string
|
||||
Password string
|
||||
}
|
||||
|
||||
type PiHConnector struct {
|
||||
Host string
|
||||
Session PiHSession
|
||||
}
|
||||
|
||||
type PiHAuth struct {
|
||||
Session PiHSession `json:"session"`
|
||||
}
|
||||
|
||||
type PiHSession struct {
|
||||
SID string `json:"sid"`
|
||||
CSRF string `json:"csrf"`
|
||||
}
|
||||
|
||||
func (ph *PiHConnector) get(endpoint string) []byte {
|
||||
return ph.do("GET", endpoint, nil)
|
||||
}
|
||||
|
||||
func (ph *PiHConnector) post(endpoint string, body io.Reader) []byte {
|
||||
return ph.do("POST", endpoint, body)
|
||||
}
|
||||
|
||||
func (ph *PiHConnector) do(method string, endpoint string, body io.Reader) []byte {
|
||||
var requestString = "http://" + ph.Host + "/api/" + endpoint
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest(method, requestString, body)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
req.Header.Add("X-FTL-SID", ph.Session.SID)
|
||||
req.Header.Add("X-FTL-CSRF", ph.Session.CSRF)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return respBody
|
||||
}
|
||||
|
||||
func Connect() PiHConnector {
|
||||
cfg := &configFile{}
|
||||
config.LoadConfig(cfg)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "http://"+cfg.Pihole.Host+"/api/auth", strings.NewReader("{\"password\": \""+cfg.Pihole.Password+"\"}"))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s := &PiHAuth{}
|
||||
|
||||
err = json.Unmarshal(respBody, s)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return PiHConnector{
|
||||
Host: cfg.Pihole.Host,
|
||||
Session: s.Session,
|
||||
}
|
||||
}
|
||||
41
apis/pihole/stats.go
Normal file
41
apis/pihole/stats.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package pihole
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Queries Queries `json:"queries"`
|
||||
Clients Clients `json:"clients"`
|
||||
Gravity Gravity `json:"gravity"`
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
Total int64 `json:"total"`
|
||||
Blocked int64 `json:"blocked"`
|
||||
PercentBlocked float64 `json:"percent_blocked"`
|
||||
UniqueDomains int64 `json:"unique_domains"`
|
||||
Forwarded int64 `json:"forwarded"`
|
||||
Cached int64 `json:"cached"`
|
||||
}
|
||||
|
||||
type Clients struct {
|
||||
Active int64 `json:"active"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type Gravity struct {
|
||||
DomainsBeingBlocked int64 `json:"domains_being_blocked"`
|
||||
LastUpdated int64 `json:"last_updated"`
|
||||
}
|
||||
|
||||
func (ph *PiHConnector) Summary() Summary {
|
||||
summary := &Summary{}
|
||||
|
||||
err := json.Unmarshal(ph.get("stats/summary"), summary)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return *summary
|
||||
}
|
||||
Reference in New Issue
Block a user