Cet article décrit l’utilisation d’une bibliothèque C nommée Melon pour le traçage dynamique de l’espace utilisateur, le traitement des données de traçage et la transmission des résultats au programme lui-même et à d’autres programmes distants.
En ce qui concerne le traçage dynamique, la première impression peut être BPF, Dtrace, Systemtap, etc., mais le traçage dynamique présenté dans cet article ne dépend pas de ceux-ci. Les fonctions fournies dans Melon sont plus enclines à permettre au programme de réaliser son propre traçage dynamique dans l’espace utilisateur, sans s’appuyer sur le noyau, ni sur Uprobe ou USDT.
Principe de mise en œuvre
Le traçage dynamique de la bibliothèque Melon est implémenté en ajoutant des points de traçage dans le programme. Dans Melon, une application est divisée en deux couches (mais les deux sont exécutées dans le même processus) :
-
Couche de code C
-
Couche de code de script Melang
Les deux niveaux de code peuvent s’exécuter dans le même filou dans des threads différents, mais ils doivent s’exécuter dans le même processus.
Autrement dit: Les données du point de traçage seront lancées sur la couche C et transmises à la tâche de script spécifiée, puis la tâche de script recevra les données et les traitera.
Cela ne semble pas différent de se connecter à un programme et de lire le journal dans un autre programme pour le traitement, alors quels sont les avantages de le faire ?
Avantages:
-
Pas besoin d’analyser le format du journal, vous pouvez obtenir directement le type de données correspondant
-
La transmission ou le stockage à distance peut être effectué après le traitement côté script (garantie de fonctionnement de la bibliothèque de scripts)
-
Même sous le même thread, l’exécution de la tâche de script n’interrompra pas la logique de la couche C pendant une longue période, et le script et la logique C sont automatiquement programmés en temps partagé
-
En utilisant l’API de rétroaction côté script, le résultat du calcul peut être renvoyé à la couche C, de sorte que la logique d’exécution de la couche de code C peut être modifiée en fonction du résultat, par exemple : dégradation du service
Exemple
Prenons un exemple très simple :
#include <stdio.h>
#include "mln_log.h"
#include "mln_core.h"
#include "mln_trace.h"
#include "mln_conf.h"
#include "mln_event.h"
int timeout = 100;
static void timeout_handler(mln_event_t *ev, void *data)
{
mln_trace("sir", "Hello", getpid(), 3.1);
mln_event_timer_set(ev, timeout, NULL, timeout_handler);
}
static int recv_handler(mln_lang_ctx_t *ctx, mln_lang_val_t *val)
{
timeout += val->data.i;
return 0;
}
int main(int argc, char *argv[])
{
mln_event_t *ev;
struct mln_core_attr cattr;
cattr.argc = argc;
cattr.argv = argv;
cattr.global_init = NULL;
cattr.main_thread = NULL;
cattr.master_process = NULL;
cattr.worker_process = NULL;
if (mln_core_init(&cattr) < 0) {
fprintf(stderr, "Melon init failed.\n");
return -1;
}
if ((ev = mln_event_new()) == NULL) {
mln_log(error, "event new error\n");
return -1;
}
if (mln_trace_init(ev, mln_trace_path()) < 0) {
mln_log(error, "trace init error\n");
return -1;
}
mln_trace_recv_handler_set(recv_handler);
mln_event_timer_set(ev, 1000, NULL, timeout_handler);
mln_event_dispatch(ev);
return 0;
}
Décrivez brièvement le déroulement du programme :
-
Initialisation de la librairie Melon (
mln_core_init
) -
Initialiser l’objet événement
-
Initialiser le script de traçage
-
Définir la fonction utilisée pour traiter les données envoyées par la couche de script
-
Définir l’événement de délai d’attente
-
Envoi d’événement, l’événement de temporisation sera déclenché
Dans la fonction de traitement du délai d’attente timeout_handler
nous utilisons mln_trace
pour envoyer trois types de données différents à la tâche de script, puis continuez à définir l’événement de délai d’attente.
Le délai d’attente est une variable globale timeout
qui est initialement de 100 millisecondes.
Lorsque la couche script envoie des données, ici on convient que la couche script doit envoyer un entier, puis dans la fonction réceptrice recv_handler
on accumule cette valeur et timeout
comme période de temporisation suivante.
À partir de là, on peut deviner que la quantité de données livrées à la couche de script par seconde dans le programme sera de moins en moins importante.
Le code de la couche de script est donné ci-dessous :
sys = Import('sys');
Pipe('subscribe');
while (1) {
ret = Pipe('recv');
if (ret) {
for (i = 0; i < sys. size(ret); ++i) {
sys.print(ret[i]);
}
Pipe('send', 100);
} fi
sys.msleep(1000);
}
Pipe('unsubscribe');
Une brève description de la logique de la couche de script consiste à recevoir un lot de données de la couche C toutes les secondes, puis à les envoyer au terminal. Et après la sortie, il enverra un entier 100
à la couche C.
Examinons les résultats de l’exécution du programme :
... [Hello, 72173, 3.100000, ] [Hello, 72173, 3.100000, ] [Hello, 72173, 3.100000, ] ...
Vous verrez une grande partie de la sortie ci-dessus, mais si vous l’exécutez vous-même, vous constaterez que le nombre de lignes de sortie par seconde sera de moins en moins, ce qui est cohérent avec la logique de notre programme.
Conclusion
Comme le montre cet exemple, nous pouvons non seulement tracer et traiter du côté du script, mais également utiliser les résultats du traitement pour faire remonter et contrôler la couche C. Et cela a trois avantages :
-
Il n’est pas nécessaire d’ajouter des variables statistiques et des structures supplémentaires dans la couche C
-
La couche C et la couche script sont séparées sur l’aspect gestion du code
-
Les deux niveaux de code s’exécutent dans le même thread
Afin de simplifier le code de démonstration, l’exemple ci-dessus ne montre pas la communication réseau et le stockage de données au niveau de la couche de script, mais toutes ces fonctions sont prises en charge par Melang.
Merci d’avoir lu!