Skip to content

Commit

Permalink
[TASK-1010] probabilistic and error samplers (#1349)
Browse files Browse the repository at this point in the history
This PR adds two new Odigos actions that allow the user to sample their data.
  • Loading branch information
alonkeyval authored Jul 10, 2024
1 parent 6f466e9 commit 2ce5948
Show file tree
Hide file tree
Showing 24 changed files with 347 additions and 31 deletions.
100 changes: 100 additions & 0 deletions frontend/endpoints/actions/errorsampler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package actions

import (
"github.com/gin-gonic/gin"
"github.com/odigos-io/odigos/api/actions/v1alpha1"
"github.com/odigos-io/odigos/frontend/kube"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func GetErrorSampler(c *gin.Context, odigosns string, id string) {
action, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Get(c, id, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
c.JSON(404, gin.H{
"error": "not found",
})
return
} else {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}
}
c.JSON(200, action.Spec)
}

func CreateErrorSampler(c *gin.Context, odigosns string) {
var action v1alpha1.ErrorSampler
if err := c.ShouldBindJSON(&action.Spec); err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
action.GenerateName = "es-"
generatedAction, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Create(c, &action, metav1.CreateOptions{})
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}
c.JSON(201, gin.H{
"id": generatedAction.Name,
})
}

func UpdateErrorSampler(c *gin.Context, odigosns string, id string) {
action, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Get(c, id, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
c.JSON(404, gin.H{
"error": "not found",
})
return
} else {
c.JSON(500, gin.H{
"error": err.Error(),
})
}
return
}
action.Spec = v1alpha1.ErrorSamplerSpec{}
if err := c.ShouldBindJSON(&action.Spec); err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
action.Name = id

_, err = kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Update(c, action, metav1.UpdateOptions{})
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}
c.JSON(204, nil)
}

func DeleteErrorSampler(c *gin.Context, odigosns string, id string) {
err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Delete(c, id, metav1.DeleteOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
c.JSON(404, gin.H{
"error": "not found",
})
return
} else {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}
}
c.JSON(204, nil)
}
2 changes: 1 addition & 1 deletion frontend/endpoints/actions/probabilisticsampler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func CreateProbabilisticSampler(c *gin.Context, odigosns string) {
})
return
}
action.GenerateName = "da-"
action.GenerateName = "ps-"
generatedAction, err := kube.DefaultClient.ActionsClient.ProbabilisticSamplers(odigosns).Create(c, &action, metav1.CreateOptions{})
if err != nil {
c.JSON(500, gin.H{
Expand Down
33 changes: 33 additions & 0 deletions frontend/endpoints/actions/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,24 @@ func GetActions(c *gin.Context, odigosns string) {
})
}

esActions, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).List(c, metav1.ListOptions{})
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}

for _, action := range esActions.Items {
response = append(response, IcaInstanceResponse{
Id: action.Name,
Type: action.Kind,
Spec: action.Spec,
})
}

lsActions, err := kube.DefaultClient.ActionsClient.LatencySamplers(odigosns).List(c, metav1.ListOptions{})

if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
Expand All @@ -80,5 +97,21 @@ func GetActions(c *gin.Context, odigosns string) {
})
}

psActions, err := kube.DefaultClient.ActionsClient.ProbabilisticSamplers(odigosns).List(c, metav1.ListOptions{})
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}

for _, action := range psActions.Items {
response = append(response, IcaInstanceResponse{
Id: action.Name,
Type: action.Kind,
Spec: action.Spec,
})
}

c.JSON(200, response)
}
11 changes: 11 additions & 0 deletions frontend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,22 @@ func startHTTPServer(flags *Flags) (*gin.Engine, error) {
apis.PUT("/actions/types/RenameAttribute/:id", func(c *gin.Context) { actions.UpdateRenameAttribute(c, flags.Namespace, c.Param("id")) })
apis.DELETE("/actions/types/RenameAttribute/:id", func(c *gin.Context) { actions.DeleteRenameAttribute(c, flags.Namespace, c.Param("id")) })

// ErrorSampler
apis.GET("/actions/types/ErrorSampler/:id", func(c *gin.Context) { actions.GetErrorSampler(c, flags.Namespace, c.Param("id")) })
apis.POST("/actions/types/ErrorSampler", func(c *gin.Context) { actions.CreateErrorSampler(c, flags.Namespace) })
apis.PUT("/actions/types/ErrorSampler/:id", func(c *gin.Context) { actions.UpdateErrorSampler(c, flags.Namespace, c.Param("id")) })
apis.DELETE("/actions/types/ErrorSampler/:id", func(c *gin.Context) { actions.DeleteErrorSampler(c, flags.Namespace, c.Param("id")) })
// LatencySampler
apis.GET("/actions/types/LatencySampler/:id", func(c *gin.Context) { actions.GetLatencySampler(c, flags.Namespace, c.Param("id")) })
apis.POST("/actions/types/LatencySampler", func(c *gin.Context) { actions.CreateLatencySampler(c, flags.Namespace) })
apis.PUT("/actions/types/LatencySampler/:id", func(c *gin.Context) { actions.UpdateLatencySampler(c, flags.Namespace, c.Param("id")) })
apis.DELETE("/actions/types/LatencySampler/:id", func(c *gin.Context) { actions.DeleteLatencySampler(c, flags.Namespace, c.Param("id")) })

//ProbabilisticSampler
apis.GET("/actions/types/ProbabilisticSampler/:id", func(c *gin.Context) { actions.GetProbabilisticSampler(c, flags.Namespace, c.Param("id")) })
apis.POST("/actions/types/ProbabilisticSampler", func(c *gin.Context) { actions.CreateProbabilisticSampler(c, flags.Namespace) })
apis.PUT("/actions/types/ProbabilisticSampler/:id", func(c *gin.Context) { actions.UpdateProbabilisticSampler(c, flags.Namespace, c.Param("id")) })
apis.DELETE("/actions/types/ProbabilisticSampler/:id", func(c *gin.Context) { actions.DeleteProbabilisticSampler(c, flags.Namespace, c.Param("id")) })
}

return r, nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client';
import React from 'react';
import { OVERVIEW } from '@/utils';
import { useRouter } from 'next/navigation';
import { OverviewHeader } from '@/components';
import { ChooseActionContainer } from '@/containers';
import { useRouter } from 'next/navigation';

export default function ChooseActionPage() {
const router = useRouter();
Expand Down
6 changes: 5 additions & 1 deletion frontend/webapp/components/common/multi.checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { KeyvalCheckbox, KeyvalText } from '@/design.system';
import React, { useState } from 'react';
import React, { use, useEffect, useState } from 'react';
import styled from 'styled-components';

interface CheckboxItem {
Expand Down Expand Up @@ -28,6 +28,10 @@ export const MultiCheckboxComponent: React.FC<MultiCheckboxProps> = ({
const [selectedMonitors, setSelectedMonitors] =
useState<CheckboxItem[]>(checkboxes);

useEffect(() => {
checkboxes.length === 1 && setIsCheckboxDisabled(true);
}, [checkboxes]);

const handleCheckboxChange = (id: string) => {
// Calculate the number of currently checked checkboxes
const currentlyCheckedCount = selectedMonitors.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { ActionsType } from '@/types';
import {
AddClusterInfoIcon,
DeleteAttributeIcon,
ErrorSamplerIcon,
RenameAttributeIcon,
ProbabilisticSamplerIcon,
} from '@keyval-dev/design-system';

export function ActionIcon({ type, ...props }) {
Expand All @@ -14,6 +16,10 @@ export function ActionIcon({ type, ...props }) {
return <RenameAttributeIcon {...props} />;
case ActionsType.DELETE_ATTRIBUTES:
return <DeleteAttributeIcon {...props} />;
case ActionsType.ERROR_SAMPLER:
return <ErrorSamplerIcon {...props} />;
case ActionsType.PROBABILISTIC_SAMPLER:
return <ProbabilisticSamplerIcon {...props} />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ActionsType } from '@/types';
import { AddClusterInfoForm } from '../add.cluster.info';
import { DeleteAttributesForm } from '../delete.attribute';
import { RenameAttributesForm } from '../rename.attributes';
import { ErrorSamplerForm, ProbabilisticSamplerForm } from '../samplers';

interface DynamicActionFormProps {
type: string | undefined;
Expand All @@ -24,6 +25,10 @@ export function DynamicActionForm({
return <DeleteAttributesForm data={data} onChange={onChange} />;
case ActionsType.RENAME_ATTRIBUTES:
return <RenameAttributesForm data={data} onChange={onChange} />;
case ActionsType.ERROR_SAMPLER:
return <ErrorSamplerForm data={data} onChange={onChange} />;
case ActionsType.PROBABILISTIC_SAMPLER:
return <ProbabilisticSamplerForm data={data} onChange={onChange} />;
default:
return <div></div>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import styled from 'styled-components';
import { KeyvalInput } from '@/design.system';

const FormWrapper = styled.div`
width: 375px;
`;

interface ErrorSampler {
fallback_sampling_ratio: number;
}

interface ErrorSamplerFormProps {
data: ErrorSampler;
onChange: (key: string, value: ErrorSampler | null) => void;
}
const ACTION_DATA_KEY = 'actionData';
export function ErrorSamplerForm({
data,
onChange,
}: ErrorSamplerFormProps): React.JSX.Element {
function handleOnChange(fallback_sampling_ratio: number): void {
onChange(ACTION_DATA_KEY, {
fallback_sampling_ratio,
});
}

return (
<>
<FormWrapper>
<KeyvalInput
label="Fallback Sampling Ratio"
value={data?.fallback_sampling_ratio?.toString()}
onChange={(value) => handleOnChange(+value)}
type="number"
tooltip="Specifies the ratio of non-error traces you still want to retain"
min={0}
max={100}
error={
data?.fallback_sampling_ratio > 100
? 'Value must be less than 100'
: ''
}
/>
</FormWrapper>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './error-sampler';
export * from './probabilistic-sampler';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import styled from 'styled-components';
import { KeyvalInput } from '@/design.system';

const FormWrapper = styled.div`
width: 375px;
`;

interface ProbabilisticSampler {
sampling_percentage: string;
}

interface ProbabilisticSamplerProps {
data: ProbabilisticSampler;
onChange: (key: string, value: ProbabilisticSampler | null) => void;
}
const ACTION_DATA_KEY = 'actionData';
export function ProbabilisticSamplerForm({
data,
onChange,
}: ProbabilisticSamplerProps): React.JSX.Element {
console.log({ data });

function handleOnChange(sampling_percentage: string): void {
onChange(ACTION_DATA_KEY, {
sampling_percentage,
});
}

return (
<>
<FormWrapper>
<KeyvalInput
label="Fallback Sampling Ratio"
value={data?.sampling_percentage}
onChange={(value) => handleOnChange(value)}
type="number"
tooltip="Percentage at which items are sampled; = 100 samples all items, 0 rejects all items"
min={0}
max={100}
error={
+data?.sampling_percentage > 100
? 'Value must be less than 100'
: ''
}
/>
</FormWrapper>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ export default function ActionRowDynamicContent({
{`${Object.keys(item?.spec?.renames).length} renamed attributes`}
</KeyvalText>
);
case ActionsType.ERROR_SAMPLER:
return (
<KeyvalText color={theme.text.grey} size={14} weight={400}>
{`${item?.spec?.fallback_sampling_ratio}% sampling ratio`}s
</KeyvalText>
);
case ActionsType.PROBABILISTIC_SAMPLER:
return (
<KeyvalText color={theme.text.grey} size={14} weight={400}>
{`${item?.spec?.sampling_percentage}% sampling ratio`}
</KeyvalText>
);
default:
return <div>{item.type}</div>;
}
Expand Down
Loading

0 comments on commit 2ce5948

Please sign in to comment.