Skip to content

Commit

Permalink
Merge pull request #2 from simpledotorg/dhis2-single-metric
Browse files Browse the repository at this point in the history
Using single metric for dhis2 systems info
  • Loading branch information
roypeter authored Sep 9, 2024
2 parents a550c86 + 0be2841 commit bec803f
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 52 deletions.
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

0 comments on commit bec803f

Please sign in to comment.