From 57aadcbf53d4a0be1d36e4947c00e3090b944680 Mon Sep 17 00:00:00 2001 From: Phill Stiby Date: Tue, 12 Aug 2025 15:48:54 +0100 Subject: [PATCH 1/6] Provide a default transiant store for execution counter, so that subsequent runs will increment between host restarts/container deployments. --- counter/counter.go | 107 ++++++++++++++++++++++++++++++++++++++++ counter/counter_test.go | 29 +++++++++++ go.mod | 2 +- main.go | 38 +++++++++++--- 4 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 counter/counter.go create mode 100644 counter/counter_test.go diff --git a/counter/counter.go b/counter/counter.go new file mode 100644 index 0000000..1a8bde0 --- /dev/null +++ b/counter/counter.go @@ -0,0 +1,107 @@ +// Package counter, provides a means of incrementing a named counter. +package counter + +import ( + "encoding/json" + "fmt" + _ "fmt" + "io/ioutil" + "os" +) + +// IncrementNamedCounter takes a key, an increment value, and a JSON file path, and updates the value in the file +func IncrementNamedCounter(key string, increment int, filename string) (int, error) { + // Load the JSON data from the file + jsonData, err := loadJSONFile(filename) + if err != nil { + return 0, err + } + + // Get the existing value for the specified key + existingValue, ok := jsonData[key] + if !ok { + jsonData[key] = increment + } else { + // Increment the value + switch v := existingValue.(type) { + case float64: + println("float64") + jsonData[key] = v + float64(increment) + case int64: + println("int64") + jsonData[key] = v + int64(increment) + case int32: + println("int32") + jsonData[key] = v + int32(increment) + case int: + println("int") + jsonData[key] = v + increment + default: + return 0, fmt.Errorf("value for key '%s' is not a valid numeric type", key) + } + } + + err = writeJSONFile(filename, jsonData) + if err != nil { + return 0, err + } + + // Get the value for the specified key and convert it to int32 + value, ok := jsonData[key] + if !ok { + return 1, nil // return 1 if key doesn't exist, default behaiviour + } + + switch v := value.(type) { + case float64: + return int(v), nil + case int64: + return int(v), nil + case int: + return int(v), nil + default: + return 0, fmt.Errorf("value for key '%s' is not a valid int32", key) + } +} + +func writeJSONFile(filename string, jsonData map[string]interface{}) error { + // Marshal the updated JSON data + updatedData, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return err + } + + // Write the updated data back to the file + err = ioutil.WriteFile(filename, updatedData, 0644) + if err != nil { + return err + } + + return nil +} + +// loadJSONFile reads the contents of a JSON file and returns a map[string]interface{} +func loadJSONFile(filename string) (map[string]interface{}, error) { + + // Check if the file exists + if _, err := os.Stat(filename); os.IsNotExist(err) { + // Create the file with an empty JSON object + err := ioutil.WriteFile(filename, []byte("{}"), 0644) + if err != nil { + return nil, fmt.Errorf("failed to create file: %w", err) + } + } + + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + var jsonData map[string]interface{} + err = json.Unmarshal(data, &jsonData) + if err != nil { + return nil, err + } + + return jsonData, nil +} diff --git a/counter/counter_test.go b/counter/counter_test.go new file mode 100644 index 0000000..461ba38 --- /dev/null +++ b/counter/counter_test.go @@ -0,0 +1,29 @@ +package counter + +import ( + "path/filepath" + "testing" +) + +func TestThatANoneExistentCounterWillBeCreatedAndIncrements(t *testing.T) { + + // Create a temporary directory + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test.json") + + counter, err := IncrementNamedCounter("testCounter", 1, testFile) + if err != nil { + t.Errorf("Not expecting and err to be returned: %v", err) + } + if counter != 1 { + t.Errorf("Counter expected to be 1 but got %d", counter) + } + + counter, err = IncrementNamedCounter("testCounter", 1, testFile) + if err != nil { + t.Errorf("Not expecting and err to be returned: %v", err) + } + if counter != 2 { + t.Errorf("Counter expected to be 2 but got %d", counter) + } +} diff --git a/go.mod b/go.mod index b957a9a..eb3d945 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/meysam81/prometheus-command-timer -go 1.24.0 +go 1.23 diff --git a/main.go b/main.go index 7d16d9e..18c82b3 100644 --- a/main.go +++ b/main.go @@ -6,20 +6,25 @@ import ( "net/http" "os" "os/exec" + "path/filepath" "runtime/debug" + "strconv" "strings" "syscall" "time" + + "github.com/meysam81/prometheus-command-timer/counter" ) type Config struct { - PushgatewayURL string - JobName string - InstanceName string - Labels string - Debug bool - Version bool - Info bool + PushgatewayURL string + JobName string + InstanceName string + Labels string + ExeCountTransiantStore string + Debug bool + Version bool + Info bool } func main() { @@ -32,6 +37,7 @@ func main() { flag.StringVar(&config.JobName, "job-name", "", "Job name for metrics (required)") flag.StringVar(&config.InstanceName, "instance-name", config.InstanceName, "Instance name for metrics") flag.StringVar(&config.Labels, "labels", "", "Additional labels in key=value format, comma-separated (e.g., env=prod,team=infra)") + flag.StringVar(&config.ExeCountTransiantStore, "execution-count-store", filepath.Join(os.TempDir(), "prometheus-command-time.json"), "Override the default transiant store filename (/prometheus-command-time.json)") flag.BoolVar(&config.Version, "version", false, "Output version") showHelp := flag.Bool("help", false, "Show help message") flag.BoolVar(showHelp, "h", false, "Show help message (shorthand)") @@ -133,11 +139,27 @@ func executeCommand(config *Config, cmdArgs []string) int { sendMetric(config, "job_duration_seconds", fmt.Sprintf("%.6f", duration), "gauge", "Total time taken for job execution in seconds") sendMetric(config, "job_exit_status", fmt.Sprintf("%d", exitStatus), "gauge", "Exit status code of the last job execution (0=success)") sendMetric(config, "job_last_execution_timestamp", fmt.Sprintf("%d", endTime), "gauge", "Timestamp of the last job execution") - sendMetric(config, "job_executions_total", "1", "counter", "Total number of job executions") + sendMetric(config, "job_executions_total", strconv.Itoa(incrementExecutionCounter(config)), "counter", "Total number of job executions") return exitStatus } +// incrementExecutionCounter will return the next value of a counter which +// is named using the push gateway URL. +func incrementExecutionCounter(config *Config) int { + counterVal := 1 + counterName, err := buildPushgatewayURL(config) + if err != nil { + logStdout(config, "error building counter name: %v", err) + } else { + counterVal, err = counter.IncrementNamedCounter(counterName, 1, config.ExeCountTransiantStore) + if err != nil { + logStdout(config, "error loading counter: %v", err) + } + } + return counterVal +} + func buildPushgatewayURL(config *Config) (string, error) { url := fmt.Sprintf("%s/metrics/job/%s/instance/%s", strings.TrimSuffix(config.PushgatewayURL, "/"), From ae10638c57f9aa0bac02070316455022db58dde8 Mon Sep 17 00:00:00 2001 From: "Phillip Stiby @ Truespeed" Date: Thu, 14 Aug 2025 14:59:27 +0100 Subject: [PATCH 2/6] Update main.go spelling changes --- main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 18c82b3..2837bd6 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ type Config struct { JobName string InstanceName string Labels string - ExeCountTransiantStore string + ExeCountTransientStore string Debug bool Version bool Info bool @@ -37,7 +37,7 @@ func main() { flag.StringVar(&config.JobName, "job-name", "", "Job name for metrics (required)") flag.StringVar(&config.InstanceName, "instance-name", config.InstanceName, "Instance name for metrics") flag.StringVar(&config.Labels, "labels", "", "Additional labels in key=value format, comma-separated (e.g., env=prod,team=infra)") - flag.StringVar(&config.ExeCountTransiantStore, "execution-count-store", filepath.Join(os.TempDir(), "prometheus-command-time.json"), "Override the default transiant store filename (/prometheus-command-time.json)") + flag.StringVar(&config.ExeCountTransientStore, "execution-count-store", filepath.Join(os.TempDir(), "prometheus-command-time.json"), "Override the default transient store filename (/prometheus-command-time.json)") flag.BoolVar(&config.Version, "version", false, "Output version") showHelp := flag.Bool("help", false, "Show help message") flag.BoolVar(showHelp, "h", false, "Show help message (shorthand)") @@ -144,7 +144,7 @@ func executeCommand(config *Config, cmdArgs []string) int { return exitStatus } -// incrementExecutionCounter will return the next value of a counter which +// incrementExecutionCounter will return the next value of a counter which // is named using the push gateway URL. func incrementExecutionCounter(config *Config) int { counterVal := 1 @@ -152,7 +152,7 @@ func incrementExecutionCounter(config *Config) int { if err != nil { logStdout(config, "error building counter name: %v", err) } else { - counterVal, err = counter.IncrementNamedCounter(counterName, 1, config.ExeCountTransiantStore) + counterVal, err = counter.IncrementNamedCounter(counterName, 1, config.ExeCountTransientStore) if err != nil { logStdout(config, "error loading counter: %v", err) } From 60554e2740aa71a736f86e64a5916e775dd1f00c Mon Sep 17 00:00:00 2001 From: Phill Stiby Date: Fri, 15 Aug 2025 11:44:37 +0100 Subject: [PATCH 3/6] Code review comment changes --- counter/counter.go | 11 ++--------- counter/counter_test.go | 2 -- main.go | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/counter/counter.go b/counter/counter.go index 1a8bde0..c86ba56 100644 --- a/counter/counter.go +++ b/counter/counter.go @@ -11,18 +11,15 @@ import ( // IncrementNamedCounter takes a key, an increment value, and a JSON file path, and updates the value in the file func IncrementNamedCounter(key string, increment int, filename string) (int, error) { - // Load the JSON data from the file jsonData, err := loadJSONFile(filename) if err != nil { return 0, err } - // Get the existing value for the specified key existingValue, ok := jsonData[key] if !ok { jsonData[key] = increment } else { - // Increment the value switch v := existingValue.(type) { case float64: println("float64") @@ -46,10 +43,9 @@ func IncrementNamedCounter(key string, increment int, filename string) (int, err return 0, err } - // Get the value for the specified key and convert it to int32 value, ok := jsonData[key] if !ok { - return 1, nil // return 1 if key doesn't exist, default behaiviour + return 1, nil } switch v := value.(type) { @@ -64,14 +60,13 @@ func IncrementNamedCounter(key string, increment int, filename string) (int, err } } +// writeJSONFile writes the map into a JSON file func writeJSONFile(filename string, jsonData map[string]interface{}) error { - // Marshal the updated JSON data updatedData, err := json.MarshalIndent(jsonData, "", " ") if err != nil { return err } - // Write the updated data back to the file err = ioutil.WriteFile(filename, updatedData, 0644) if err != nil { return err @@ -83,9 +78,7 @@ func writeJSONFile(filename string, jsonData map[string]interface{}) error { // loadJSONFile reads the contents of a JSON file and returns a map[string]interface{} func loadJSONFile(filename string) (map[string]interface{}, error) { - // Check if the file exists if _, err := os.Stat(filename); os.IsNotExist(err) { - // Create the file with an empty JSON object err := ioutil.WriteFile(filename, []byte("{}"), 0644) if err != nil { return nil, fmt.Errorf("failed to create file: %w", err) diff --git a/counter/counter_test.go b/counter/counter_test.go index 461ba38..550d3ca 100644 --- a/counter/counter_test.go +++ b/counter/counter_test.go @@ -6,8 +6,6 @@ import ( ) func TestThatANoneExistentCounterWillBeCreatedAndIncrements(t *testing.T) { - - // Create a temporary directory tempDir := t.TempDir() testFile := filepath.Join(tempDir, "test.json") diff --git a/main.go b/main.go index 2837bd6..341c2ba 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,14 @@ import ( ) type Config struct { - PushgatewayURL string - JobName string - InstanceName string - Labels string - ExeCountTransientStore string - Debug bool - Version bool - Info bool + PushgatewayURL string + JobName string + InstanceName string + Labels string + ExecCountTransientStore string + Debug bool + Version bool + Info bool } func main() { @@ -37,7 +37,7 @@ func main() { flag.StringVar(&config.JobName, "job-name", "", "Job name for metrics (required)") flag.StringVar(&config.InstanceName, "instance-name", config.InstanceName, "Instance name for metrics") flag.StringVar(&config.Labels, "labels", "", "Additional labels in key=value format, comma-separated (e.g., env=prod,team=infra)") - flag.StringVar(&config.ExeCountTransientStore, "execution-count-store", filepath.Join(os.TempDir(), "prometheus-command-time.json"), "Override the default transient store filename (/prometheus-command-time.json)") + flag.StringVar(&config.ExecCountTransientStore, "execution-count-store", filepath.Join(os.TempDir(), "prometheus-command-time.json"), "Override the default transient store filename (/prometheus-command-time.json)") flag.BoolVar(&config.Version, "version", false, "Output version") showHelp := flag.Bool("help", false, "Show help message") flag.BoolVar(showHelp, "h", false, "Show help message (shorthand)") @@ -152,7 +152,7 @@ func incrementExecutionCounter(config *Config) int { if err != nil { logStdout(config, "error building counter name: %v", err) } else { - counterVal, err = counter.IncrementNamedCounter(counterName, 1, config.ExeCountTransientStore) + counterVal, err = counter.IncrementNamedCounter(counterName, 1, config.ExecCountTransientStore) if err != nil { logStdout(config, "error loading counter: %v", err) } From 5bba36474d072a869cc0488864532378d8715b85 Mon Sep 17 00:00:00 2001 From: Meysam Date: Wed, 20 Aug 2025 15:16:43 +0700 Subject: [PATCH 4/6] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index eb3d945..b957a9a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/meysam81/prometheus-command-timer -go 1.23 +go 1.24.0 From e3e9ff3d9d25f764315205647ce98197454d3c24 Mon Sep 17 00:00:00 2001 From: Meysam Date: Wed, 20 Aug 2025 15:18:20 +0700 Subject: [PATCH 5/6] Update counter.go --- counter/counter.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/counter/counter.go b/counter/counter.go index c86ba56..473e9de 100644 --- a/counter/counter.go +++ b/counter/counter.go @@ -22,16 +22,12 @@ func IncrementNamedCounter(key string, increment int, filename string) (int, err } else { switch v := existingValue.(type) { case float64: - println("float64") jsonData[key] = v + float64(increment) case int64: - println("int64") jsonData[key] = v + int64(increment) case int32: - println("int32") jsonData[key] = v + int32(increment) case int: - println("int") jsonData[key] = v + increment default: return 0, fmt.Errorf("value for key '%s' is not a valid numeric type", key) From d56a23d7b27c106f45f3665b103510f91906c1c3 Mon Sep 17 00:00:00 2001 From: Meysam Date: Wed, 20 Aug 2025 15:18:41 +0700 Subject: [PATCH 6/6] Update counter.go --- counter/counter.go | 1 - 1 file changed, 1 deletion(-) diff --git a/counter/counter.go b/counter/counter.go index 473e9de..a8b25e3 100644 --- a/counter/counter.go +++ b/counter/counter.go @@ -4,7 +4,6 @@ package counter import ( "encoding/json" "fmt" - _ "fmt" "io/ioutil" "os" )