Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add archived-exporter component #81

Merged
merged 1 commit into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,15 @@ jobs:
ghcr.io/${{ github.repository }}/cli:${{ github.ref_name }}
ghcr.io/${{ github.repository }}/cli:${{ github.ref_name }}-${{ steps.timestamp.outputs.now }}
outputs: type=image,name=ghcr.io/${{ github.repository }}/cli,annotation-index.org.opencontainers.image.description=${{ github.repository }}
- name: Build and push exporter container image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.exporter
platforms: amd64
push: true
tags: |
ghcr.io/${{ github.repository }}/exporter:latest
ghcr.io/${{ github.repository }}/exporter:${{ github.ref_name }}
ghcr.io/${{ github.repository }}/exporter:${{ github.ref_name }}-${{ steps.timestamp.outputs.now }}
outputs: type=image,name=ghcr.io/${{ github.repository }}/exporter,annotation-index.org.opencontainers.image.description=${{ github.repository }}
8 changes: 8 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,11 @@ jobs:
platforms: amd64
push: false
outputs: type=image,name=ghcr.io/${{ github.repository }}/cli,annotation-index.org.opencontainers.image.description=${{ github.repository }}
- name: Build exporter container image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.exporter
platforms: amd64
push: false
outputs: type=image,name=ghcr.io/${{ github.repository }}/exporter,annotation-index.org.opencontainers.image.description=${{ github.repository }}
15 changes: 15 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ builds:
goamd64: ["v1", "v2", "v3"]
goarm: ["7"]
mod_timestamp: "{{ .CommitTimestamp }}"
- id: archived-exporter
main: ./cmd/exporter
binary: archived-exporter
ldflags:
- -s -w -X main.appVersion={{.Version}} -X main.buildTimestamp={{.Date}}
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
goamd64: ["v1", "v2", "v3"]
goarm: ["7"]
mod_timestamp: "{{ .CommitTimestamp }}"
archives:
- format: binary
checksum:
Expand Down
11 changes: 11 additions & 0 deletions Dockerfile.exporter
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM alpine:3.20.0 AS certificates

RUN apk add --update --no-cache \
ca-certificates=20240705-r0

FROM scratch

COPY --from=certificates /etc/ssl/cert.pem /etc/ssl/cert.pem
COPY --chmod=0755 --chown=root:root dist/archived-exporter_linux_amd64_v3/archived-exporter /archived-exporter

ENTRYPOINT [ "/archived-exporter" ]
73 changes: 73 additions & 0 deletions cmd/exporter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"context"
"database/sql"
"net/http"

"github.com/kelseyhightower/envconfig"
_ "github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"

"github.com/teran/archived/exporter/service"
"github.com/teran/archived/repositories/metadata/postgresql"
)

var (
appVersion = "n/a (dev build)"
buildTimestamp = "undefined"
)

type config struct {
Addr string `envconfig:"METRICS_ADDR" default:":8081"`

LogLevel log.Level `envconfig:"LOG_LEVEL" default:"info"`

MetadataDSN string `envconfig:"METADATA_DSN" required:"true"`
}

func main() {
var cfg config
envconfig.MustProcess("", &cfg)

log.SetLevel(cfg.LogLevel)

lf := new(log.TextFormatter)
lf.FullTimestamp = true
log.SetFormatter(lf)

log.Infof("Initializing archived-exporter (%s @ %s) ...", appVersion, buildTimestamp)

db, err := sql.Open("postgres", cfg.MetadataDSN)
if err != nil {
panic(err)
}

if err := db.Ping(); err != nil {
panic(err)
}

postgresqlRepo := postgresql.New(db)

svc, err := service.New(postgresqlRepo)
if err != nil {
panic(err)
}

g, ctx := errgroup.WithContext(context.Background())

g.Go(func() error {
return svc.Run(ctx)
})

g.Go(func() error {
http.Handle("/metrics", promhttp.Handler())
return http.ListenAndServe(cfg.Addr, nil)
})

if err := g.Wait(); err != nil {
panic(err)
}
}
30 changes: 30 additions & 0 deletions exporter/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package models

type VersionsCount struct {
ContainerName string
VersionsCount uint64
IsPublished bool
}

type ObjectsCount struct {
ContainerName string
VersionName string
IsPublished bool
ObjectsCount uint64
}

type BlobsRawSizeBytes struct {
ContainerName string
VersionName string
IsPublished bool
SizeBytes uint64
}

type Stats struct {
ContainersCount uint64
VersionsCount []VersionsCount
ObjectsCount []ObjectsCount
BlobsCount uint64
BlobsRawSizeBytes []BlobsRawSizeBytes
BlobsTotalSizeBytes uint64
}
1 change: 1 addition & 0 deletions exporter/service/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package service
143 changes: 143 additions & 0 deletions exporter/service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package service

import (
"context"
"strconv"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/teran/archived/repositories/metadata"
)

type Service interface {
Run(ctx context.Context) error
}

type service struct {
containersTotal *prometheus.GaugeVec
versionsTotal *prometheus.GaugeVec
objectsTotal *prometheus.GaugeVec
blobsTotal *prometheus.GaugeVec
blobsSize *prometheus.GaugeVec
blobsTotalRawSize *prometheus.GaugeVec

repo metadata.Repository
}

func New(repo metadata.Repository) (Service, error) {
svc := &service{
repo: repo,

containersTotal: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "archived",
Name: "containers_total",
Help: "Total amount of containers",
},
[]string{},
),

versionsTotal: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "archived",
Name: "versions_amount",
Help: "Total amount of versions",
},
[]string{"container", "is_published"},
),

objectsTotal: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "archived",
Name: "objects_amount",
Help: "Total amount of objects",
},
[]string{"container", "version", "is_published"},
),

blobsTotal: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "archived",
Name: "blobs_total",
Help: "Total amount of blobs",
},
[]string{},
),

blobsSize: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "archived",
Name: "blobs_raw_size_bytes",
Help: "Total raw size of blobs (i.e. before deduplication)",
}, []string{"container", "version", "is_published"},
),

blobsTotalRawSize: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "archived",
Name: "blobs_effective_size_total_bytes",
Help: "Total effective size of blobs (i.e. after deduplication)",
}, []string{},
),
}

for _, m := range []*prometheus.GaugeVec{
svc.containersTotal,
svc.versionsTotal,
svc.objectsTotal,
svc.blobsTotal,
svc.blobsSize,
svc.blobsTotalRawSize,
} {
if err := prometheus.Register(m); err != nil {
return nil, err
}
}

return svc, nil
}

func (s *service) observe(ctx context.Context) error {
stats, err := s.repo.CountStats(ctx)
if err != nil {
return err
}

s.containersTotal.WithLabelValues().Set(float64(stats.ContainersCount))
for _, vt := range stats.VersionsCount {
s.versionsTotal.WithLabelValues(
vt.ContainerName, strconv.FormatBool(vt.IsPublished),
).Set(float64(vt.VersionsCount))
}

for _, ot := range stats.ObjectsCount {
s.objectsTotal.WithLabelValues(
ot.ContainerName, ot.VersionName, strconv.FormatBool(ot.IsPublished),
).Set(float64(ot.ObjectsCount))
}

s.blobsTotal.WithLabelValues().Set(float64(stats.BlobsCount))

for _, bs := range stats.BlobsRawSizeBytes {
s.blobsSize.WithLabelValues(
bs.ContainerName, bs.VersionName, strconv.FormatBool(bs.IsPublished),
).Set(float64(bs.SizeBytes))
}

s.blobsTotalRawSize.WithLabelValues().Set(float64(stats.BlobsTotalSizeBytes))

return nil
}

func (s *service) Run(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(30 * time.Second):
if err := s.observe(ctx); err != nil {
return err
}
}
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/teran/go-time v0.0.2
golang.org/x/sync v0.7.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
)

require (
Expand Down Expand Up @@ -98,6 +99,5 @@ require (
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions repositories/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"

"github.com/pkg/errors"

"github.com/teran/archived/exporter/models"
)

var ErrNotFound = errors.New("not found")
Expand All @@ -27,4 +29,6 @@ type Repository interface {
CreateBLOB(ctx context.Context, checksum string, size uint64, mimeType string) error
GetBlobKeyByObject(ctx context.Context, container, version, key string) (string, error)
EnsureBlobKey(ctx context.Context, key string, size uint64) error

CountStats(ctx context.Context) (*models.Stats, error)
}
6 changes: 6 additions & 0 deletions repositories/metadata/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/stretchr/testify/mock"
"github.com/teran/archived/exporter/models"
"github.com/teran/archived/repositories/metadata"
)

Expand Down Expand Up @@ -91,3 +92,8 @@ func (m *Mock) EnsureBlobKey(_ context.Context, key string, size uint64) error {
args := m.Called(key, size)
return args.Error(0)
}

func (m *Mock) CountStats(ctx context.Context) (*models.Stats, error) {
args := m.Called()
return args.Get(0).(*models.Stats), args.Error(1)
}
Loading
Loading