Skip to content

Commit 3d32015

Browse files
Added default transient store the execution counter (#21)
* Provide a default transiant store for execution counter, so that subsequent runs will increment between host restarts/container deployments. * Update main.go spelling changes * Code review comment changes * Update go.mod * Update counter.go * Update counter.go --------- Co-authored-by: Meysam <meysam@developer-friendly.blog>
1 parent 65b160f commit 3d32015

File tree

3 files changed

+152
-8
lines changed

3 files changed

+152
-8
lines changed

counter/counter.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Package counter, provides a means of incrementing a named counter.
2+
package counter
3+
4+
import (
5+
"encoding/json"
6+
"fmt"
7+
"io/ioutil"
8+
"os"
9+
)
10+
11+
// IncrementNamedCounter takes a key, an increment value, and a JSON file path, and updates the value in the file
12+
func IncrementNamedCounter(key string, increment int, filename string) (int, error) {
13+
jsonData, err := loadJSONFile(filename)
14+
if err != nil {
15+
return 0, err
16+
}
17+
18+
existingValue, ok := jsonData[key]
19+
if !ok {
20+
jsonData[key] = increment
21+
} else {
22+
switch v := existingValue.(type) {
23+
case float64:
24+
jsonData[key] = v + float64(increment)
25+
case int64:
26+
jsonData[key] = v + int64(increment)
27+
case int32:
28+
jsonData[key] = v + int32(increment)
29+
case int:
30+
jsonData[key] = v + increment
31+
default:
32+
return 0, fmt.Errorf("value for key '%s' is not a valid numeric type", key)
33+
}
34+
}
35+
36+
err = writeJSONFile(filename, jsonData)
37+
if err != nil {
38+
return 0, err
39+
}
40+
41+
value, ok := jsonData[key]
42+
if !ok {
43+
return 1, nil
44+
}
45+
46+
switch v := value.(type) {
47+
case float64:
48+
return int(v), nil
49+
case int64:
50+
return int(v), nil
51+
case int:
52+
return int(v), nil
53+
default:
54+
return 0, fmt.Errorf("value for key '%s' is not a valid int32", key)
55+
}
56+
}
57+
58+
// writeJSONFile writes the map into a JSON file
59+
func writeJSONFile(filename string, jsonData map[string]interface{}) error {
60+
updatedData, err := json.MarshalIndent(jsonData, "", " ")
61+
if err != nil {
62+
return err
63+
}
64+
65+
err = ioutil.WriteFile(filename, updatedData, 0644)
66+
if err != nil {
67+
return err
68+
}
69+
70+
return nil
71+
}
72+
73+
// loadJSONFile reads the contents of a JSON file and returns a map[string]interface{}
74+
func loadJSONFile(filename string) (map[string]interface{}, error) {
75+
76+
if _, err := os.Stat(filename); os.IsNotExist(err) {
77+
err := ioutil.WriteFile(filename, []byte("{}"), 0644)
78+
if err != nil {
79+
return nil, fmt.Errorf("failed to create file: %w", err)
80+
}
81+
}
82+
83+
data, err := ioutil.ReadFile(filename)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
var jsonData map[string]interface{}
89+
err = json.Unmarshal(data, &jsonData)
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
return jsonData, nil
95+
}

counter/counter_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package counter
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
)
7+
8+
func TestThatANoneExistentCounterWillBeCreatedAndIncrements(t *testing.T) {
9+
tempDir := t.TempDir()
10+
testFile := filepath.Join(tempDir, "test.json")
11+
12+
counter, err := IncrementNamedCounter("testCounter", 1, testFile)
13+
if err != nil {
14+
t.Errorf("Not expecting and err to be returned: %v", err)
15+
}
16+
if counter != 1 {
17+
t.Errorf("Counter expected to be 1 but got %d", counter)
18+
}
19+
20+
counter, err = IncrementNamedCounter("testCounter", 1, testFile)
21+
if err != nil {
22+
t.Errorf("Not expecting and err to be returned: %v", err)
23+
}
24+
if counter != 2 {
25+
t.Errorf("Counter expected to be 2 but got %d", counter)
26+
}
27+
}

main.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,25 @@ import (
66
"net/http"
77
"os"
88
"os/exec"
9+
"path/filepath"
910
"runtime/debug"
11+
"strconv"
1012
"strings"
1113
"syscall"
1214
"time"
15+
16+
"github.com/meysam81/prometheus-command-timer/counter"
1317
)
1418

1519
type Config struct {
16-
PushgatewayURL string
17-
JobName string
18-
InstanceName string
19-
Labels string
20-
Debug bool
21-
Version bool
22-
Info bool
20+
PushgatewayURL string
21+
JobName string
22+
InstanceName string
23+
Labels string
24+
ExecCountTransientStore string
25+
Debug bool
26+
Version bool
27+
Info bool
2328
}
2429

2530
func main() {
@@ -32,6 +37,7 @@ func main() {
3237
flag.StringVar(&config.JobName, "job-name", "", "Job name for metrics (required)")
3338
flag.StringVar(&config.InstanceName, "instance-name", config.InstanceName, "Instance name for metrics")
3439
flag.StringVar(&config.Labels, "labels", "", "Additional labels in key=value format, comma-separated (e.g., env=prod,team=infra)")
40+
flag.StringVar(&config.ExecCountTransientStore, "execution-count-store", filepath.Join(os.TempDir(), "prometheus-command-time.json"), "Override the default transient store filename (<tmp>/prometheus-command-time.json)")
3541
flag.BoolVar(&config.Version, "version", false, "Output version")
3642
showHelp := flag.Bool("help", false, "Show help message")
3743
flag.BoolVar(showHelp, "h", false, "Show help message (shorthand)")
@@ -133,11 +139,27 @@ func executeCommand(config *Config, cmdArgs []string) int {
133139
sendMetric(config, "job_duration_seconds", fmt.Sprintf("%.6f", duration), "gauge", "Total time taken for job execution in seconds")
134140
sendMetric(config, "job_exit_status", fmt.Sprintf("%d", exitStatus), "gauge", "Exit status code of the last job execution (0=success)")
135141
sendMetric(config, "job_last_execution_timestamp", fmt.Sprintf("%d", endTime), "gauge", "Timestamp of the last job execution")
136-
sendMetric(config, "job_executions_total", "1", "counter", "Total number of job executions")
142+
sendMetric(config, "job_executions_total", strconv.Itoa(incrementExecutionCounter(config)), "counter", "Total number of job executions")
137143

138144
return exitStatus
139145
}
140146

147+
// incrementExecutionCounter will return the next value of a counter which
148+
// is named using the push gateway URL.
149+
func incrementExecutionCounter(config *Config) int {
150+
counterVal := 1
151+
counterName, err := buildPushgatewayURL(config)
152+
if err != nil {
153+
logStdout(config, "error building counter name: %v", err)
154+
} else {
155+
counterVal, err = counter.IncrementNamedCounter(counterName, 1, config.ExecCountTransientStore)
156+
if err != nil {
157+
logStdout(config, "error loading counter: %v", err)
158+
}
159+
}
160+
return counterVal
161+
}
162+
141163
func buildPushgatewayURL(config *Config) (string, error) {
142164
url := fmt.Sprintf("%s/metrics/job/%s/instance/%s",
143165
strings.TrimSuffix(config.PushgatewayURL, "/"),

0 commit comments

Comments
 (0)