Skip to content

Commit

Permalink
Added exercise metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenuhrbach committed Nov 9, 2024
1 parent 695636e commit 6212495
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 5 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,21 @@ docker run \
|egym_strength|Strength metrics about various muscles and muscle groups.|
|egym_flexibility|Flexibility metrics about different body parts like neck or hips.|
|egym_muscle_imbalance|Proportions and (im)balances of different muscle pairs.|
|egym_exercise_activity_points|Collected activity points of a specific exercise|
|egym_exercise_activity_distance|Distance left behind with the specific exercise|
|egym_exercise_activity_duration|Duration of a specific exercise|
|egym_exercise_activity_calories|Burned calories with a specific exercise|
|egym_exercise_average_speed|Average speed within a specific exercise|
|egym_exercise_sets|Number of sets within a specific exercise|
|egym_exercise_reps|number of reps over all sets of a specific exercise|
|egym_exercise_weight_total|Total weight across all reps of all sets of the exercise|

## Release Notes

### 0.7.0

Added metrics about exercises.

### 0.6.0

Added muscle imbalance metrics.
Expand Down
11 changes: 6 additions & 5 deletions internal/egym/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type EgymClient struct {
userId string
cookies string
defaultHeaders map[string]string
loginUrl string
apiUrl string
brandApiUrl string

httpClient *http.Client
}
Expand All @@ -42,9 +42,9 @@ func NewEgymClient(brand, username, password string) (*EgymClient, error) {
"x-np-app-version": "3.11",
"Accept": "application/json",
},
loginUrl: fmt.Sprintf("https://%s.netpulse.com/np/exerciser/login", brand),
apiUrl: "https://mobile-api.int.api.egym.com",
httpClient: httpClient,
brandApiUrl: fmt.Sprintf("https://%s.netpulse.com", brand),
apiUrl: "https://mobile-api.int.api.egym.com",
httpClient: httpClient,
}
loggedIn, err := c.login()
if err != nil || !loggedIn {
Expand All @@ -62,7 +62,8 @@ func (c *EgymClient) login() (bool, error) {
hasLogin := c.userId != ""
data.Set("relogin", fmt.Sprintf("%t", hasLogin))

req, err := http.NewRequest("POST", c.loginUrl, strings.NewReader(data.Encode()))
loginUrl := fmt.Sprintf("%s/np/exerciser/login", c.brandApiUrl)
req, err := http.NewRequest("POST", loginUrl, strings.NewReader(data.Encode()))
if err != nil {
return false, err
}
Expand Down
94 changes: 94 additions & 0 deletions internal/egym/workouts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package egym

import (
"encoding/json"
"fmt"
"time"
)

func (c *EgymClient) GetWorkoutsInPeriod(startDate, endDate time.Time) (*[]Workout, error) {
url := fmt.Sprintf(
"%s/workouts/api/workouts/v2.3/exercisers/%s/workouts?completedAfter=%s&completedBefore=%s",
c.brandApiUrl,
c.userId,
startDate.Format(time.RFC3339),
endDate.Format(time.RFC3339),
)
body, err := c.fetch(url, 1)
if err != nil {
return nil, err
}

var response GetWorkoutsResponse
err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
return &response.Workouts, nil
}

type GetWorkoutsResponse struct {
Workouts []Workout `json:"workouts"`
}

type Workout struct {
Code string `json:"code"`
Exercises []Exercise `json:"exercises"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
CompletedAt string `json:"completedAt"`
Timezone string `json:"timezone"`
WorkoutPlanCode string `json:"workoutPlanCode"`
WorkoutPlanLabel string `json:"workoutPlanLabel"`
WorkoutPlanImageUrl string `json:"workoutPlanImageUrl"`
WorkoutPlanGroupType string `json:"workoutPlanGroupType"`
}

type Exercise struct {
Code string `json:"code"`
ExerciseCode string `json:"exerciseCode"`
LibraryCode string `json:"libraryCode"`
Source struct {
Label string `json:"label"`
Code string `json:"code"`
} `json:"source"`
Exercise struct {
Label string `json:"label"`
Code string `json:"code"`
Icons []string `json:"icons"`
Videos []string `json:"videos"`
Previews []string `json:"previews"`
Description string `json:"description"`
Synonyms []string `json:"synonyms"`
Category struct {
Code string `json:"code"`
Label string `json:"label"`
} `json:"category"`
MachineBased bool `json:"machineBased"`
} `json:"exercise"`
Attributes struct {
Distance *ValueWithUnit `json:"distance"`
Duration *ValueWithUnit `json:"duration"`
Calories *ValueWithUnit `json:"calories"`
ActivityPoints *ValueWithUnit `json:"activity_points"`
AverageSpeed *ValueWithUnit `json:"average_speed"`
Sets []struct {
Reps *ValueWithUnit `json:"reps"`
Duration *ValueWithUnit `json:"duration"`
Weight *ValueWithUnit `json:"weight"`
} `json:"sets_of_reps_and_weight_or_duration_and_weight"`
} `json:"attributes"`
Name string `json:"name"`
Editable bool `json:"editable"`
Deletable bool `json:"deletable"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
CompletedAt string `json:"completedAt"`
Timezone string `json:"timezone"`
Origin string `json:"origin"`
}

type ValueWithUnit struct {
Unit string `json:"unit"`
Value float64 `json:"value"`
}
165 changes: 165 additions & 0 deletions internal/exporter/exercises.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package exporter

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/soerenuhrbach/egym-exporter/internal/egym"

"time"

log "github.com/sirupsen/logrus"
)

const exerciseNamespace = "exercise"

var (
exerciseLabels = []string{"exercise", "code", "completed_at", "source", "source_label", "unit", "workout"}

exerciseActivityPoints = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "activity_points"),
"Activity points of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseDistance = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "distance"),
"Distance of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseDuration = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "duration"),
"Training duration of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseCalories = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "calories"),
"Burned calories with the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseAverageSpeed = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "average_speed"),
"Average speed of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseSets = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "sets"),
"Number of sets of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseReps = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "reps"),
"Total number of reps over all sets of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseWeightPerRep = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "weight_per_rep"),
"Average weight per rep of the exercise",
append(labels, exerciseLabels...),
nil,
)
exerciseTotalWeight = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exerciseNamespace, "weight_total"),
"Total weight across all reps of all sets of the exercise",
append(labels, exerciseLabels...),
nil,
)
)

func (c *EgymExporter) describeExerciseMetrics(ch chan<- *prometheus.Desc) {
ch <- exerciseActivityPoints
}

func (c *EgymExporter) collectExerciseMetrics(ch chan<- prometheus.Metric) {
now := time.Now().UTC()
today := time.Date(
now.Year(),
now.Month(),
now.Day(),
int(0),
int(0),
int(0),
int(0),
now.Location(),
)
endOfDay := time.Date(
now.Year(),
now.Month(),
now.Day(),
int(23),
int(59),
int(59),
int(59),
now.Location(),
)

workouts, err := c.client.GetWorkoutsInPeriod(today, endOfDay)
if err != nil {
log.Error("could not retrieve exercise data!", err)
return
}

for _, workout := range *workouts {
for _, exercise := range workout.Exercises {

if exercise.Attributes.ActivityPoints != nil && exercise.Attributes.ActivityPoints.Value > 0 {
ch <- c.createExerciseMetric(exerciseActivityPoints, workout, exercise, exercise.Attributes.ActivityPoints.Value, exercise.Attributes.ActivityPoints.Unit)
}
if exercise.Attributes.Distance != nil && exercise.Attributes.Distance.Value > 0 {
ch <- c.createExerciseMetric(exerciseDistance, workout, exercise, exercise.Attributes.Distance.Value, exercise.Attributes.Distance.Unit)
}
if exercise.Attributes.Duration != nil && exercise.Attributes.Duration.Value > 0 {
ch <- c.createExerciseMetric(exerciseDuration, workout, exercise, exercise.Attributes.Duration.Value, exercise.Attributes.Duration.Unit)
}
if exercise.Attributes.Calories != nil && exercise.Attributes.Calories.Value > 0 {
ch <- c.createExerciseMetric(exerciseCalories, workout, exercise, exercise.Attributes.Calories.Value, exercise.Attributes.Calories.Unit)
}
if exercise.Attributes.AverageSpeed != nil && exercise.Attributes.AverageSpeed.Value > 0 {
ch <- c.createExerciseMetric(exerciseAverageSpeed, workout, exercise, exercise.Attributes.AverageSpeed.Value, exercise.Attributes.AverageSpeed.Unit)
}

if exercise.Attributes.Sets != nil && len(exercise.Attributes.Sets) > 0 {
sets := float64(len(exercise.Attributes.Sets))
reps := float64(0)
weight := float64(0)

for _, set := range exercise.Attributes.Sets {
if set.Reps != nil {
reps += set.Reps.Value
}

if set.Weight != nil {
weight += set.Weight.Value
}
}

weight = weight / sets

ch <- c.createExerciseMetric(exerciseSets, workout, exercise, sets, "")
ch <- c.createExerciseMetric(exerciseReps, workout, exercise, reps, "")
ch <- c.createExerciseMetric(exerciseWeightPerRep, workout, exercise, weight, "kg")
ch <- c.createExerciseMetric(exerciseTotalWeight, workout, exercise, weight*reps, "kg")
}
}
}
}

func (c *EgymExporter) createExerciseMetric(desc *prometheus.Desc, workout egym.Workout, exercise egym.Exercise, value float64, unit string) prometheus.Metric {
return prometheus.MustNewConstMetric(
desc,
prometheus.CounterValue,
value,
c.client.Username,
exercise.Exercise.Label,
exercise.Exercise.Code,
exercise.CompletedAt,
exercise.Source.Code,
exercise.Source.Label,
unit,
workout.Code,
)
}
2 changes: 2 additions & 0 deletions internal/exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func (e *EgymExporter) Describe(ch chan<- *prometheus.Desc) {
e.describeStrengthMetrics(ch)
e.describeFlexibilityMetrics(ch)
e.describeMuscleImbalanceMetrics(ch)
e.describeExerciseMetrics(ch)
}

func (e *EgymExporter) Collect(ch chan<- prometheus.Metric) {
Expand All @@ -31,6 +32,7 @@ func (e *EgymExporter) Collect(ch chan<- prometheus.Metric) {
e.collectStrengthMetrics(ch)
e.collectFlexibilityMetrics(ch)
e.collectMuscleImbalanceMetrics(ch)
e.collectExerciseMetrics(ch)
}

func NewEgymExporter(client *egym.EgymClient) *EgymExporter {
Expand Down

0 comments on commit 6212495

Please sign in to comment.