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

Using single metric for dhis2 systems info #2

Merged
merged 9 commits into from
Sep 9, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
config.yaml
.DS_Store
rtsl_exporter
48 changes: 31 additions & 17 deletions dhis2/client.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
package dhis2

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"time"
)

const httpTimeOutSec = 1
const DefaultConnectionTimeout = 1 * time.Second

type Client struct {
Username string
Password string
BaseURL string
Username string
Password string
BaseURL string
ConnectionTimeout time.Duration
}

type Info struct {
Expand All @@ -38,16 +39,12 @@ func (c *Client) GetInfo() (*Info, error) {

// common method to do request
func (c *Client) doRequest(path string, result interface{}) error {
// Create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), httpTimeOutSec*time.Second)
defer cancel()

url := fmt.Sprintf("%s%s", c.BaseURL, path)

// Create a new request
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
return fmt.Errorf("failed to create request: %w", err)
}

// Encode credentials
Expand All @@ -56,21 +53,38 @@ func (c *Client) doRequest(path string, result interface{}) error {
// Set Authorization header
req.Header.Set("Authorization", "Basic "+credentials)

// Custom Transport with Connect Timeout
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: c.ConnectionTimeout,
}).DialContext,
}

// Make the request
client := &http.Client{}
client := &http.Client{
Transport: transport,
}
resp, err := client.Do(req)
if err != nil {
return err
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()

// Check if the status code is not 200
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

// Read the body
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
return fmt.Errorf("failed to read response body: %w", err)
}

// Unmarshal JSON response into result
err = json.Unmarshal(body, result)
return err
if err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
return nil
}
52 changes: 24 additions & 28 deletions dhis2/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,22 @@ package dhis2

import (
"log"
"strings"
"sync"

"github.com/prometheus/client_golang/prometheus"
)

// sanitizeName prepares a valid prometheus metric name from a given URL
func sanitizeName(url string) string {
// Remove the protocol part
cleanURL := strings.TrimPrefix(url, "https://")
cleanURL = strings.TrimPrefix(cleanURL, "http://")

// Replace dots and dashes with underscores
cleanURL = strings.ReplaceAll(cleanURL, "-", "_")
cleanURL = strings.ReplaceAll(cleanURL, ".", "_")

return cleanURL
}

type Exporter struct {
client *Client

info *prometheus.GaugeVec
clients []*Client
info *prometheus.GaugeVec
}

func NewExporter(client *Client) *Exporter {
dynamicName := "system_info_" + sanitizeName(client.BaseURL)
func NewExporter(clients []*Client) *Exporter {
return &Exporter{
client: client,
clients: clients,
info: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "dhis2",
Name: dynamicName,
Name: "system_info",
Help: "Information about the DHIS2 system",
}, []string{"version", "revision", "contextPath", "buildTime"}),
}
Expand All @@ -46,14 +31,25 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {

// Collect is called by the Prometheus registry when collecting metrics.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
info, err := e.client.GetInfo()
if err != nil {
log.Printf("Failed to get dhis2 system information: %v\n", err)
return // Early return on error to avoid using uninitialized info
var wg sync.WaitGroup
var mu sync.Mutex

for _, client := range e.clients {
wg.Add(1)
go func(client *Client) {
defer wg.Done()
info, err := client.GetInfo()
if err != nil {
log.Printf("ERROR: Failed to get system information from %s: %v\n", client.BaseURL, err)
return
}

mu.Lock()
e.info.WithLabelValues(info.Version, info.Revision, client.BaseURL, info.BuildTime).Set(1)
mu.Unlock()
}(client)
}

// Set the version and revision as labels; gauge value is less meaningful here, just set to 1
e.info.WithLabelValues(info.Version, info.Revision, info.ContextPath, info.BuildTime).Set(1)

wg.Wait()
e.info.Collect(ch)
}
20 changes: 13 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package main

import (
"context"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"io/ioutil"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/simpledotorg/rtsl_exporter/alphasms"
Expand All @@ -17,7 +18,7 @@ import (
)

type Config struct {
ALPHASMSAPIKey string `yaml:"alphasms_api_key"`
ALPHASMSAPIKey string `yaml:"alphasms_api_key"`
SendGridAccounts []struct {
AccountName string `yaml:"account_name"`
APIKey string `yaml:"api_key"`
Expand Down Expand Up @@ -73,22 +74,27 @@ func main() {
log.Fatalf("Error reading config file: %v", err)
}

// Alphasms
if config.ALPHASMSAPIKey == "" {
log.Fatalf("ALPHASMS_API_KEY not provided in config file")
}
alphasmsClient := alphasms.Client{APIKey: config.ALPHASMSAPIKey}
alphasmsExporter := alphasms.NewExporter(&alphasmsClient)
prometheus.MustRegister(alphasmsExporter)

// DHIS2
dhis2Clients := []*dhis2.Client{}
for _, endpoint := range config.DHIS2Endpoints {
dhis2Client := dhis2.Client{
Username: endpoint.Username,
Password: endpoint.Password,
BaseURL: endpoint.BaseURL,
Username: endpoint.Username,
Password: endpoint.Password,
BaseURL: endpoint.BaseURL,
ConnectionTimeout: dhis2.DefaultConnectionTimeout,
}
dhis2Exporter := dhis2.NewExporter(&dhis2Client)
prometheus.MustRegister(dhis2Exporter)
dhis2Clients = append(dhis2Clients, &dhis2Client)
}
dhis2Exporter := dhis2.NewExporter(dhis2Clients)
prometheus.MustRegister(dhis2Exporter)

// Register SendGrid exporters
apiKeys := make(map[string]string)
Expand Down