Skip to content

Commit bf7a855

Browse files
versilisliujed
andauthored
Init Demo App (#7)
This adds initial the services and infrastructure required for the Akita demo Changes include: - #2 - #3 - #4 Co-authored-by: Jed Liu <liujed@users.noreply.github.com>
1 parent 88dd08d commit bf7a855

File tree

18 files changed

+1208
-1
lines changed

18 files changed

+1208
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@
1717
# Dependency directories (remove the comment below to include it)
1818
# vendor/
1919

20+
# IDE files
21+
.idea
22+
2023
# Go workspace file
2124
go.work

Makefile

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
CLIENT_IMAGE ?= akitasoftware/demo-client
2+
SERVER_IMAGE ?= akitasoftware/demo-server
3+
CONFIG_FILE ?= application.yml
4+
TAG ?= latest
5+
LATEST ?= true
6+
7+
BUILDER=buildx-multi-arch
8+
9+
INFO_COLOR = \033[0;36m
10+
NO_COLOR = \033[m
11+
12+
build-client: ## Build the demo client
13+
docker build --tag=$(CLIENT_IMAGE):$(TAG) --secret id=application.yml,src=$(CONFIG_FILE) -f client/Dockerfile client
14+
.PHONY: build-client
15+
16+
build-server: ## Build the demo server
17+
docker build --tag=$(SERVER_IMAGE):$(TAG) -f server/Dockerfile server
18+
.PHONY: build-server
19+
20+
build-images: build-client build-server ## Build the demo images
21+
22+
prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
23+
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host
24+
.PHONY: prepare-buildx
25+
26+
# Check if the specified tag exists remotely
27+
define check_remote_tag
28+
@if docker pull $(1):$(TAG) >/dev/null 2>&1; then \
29+
echo "Error: Image $(1):$(TAG) already exists in the registry."; \
30+
exit 1; \
31+
fi
32+
endef
33+
34+
push-client: prepare-buildx ## Push the demo client image to the registry
35+
$(call check_remote_tag,$(CLIENT_IMAGE))
36+
ifeq ($(LATEST),true)
37+
docker buildx build \
38+
--push \
39+
--builder=$(BUILDER) \
40+
--platform=linux/amd64,linux/arm64 \
41+
--secret id=application.yml,src=$(CONFIG_FILE) \
42+
--build-arg TAG=$(TAG) \
43+
--tag=$(CLIENT_IMAGE):$(TAG) \
44+
--tag=$(CLIENT_IMAGE):latest \
45+
-f client/Dockerfile client
46+
else
47+
docker buildx build \
48+
--push \
49+
--builder=$(BUILDER) \
50+
--platform=linux/amd64,linux/arm64 \
51+
--secret id=application.yml,src=$(CONFIG_FILE) \
52+
--build-arg TAG=$(TAG) \
53+
--tag=$(CLIENT_IMAGE):$(TAG) \
54+
-f client/Dockerfile client
55+
endif
56+
.PHONY: push-client
57+
58+
push-server: prepare-buildx
59+
$(call check_remote_tag,$(SERVER_IMAGE))
60+
ifeq ($(LATEST),true)
61+
docker buildx build \
62+
--push \
63+
--builder=$(BUILDER) \
64+
--platform=linux/amd64,linux/arm64 \
65+
--build-arg TAG=$(TAG) \
66+
--tag=$(SERVER_IMAGE):$(TAG) \
67+
--tag=$(SERVER_IMAGE):latest \
68+
-f server/Dockerfile server
69+
else
70+
docker buildx build \
71+
--push \
72+
--builder=$(BUILDER) \
73+
--platform=linux/amd64,linux/arm64 \
74+
--build-arg TAG=$(TAG) \
75+
--tag=$(SERVER_IMAGE):$(TAG) \
76+
-f server/Dockerfile server
77+
endif
78+
.PHONY: push-server
79+
80+
push-images: push-client push-server ## Push the demo images to the registry
81+
.PHONY: push-images
82+
83+
help: ## Show this help
84+
@echo Please specify a build target. The choices are:
85+
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(INFO_COLOR)%-30s$(NO_COLOR) %s\n", $$1, $$2}'
86+
.PHONY: help

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,59 @@
1-
# demo
1+
# Akita Demo
2+
3+
This repository hosts the code for demoing the Akita Agent. Before running the demo, please ensure your system meets the following requirements
4+
5+
## Prerequisites
6+
1. **Docker**: Please ensure that you have Docker isntalled on your machine. You can download it from the official [Docker Website](https://docs.docker.com/engine/install/)
7+
2. **Akita User Account**: You must have an active user account with Akita. If you don't have one yet, please [sign up here](https://app.akita.software/login?sign_up)
8+
9+
## Getting started
10+
### Step 1: Obtain Akita API Credentials and Project Name
11+
Go through initial onboarding in [Akita's web dashboard](https://app.akita.software) and store your API credentials and created project name for later use
12+
13+
### Step 2: Clone the repository
14+
Clone this repository to your local machine:
15+
`git clone https://github.com/akitasoftware/akita-demo.git`
16+
17+
### Step 3: Run the demo
18+
Run the following commands to start the demo:
19+
```
20+
chmod +x run.sh
21+
./run.sh
22+
```
23+
24+
To stop the demo, run `./stop.sh`. To restart the demo, run `./restart.sh`
25+
26+
## Getting involved
27+
28+
* Please file bugs as issues to this repository.
29+
* We welcome contributions! If you want to make changes please see our [contributing guide](CONTRIBUTING.md).
30+
* We're always happy to answer any questions about the Docker extension, or about how you
31+
can contribute. Email us at `opensource [at] akitasoftware [dot] com` or
32+
[request to join our Slack](https://docs.google.com/forms/d/e/1FAIpQLSfF-Mf4Li_DqysCHy042IBfvtpUDHGYrV6DOHZlJcQV8OIlAA/viewform?usp=sf_link)!
33+
34+
## What is Akita?
35+
36+
Drop-in API monitoring, no code changes necessary.
37+
38+
Built for busy developer teams who don't have time to become experts in monitoring and observability, Akita is the
39+
fastest, easiest way to see and monitor your API endpoints which makes it possible to quickly track API endpoints and
40+
their usage in real time.
41+
42+
* **See API endpoints.** Automatically get a searchable map of your API endpoints in use. Explore by latency, errors,
43+
and usage. Export as OpenAPI specs.
44+
* **Get drop-in API monitoring.** Get a drop-in view of volume, latency, and errors, updated in near real-time. Set
45+
per-endpoint alerts.
46+
* **Quickly understand the impact of changes.** Keep track of the endpoints you care about and identify how new
47+
deployments impact your endpoints.
48+
49+
Simply drop Akita into your system to understand your system behavior, without having to instrument code or build your
50+
own dashboards.
51+
52+
[Create an account in the Akita App](https//app.akita.software/login?sign_up) to get started!
53+
54+
## Related links
55+
* [Akita blog](https://www.akitasoftware.com/blog)
56+
* [Akita docs](https://docs.akita.software/)
57+
58+
59+

application.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
analytics:
2+
enabled: true
3+
segment_write_key: <SEGMENT_WRITE_KEY>
4+
app:
5+
name: aki-demo
6+
version: <APP_VERSION>
7+
batch_size: 1
8+
akita:
9+
base_url: https://api.akita.software

client/Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
FROM golang:1.19-alpine AS builder
2+
ENV CGO_ENABLED=0
3+
4+
WORKDIR /app
5+
6+
# Copy go.mod and go.sum files to the workspace
7+
COPY go.* ./
8+
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
9+
RUN --mount=type=cache,target=/go/pkg/mod \
10+
--mount=type=cache,target=/root/.cache/go-build \
11+
go mod download
12+
13+
COPY . .
14+
RUN --mount=type=cache,target=/go/pkg/mod \
15+
--mount=type=cache,target=/root/.cache/go-build \
16+
--mount=type=secret,id=application.yml,dst=./application.yml \
17+
go build -trimpath -ldflags="-s -w" -o bin/service
18+
19+
FROM alpine
20+
21+
# Provide the target platform as an environment variable to the application
22+
ARG TARGETPLATFORM
23+
ENV TARGETPLATFORM=${TARGETPLATFORM}
24+
25+
COPY --from=builder /app/bin/service /
26+
27+
CMD /service
28+

client/app.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package main
2+
3+
import (
4+
"akitasoftware.com/demo-client/datasource"
5+
"github.com/akitasoftware/akita-libs/analytics"
6+
"github.com/akitasoftware/go-utils/optionals"
7+
"github.com/golang/glog"
8+
"github.com/pkg/errors"
9+
"math/rand"
10+
"sync"
11+
"time"
12+
)
13+
14+
type App struct {
15+
Config *Configuration
16+
// The demo server used to generate traffic.
17+
DemoServer datasource.DemoServer
18+
// The client used to communicate with the Akita backend.
19+
AkitaClient datasource.AkitaClient
20+
// Client used to send analytics events.
21+
AnalyticsClient optionals.Optional[analytics.Client]
22+
}
23+
24+
// Create a new app instance with the given configuration and demo server.
25+
func NewApp(config *Configuration, server datasource.DemoServer) *App {
26+
return &App{
27+
Config: config,
28+
DemoServer: server,
29+
AkitaClient: config.AkitaClient,
30+
AnalyticsClient: config.Analytics,
31+
}
32+
}
33+
34+
// SendEvent sends an analytics event to the Akita backend.
35+
func (a App) SendEvent(name string, properties map[string]any) {
36+
// Add the platform to the properties.
37+
properties["platform"] = a.Config.Platform
38+
39+
analyticsClient, ok := a.AnalyticsClient.Get()
40+
if !ok {
41+
glog.Warningf("analytics client not configured")
42+
return
43+
}
44+
45+
email, err := a.AkitaClient.GetUserEmail(a.Config.Credentials.APIKey, a.Config.Credentials.APISecret)
46+
if err != nil {
47+
glog.Errorf("failed to get user email: %v", err)
48+
return
49+
}
50+
51+
if err := analyticsClient.Track(email, name, properties); err != nil {
52+
glog.Errorf("failed to emit analytics event: %v", err)
53+
}
54+
}
55+
56+
// HandleDemoTasks sends requests to the demo server at a regular interval.
57+
func (a App) HandleDemoTasks() {
58+
requestInterval := time.Second
59+
60+
// Create a ticker that fires every second.
61+
ticker := time.NewTicker(requestInterval)
62+
// Mutex for keeping track of errors logged by the ticker.
63+
var rwMutex sync.RWMutex
64+
var errorCount int
65+
66+
for {
67+
select {
68+
case <-ticker.C:
69+
go func() {
70+
// Send a request to the demo server.
71+
err := a.sendMockTraffic()
72+
73+
// Send an error event if we've sent less than 5 error events
74+
// TODO: It would be nice to have the error count be configurable.
75+
// We could also consider resetting the error count after a certain amount of time.
76+
rwMutex.RLock()
77+
if err != nil && errorCount < 5 {
78+
a.SendEvent(
79+
"Demo Client Error", map[string]any{
80+
"error": err.Error(),
81+
},
82+
)
83+
// Increment the error count. This is protected by a mutex because we're in a goroutine.
84+
rwMutex.RUnlock()
85+
rwMutex.Lock()
86+
errorCount++
87+
rwMutex.Unlock()
88+
}
89+
}()
90+
}
91+
}
92+
}
93+
94+
// Send a random request to the demo server.
95+
func (a App) sendMockTraffic() error {
96+
var err error
97+
98+
// To showcase response count metric, we should attempt to send request disproportionately
99+
r := rand.New(rand.NewSource(time.Now().UnixNano()))
100+
101+
randomNumber := r.Intn(100)
102+
if randomNumber < 10 {
103+
err = a.DemoServer.GetOwner()
104+
} else if randomNumber < 67 {
105+
err = a.DemoServer.GetBreed()
106+
} else {
107+
err = a.DemoServer.PostTrick()
108+
}
109+
110+
if err != nil {
111+
return errors.Wrap(err, "failed to send mock traffic")
112+
}
113+
114+
return nil
115+
}

client/config.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import (
4+
"akitasoftware.com/demo-client/datasource"
5+
_ "embed"
6+
"fmt"
7+
"github.com/akitasoftware/akita-libs/analytics"
8+
"github.com/akitasoftware/go-utils/optionals"
9+
"github.com/golang/glog"
10+
"gopkg.in/yaml.v3"
11+
"net/http"
12+
"os"
13+
)
14+
15+
type rawConfiguration struct {
16+
Analytics struct {
17+
// Configures the analytics client.
18+
analytics.Config `yaml:",inline"`
19+
// Whether analytics are enabled.
20+
Enabled bool `yaml:"enabled"`
21+
} `yaml:"analytics"`
22+
Akita struct {
23+
// Base URL of the Akita API.
24+
BaseURL string `yaml:"base_url"`
25+
} `yaml:"akita"`
26+
}
27+
28+
type UserCredentials struct {
29+
// The credentials used to identify the user.
30+
APIKey string
31+
APISecret string
32+
}
33+
34+
type Configuration struct {
35+
AkitaClient datasource.AkitaClient
36+
Analytics optionals.Optional[analytics.Client]
37+
Credentials UserCredentials
38+
// The target platform for the Docker image the client is derived from.
39+
Platform string
40+
}
41+
42+
// Takes a byte array containing a rawConfiguration and turns it into a
43+
// Configuration instance.
44+
func ParseConfiguration(rawData []byte) (*Configuration, error) {
45+
var rawConfig rawConfiguration
46+
if err := yaml.Unmarshal(rawData, &rawConfig); err != nil {
47+
return nil, fmt.Errorf("failed to parse configuration: %w", err)
48+
}
49+
50+
// Create the Akita client.
51+
akitaClient := datasource.NewAkitaClient(rawConfig.Akita.BaseURL, http.DefaultClient)
52+
53+
// Create the analytics client.
54+
var analyticsClient optionals.Optional[analytics.Client]
55+
if rawConfig.Analytics.Enabled {
56+
glog.Infof("Enabling analytics...")
57+
client, err := analytics.NewClient(rawConfig.Analytics.Config)
58+
if err != nil {
59+
// If we fail to create the analytics client, we don't want to fail the entire demo.
60+
// Instead, we just log the error and continue without analytics.
61+
glog.Errorf("Failed to create analytics client: %v", err)
62+
}
63+
64+
glog.Infof("Analytics client created successfully")
65+
analyticsClient = optionals.Some(client)
66+
} else {
67+
glog.Infof("Analytics have been disabled")
68+
}
69+
70+
return &Configuration{
71+
AkitaClient: akitaClient,
72+
Analytics: analyticsClient,
73+
Credentials: UserCredentials{
74+
// The API key and secret are set by the Docker Compose file.
75+
APIKey: os.Getenv("AKITA_API_KEY_ID"),
76+
APISecret: os.Getenv("AKITA_API_KEY_SECRET"),
77+
},
78+
// Get the target platform declared at build time.
79+
Platform: os.Getenv("TARGETPLATFORM"),
80+
}, nil
81+
}

0 commit comments

Comments
 (0)