mirror of
https://github.com/amir20/dozzle.git
synced 2026-06-23 04:10:12 +00:00
feat: adds network usage for each container (#4340)
This commit is contained in:
Vendored
+2
@@ -122,6 +122,8 @@ declare module 'vue' {
|
||||
'Ph:globeSimple': typeof import('~icons/ph/globe-simple')['default']
|
||||
'Ph:stack': typeof import('~icons/ph/stack')['default']
|
||||
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
|
||||
PhArrowDown: typeof import('~icons/ph/arrow-down')['default']
|
||||
PhArrowUp: typeof import('~icons/ph/arrow-up')['default']
|
||||
Popup: typeof import('./components/Popup.vue')['default']
|
||||
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
|
||||
RelativeTime: typeof import('./components/common/RelativeTime.vue')['default']
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="flex gap-1 md:gap-4">
|
||||
<div class="grid min-w-15 grid-cols-[auto_1fr] items-center gap-0.5 text-xs leading-none max-md:hidden">
|
||||
<PhArrowUp class="text-primary" />
|
||||
<span class="tabular-nums">{{ formatBytes(networkRate.tx, { short: true, decimals: 1 }) }}/s</span>
|
||||
<PhArrowDown class="text-secondary" />
|
||||
<span class="tabular-nums">{{ formatBytes(networkRate.rx, { short: true, decimals: 1 }) }}/s</span>
|
||||
</div>
|
||||
<StatMonitor
|
||||
:data="cpuData"
|
||||
:icon="PhCpu"
|
||||
@@ -35,9 +41,10 @@ const { containers } = defineProps<{
|
||||
containers: Container[];
|
||||
}>();
|
||||
|
||||
const totalStat = ref<Stat>({ cpu: 0, memory: 0, memoryUsage: 0 });
|
||||
const totalStat = ref<Stat>({ cpu: 0, memory: 0, memoryUsage: 0, networkRxTotal: 0, networkTxTotal: 0 });
|
||||
const { history, reset } = useSimpleRefHistory(totalStat, { capacity: 300 });
|
||||
const { hosts } = useHosts();
|
||||
const networkRate = ref({ rx: 0, tx: 0 });
|
||||
|
||||
const roundCPU = (num: number) => (Number.isInteger(num) ? num.toFixed(0) : num.toFixed(1));
|
||||
|
||||
@@ -65,12 +72,15 @@ watch(
|
||||
cpu: acc.cpu + item.cpu / cores,
|
||||
memory: acc.memory + item.memory,
|
||||
memoryUsage: acc.memoryUsage + item.memoryUsage,
|
||||
networkRxTotal: acc.networkRxTotal + item.networkRxTotal,
|
||||
networkTxTotal: acc.networkTxTotal + item.networkTxTotal,
|
||||
};
|
||||
},
|
||||
{ cpu: 0, memory: 0, memoryUsage: 0 },
|
||||
{ cpu: 0, memory: 0, memoryUsage: 0, networkRxTotal: 0, networkTxTotal: 0 },
|
||||
);
|
||||
initial.push(stat);
|
||||
}
|
||||
totalStat.value = initial[0];
|
||||
reset({ initial: initial.reverse() });
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -92,6 +102,7 @@ const limits = computed(() => {
|
||||
});
|
||||
|
||||
useIntervalFn(() => {
|
||||
const previousStat = totalStat.value;
|
||||
totalStat.value = containers.reduce(
|
||||
(acc, container) => {
|
||||
const cores = toContainerCores(container);
|
||||
@@ -99,10 +110,17 @@ useIntervalFn(() => {
|
||||
cpu: acc.cpu + container.stat.cpu / cores,
|
||||
memory: acc.memory + container.stat.memory,
|
||||
memoryUsage: acc.memoryUsage + container.stat.memoryUsage,
|
||||
networkRxTotal: acc.networkRxTotal + container.stat.networkRxTotal,
|
||||
networkTxTotal: acc.networkTxTotal + container.stat.networkTxTotal,
|
||||
};
|
||||
},
|
||||
{ cpu: 0, memory: 0, memoryUsage: 0 },
|
||||
{ cpu: 0, memory: 0, memoryUsage: 0, networkRxTotal: 0, networkTxTotal: 0 },
|
||||
);
|
||||
|
||||
networkRate.value = {
|
||||
rx: Math.max(0, totalStat.value.networkRxTotal - previousStat.networkRxTotal),
|
||||
tx: Math.max(0, totalStat.value.networkTxTotal - previousStat.networkTxTotal),
|
||||
};
|
||||
}, 1000);
|
||||
|
||||
const cpuData = computed(() =>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="bg-base-200 flex gap-1 rounded-sm p-px text-xs md:absolute md:-top-2 md:-left-0.5">
|
||||
<component :is="icon" class="text-sm" />
|
||||
<div class="font-bold select-none">
|
||||
<div class="font-bold tabular-nums select-none">
|
||||
{{ displayValue }}
|
||||
<span v-if="limit !== -1 && !mouseOver" class="max-md:hidden"> / {{ limit }} </span>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<component :is="icon" class="text-lg" />
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
<div class="mb-1.5 text-lg font-semibold">{{ formattedValue }}</div>
|
||||
<div class="text-base-content/60 mb-1 text-xs max-md:hidden">
|
||||
<div class="mb-1.5 text-lg font-semibold tabular-nums">{{ formattedValue }}</div>
|
||||
<div class="text-base-content/60 mb-1 text-xs tabular-nums max-md:hidden">
|
||||
avg {{ formatValue(average) }} • pk {{ formatValue(peak) }}
|
||||
</div>
|
||||
<BarChart class="h-8" :chartData="percentData" :barClass="barClass" />
|
||||
|
||||
@@ -51,7 +51,9 @@ export class Container {
|
||||
public readonly group?: string,
|
||||
public health?: ContainerHealth,
|
||||
) {
|
||||
this._stat = ref(stats.at(-1) || ({ cpu: 0, memory: 0, memoryUsage: 0 } as Stat));
|
||||
this._stat = ref(
|
||||
stats.at(-1) || ({ cpu: 0, memory: 0, memoryUsage: 0, networkRxTotal: 0, networkTxTotal: 0 } as Stat),
|
||||
);
|
||||
const { history } = useSimpleRefHistory(this._stat, { capacity: 300, deep: true, initial: stats });
|
||||
this._statsHistory = history;
|
||||
const { movingAverage } = useExponentialMovingAverage(this._stat, 0.2);
|
||||
|
||||
Vendored
+2
@@ -3,6 +3,8 @@ export interface ContainerStat {
|
||||
readonly cpu: number;
|
||||
readonly memory: number;
|
||||
readonly memoryUsage: number;
|
||||
readonly networkRxTotal: number;
|
||||
readonly networkTxTotal: number;
|
||||
}
|
||||
|
||||
export type ContainerJson = {
|
||||
|
||||
@@ -261,13 +261,15 @@ func (x *Container) GetFullyLoaded() bool {
|
||||
}
|
||||
|
||||
type ContainerStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"`
|
||||
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
|
||||
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
CpuPercent float64 `protobuf:"fixed64,2,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"`
|
||||
MemoryUsage float64 `protobuf:"fixed64,3,opt,name=memoryUsage,proto3" json:"memoryUsage,omitempty"`
|
||||
MemoryPercent float64 `protobuf:"fixed64,4,opt,name=memoryPercent,proto3" json:"memoryPercent,omitempty"`
|
||||
NetworkRxTotal uint64 `protobuf:"varint,5,opt,name=networkRxTotal,proto3" json:"networkRxTotal,omitempty"`
|
||||
NetworkTxTotal uint64 `protobuf:"varint,6,opt,name=networkTxTotal,proto3" json:"networkTxTotal,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ContainerStat) Reset() {
|
||||
@@ -328,6 +330,20 @@ func (x *ContainerStat) GetMemoryPercent() float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ContainerStat) GetNetworkRxTotal() uint64 {
|
||||
if x != nil {
|
||||
return x.NetworkRxTotal
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ContainerStat) GetNetworkTxTotal() uint64 {
|
||||
if x != nil {
|
||||
return x.NetworkTxTotal
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type LogFragment struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
@@ -832,14 +848,16 @@ const file_types_proto_rawDesc = "" +
|
||||
"\vfullyLoaded\x18\x13 \x01(\bR\vfullyLoaded\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x87\x01\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd7\x01\n" +
|
||||
"\rContainerStat\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x1e\n" +
|
||||
"\n" +
|
||||
"cpuPercent\x18\x02 \x01(\x01R\n" +
|
||||
"cpuPercent\x12 \n" +
|
||||
"\vmemoryUsage\x18\x03 \x01(\x01R\vmemoryUsage\x12$\n" +
|
||||
"\rmemoryPercent\x18\x04 \x01(\x01R\rmemoryPercent\"'\n" +
|
||||
"\rmemoryPercent\x18\x04 \x01(\x01R\rmemoryPercent\x12&\n" +
|
||||
"\x0enetworkRxTotal\x18\x05 \x01(\x04R\x0enetworkRxTotal\x12&\n" +
|
||||
"\x0enetworkTxTotal\x18\x06 \x01(\x04R\x0enetworkTxTotal\"'\n" +
|
||||
"\vLogFragment\x12\x18\n" +
|
||||
"\amessage\x18\x01 \x01(\tR\amessage\"\x88\x02\n" +
|
||||
"\bLogEvent\x12\x0e\n" +
|
||||
|
||||
@@ -171,10 +171,12 @@ func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_Stre
|
||||
case stat := <-stats:
|
||||
out.Send(&pb.StreamStatsResponse{
|
||||
Stat: &pb.ContainerStat{
|
||||
Id: stat.ID,
|
||||
CpuPercent: stat.CPUPercent,
|
||||
MemoryPercent: stat.MemoryPercent,
|
||||
MemoryUsage: stat.MemoryUsage,
|
||||
Id: stat.ID,
|
||||
CpuPercent: stat.CPUPercent,
|
||||
MemoryPercent: stat.MemoryPercent,
|
||||
MemoryUsage: stat.MemoryUsage,
|
||||
NetworkRxTotal: stat.NetworkRxTotal,
|
||||
NetworkTxTotal: stat.NetworkTxTotal,
|
||||
},
|
||||
})
|
||||
case <-out.Context().Done():
|
||||
|
||||
@@ -98,10 +98,12 @@ func FromProto(c *pb.Container) Container {
|
||||
|
||||
// ContainerStat represent stats instant for a container
|
||||
type ContainerStat struct {
|
||||
ID string `json:"id"`
|
||||
CPUPercent float64 `json:"cpu"`
|
||||
MemoryPercent float64 `json:"memory"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
ID string `json:"id"`
|
||||
CPUPercent float64 `json:"cpu"`
|
||||
MemoryPercent float64 `json:"memory"`
|
||||
MemoryUsage float64 `json:"memoryUsage"`
|
||||
NetworkRxTotal uint64 `json:"networkRxTotal"`
|
||||
NetworkTxTotal uint64 `json:"networkTxTotal"`
|
||||
}
|
||||
|
||||
// ContainerEvent represents events that are triggered
|
||||
|
||||
@@ -198,6 +198,7 @@ func (d *DockerClient) ContainerStats(ctx context.Context, id string, stats chan
|
||||
mem, memLimit float64
|
||||
previousCPU uint64
|
||||
previousSystem uint64
|
||||
networkRx, networkTx uint64
|
||||
)
|
||||
daemonOSType := response.OSType
|
||||
|
||||
@@ -213,15 +214,23 @@ func (d *DockerClient) ContainerStats(ctx context.Context, id string, stats chan
|
||||
mem = float64(v.MemoryStats.PrivateWorkingSet)
|
||||
}
|
||||
|
||||
// Calculate total network bytes across all interfaces
|
||||
for _, netStats := range v.Networks {
|
||||
networkRx += netStats.RxBytes
|
||||
networkTx += netStats.TxBytes
|
||||
}
|
||||
|
||||
if cpuPercent > 0 || mem > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case stats <- container.ContainerStat{
|
||||
ID: id,
|
||||
CPUPercent: cpuPercent,
|
||||
MemoryPercent: memPercent,
|
||||
MemoryUsage: mem,
|
||||
ID: id,
|
||||
CPUPercent: cpuPercent,
|
||||
MemoryPercent: memPercent,
|
||||
MemoryUsage: mem,
|
||||
NetworkRxTotal: networkRx,
|
||||
NetworkTxTotal: networkTx,
|
||||
}:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,8 @@ func (k *K8sClient) ContainerEvents(ctx context.Context, ch chan<- container.Con
|
||||
}
|
||||
|
||||
func (k *K8sClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
|
||||
panic("not implemented")
|
||||
// Stats collection is implemented in stats_collector.go using K8s metrics API
|
||||
panic("not implemented - use K8sStatsCollector instead")
|
||||
}
|
||||
|
||||
func (k *K8sClient) Ping(ctx context.Context) error {
|
||||
|
||||
@@ -105,9 +105,11 @@ func (sc *K8sStatsCollector) Start(parentCtx context.Context) bool {
|
||||
for _, pod := range metricList.Items {
|
||||
for _, c := range pod.Containers {
|
||||
stat := container.ContainerStat{
|
||||
ID: pod.Namespace + ":" + pod.Name + ":" + c.Name,
|
||||
CPUPercent: float64(c.Usage.Cpu().MilliValue()) / 1000 * 100,
|
||||
MemoryUsage: c.Usage.Memory().AsApproximateFloat64(),
|
||||
ID: pod.Namespace + ":" + pod.Name + ":" + c.Name,
|
||||
CPUPercent: float64(c.Usage.Cpu().MilliValue()) / 1000 * 100,
|
||||
MemoryUsage: c.Usage.Memory().AsApproximateFloat64(),
|
||||
NetworkRxTotal: 0, // K8s metrics API doesn't expose network stats by default
|
||||
NetworkTxTotal: 0, // Would require custom metrics or cAdvisor integration
|
||||
}
|
||||
log.Trace().Interface("stat", stat).Msg("k8s stats")
|
||||
sc.subscribers.Range(func(c context.Context, stats chan<- container.ContainerStat) bool {
|
||||
|
||||
@@ -34,6 +34,8 @@ message ContainerStat {
|
||||
double cpuPercent = 2;
|
||||
double memoryUsage = 3;
|
||||
double memoryPercent = 4;
|
||||
uint64 networkRxTotal = 5;
|
||||
uint64 networkTxTotal = 6;
|
||||
}
|
||||
|
||||
message LogFragment {
|
||||
|
||||
Reference in New Issue
Block a user