aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArnaldo Carvalho de Melo <acme@redhat.com>2009-10-20 12:25:40 -0400
committerIngo Molnar <mingo@elte.hu>2009-10-20 15:12:58 -0400
commite42049926ebdcae24fdfdc8f0e3ff8f05f24a60b (patch)
treebe0a07b62070aef5edcd64d84e12e04950220590
parented52ce2e3c33dc7626a40fa2da766d1a6460e543 (diff)
perf annotate: Use the sym_priv_size area for the histogram
We have this sym_priv_size mechanism for attaching private areas to struct symbol entries but annotate wasn't using it, adding private areas to struct symbol in addition to a ->priv pointer. Scrap all that and use the sym_priv_size mechanism. Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Mike Galbraith <efault@gmx.de> LKML-Reference: <1256055940-19511-1-git-send-email-acme@redhat.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r--tools/perf/builtin-annotate.c109
-rw-r--r--tools/perf/builtin-report.c2
-rw-r--r--tools/perf/util/data_map.c2
-rw-r--r--tools/perf/util/event.h8
-rw-r--r--tools/perf/util/map.c21
-rw-r--r--tools/perf/util/symbol.c56
-rw-r--r--tools/perf/util/symbol.h17
7 files changed, 132 insertions, 83 deletions
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 06f10278b28e..245692530de1 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -37,12 +37,44 @@ static int print_line;
37static unsigned long page_size; 37static unsigned long page_size;
38static unsigned long mmap_window = 32; 38static unsigned long mmap_window = 32;
39 39
40struct sym_hist {
41 u64 sum;
42 u64 ip[0];
43};
44
40struct sym_ext { 45struct sym_ext {
41 struct rb_node node; 46 struct rb_node node;
42 double percent; 47 double percent;
43 char *path; 48 char *path;
44}; 49};
45 50
51struct sym_priv {
52 struct sym_hist *hist;
53 struct sym_ext *ext;
54};
55
56static const char *sym_hist_filter;
57
58static int symbol_filter(struct map *map, struct symbol *sym)
59{
60 if (strcmp(sym->name, sym_hist_filter) == 0) {
61 struct sym_priv *priv = dso__sym_priv(map->dso, sym);
62 const int size = (sizeof(*priv->hist) +
63 (sym->end - sym->start) * sizeof(u64));
64
65 priv->hist = malloc(size);
66 if (priv->hist)
67 memset(priv->hist, 0, size);
68 return 0;
69 }
70 /*
71 * FIXME: We should really filter it out, as we don't want to go thru symbols
72 * we're not interested, and if a DSO ends up with no symbols, delete it too,
73 * but right now the kernel loading routines in symbol.c bail out if no symbols
74 * are found, fix it later.
75 */
76 return 0;
77}
46 78
47/* 79/*
48 * collect histogram counts 80 * collect histogram counts
@@ -51,10 +83,16 @@ static void hist_hit(struct hist_entry *he, u64 ip)
51{ 83{
52 unsigned int sym_size, offset; 84 unsigned int sym_size, offset;
53 struct symbol *sym = he->sym; 85 struct symbol *sym = he->sym;
86 struct sym_priv *priv;
87 struct sym_hist *h;
54 88
55 he->count++; 89 he->count++;
56 90
57 if (!sym || !sym->hist) 91 if (!sym || !he->map)
92 return;
93
94 priv = dso__sym_priv(he->map->dso, sym);
95 if (!priv->hist)
58 return; 96 return;
59 97
60 sym_size = sym->end - sym->start; 98 sym_size = sym->end - sym->start;
@@ -67,15 +105,16 @@ static void hist_hit(struct hist_entry *he, u64 ip)
67 if (offset >= sym_size) 105 if (offset >= sym_size)
68 return; 106 return;
69 107
70 sym->hist_sum++; 108 h = priv->hist;
71 sym->hist[offset]++; 109 h->sum++;
110 h->ip[offset]++;
72 111
73 if (verbose >= 3) 112 if (verbose >= 3)
74 printf("%p %s: count++ [ip: %p, %08Lx] => %Ld\n", 113 printf("%p %s: count++ [ip: %p, %08Lx] => %Ld\n",
75 (void *)(unsigned long)he->sym->start, 114 (void *)(unsigned long)he->sym->start,
76 he->sym->name, 115 he->sym->name,
77 (void *)(unsigned long)ip, ip - he->sym->start, 116 (void *)(unsigned long)ip, ip - he->sym->start,
78 sym->hist[offset]); 117 h->ip[offset]);
79} 118}
80 119
81static int hist_entry__add(struct thread *thread, struct map *map, 120static int hist_entry__add(struct thread *thread, struct map *map,
@@ -162,7 +201,9 @@ got_map:
162static int 201static int
163process_mmap_event(event_t *event, unsigned long offset, unsigned long head) 202process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
164{ 203{
165 struct map *map = map__new(&event->mmap, NULL, 0); 204 struct map *map = map__new(&event->mmap, NULL, 0,
205 sizeof(struct sym_priv), symbol_filter,
206 verbose);
166 struct thread *thread = threads__findnew(event->mmap.pid); 207 struct thread *thread = threads__findnew(event->mmap.pid);
167 208
168 dump_printf("%p [%p]: PERF_RECORD_MMAP %d: [%p(%p) @ %p]: %s\n", 209 dump_printf("%p [%p]: PERF_RECORD_MMAP %d: [%p(%p) @ %p]: %s\n",
@@ -314,17 +355,19 @@ static int parse_line(FILE *file, struct hist_entry *he, u64 len)
314 unsigned int hits = 0; 355 unsigned int hits = 0;
315 double percent = 0.0; 356 double percent = 0.0;
316 const char *color; 357 const char *color;
317 struct sym_ext *sym_ext = sym->priv; 358 struct sym_priv *priv = dso__sym_priv(he->map->dso, sym);
359 struct sym_ext *sym_ext = priv->ext;
360 struct sym_hist *h = priv->hist;
318 361
319 offset = line_ip - start; 362 offset = line_ip - start;
320 if (offset < len) 363 if (offset < len)
321 hits = sym->hist[offset]; 364 hits = h->ip[offset];
322 365
323 if (offset < len && sym_ext) { 366 if (offset < len && sym_ext) {
324 path = sym_ext[offset].path; 367 path = sym_ext[offset].path;
325 percent = sym_ext[offset].percent; 368 percent = sym_ext[offset].percent;
326 } else if (sym->hist_sum) 369 } else if (h->sum)
327 percent = 100.0 * hits / sym->hist_sum; 370 percent = 100.0 * hits / h->sum;
328 371
329 color = get_percent_color(percent); 372 color = get_percent_color(percent);
330 373
@@ -377,9 +420,10 @@ static void insert_source_line(struct sym_ext *sym_ext)
377 rb_insert_color(&sym_ext->node, &root_sym_ext); 420 rb_insert_color(&sym_ext->node, &root_sym_ext);
378} 421}
379 422
380static void free_source_line(struct symbol *sym, int len) 423static void free_source_line(struct hist_entry *he, int len)
381{ 424{
382 struct sym_ext *sym_ext = sym->priv; 425 struct sym_priv *priv = dso__sym_priv(he->map->dso, he->sym);
426 struct sym_ext *sym_ext = priv->ext;
383 int i; 427 int i;
384 428
385 if (!sym_ext) 429 if (!sym_ext)
@@ -389,7 +433,7 @@ static void free_source_line(struct symbol *sym, int len)
389 free(sym_ext[i].path); 433 free(sym_ext[i].path);
390 free(sym_ext); 434 free(sym_ext);
391 435
392 sym->priv = NULL; 436 priv->ext = NULL;
393 root_sym_ext = RB_ROOT; 437 root_sym_ext = RB_ROOT;
394} 438}
395 439
@@ -402,15 +446,16 @@ get_source_line(struct hist_entry *he, int len, const char *filename)
402 int i; 446 int i;
403 char cmd[PATH_MAX * 2]; 447 char cmd[PATH_MAX * 2];
404 struct sym_ext *sym_ext; 448 struct sym_ext *sym_ext;
449 struct sym_priv *priv = dso__sym_priv(he->map->dso, sym);
450 struct sym_hist *h = priv->hist;
405 451
406 if (!sym->hist_sum) 452 if (!h->sum)
407 return; 453 return;
408 454
409 sym->priv = calloc(len, sizeof(struct sym_ext)); 455 sym_ext = priv->ext = calloc(len, sizeof(struct sym_ext));
410 if (!sym->priv) 456 if (!priv->ext)
411 return; 457 return;
412 458
413 sym_ext = sym->priv;
414 start = he->map->unmap_ip(he->map, sym->start); 459 start = he->map->unmap_ip(he->map, sym->start);
415 460
416 for (i = 0; i < len; i++) { 461 for (i = 0; i < len; i++) {
@@ -419,7 +464,7 @@ get_source_line(struct hist_entry *he, int len, const char *filename)
419 u64 offset; 464 u64 offset;
420 FILE *fp; 465 FILE *fp;
421 466
422 sym_ext[i].percent = 100.0 * sym->hist[i] / sym->hist_sum; 467 sym_ext[i].percent = 100.0 * h->ip[i] / h->sum;
423 if (sym_ext[i].percent <= 0.5) 468 if (sym_ext[i].percent <= 0.5)
424 continue; 469 continue;
425 470
@@ -530,7 +575,7 @@ static void annotate_sym(struct hist_entry *he)
530 575
531 pclose(file); 576 pclose(file);
532 if (print_line) 577 if (print_line)
533 free_source_line(sym, len); 578 free_source_line(he, len);
534} 579}
535 580
536static void find_annotations(void) 581static void find_annotations(void)
@@ -540,19 +585,23 @@ static void find_annotations(void)
540 585
541 for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) { 586 for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) {
542 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); 587 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
588 struct sym_priv *priv;
543 589
544 if (he->sym && he->sym->hist) { 590 if (he->sym == NULL)
545 annotate_sym(he); 591 continue;
546 count++;
547 /*
548 * Since we have a hist_entry per IP for the same
549 * symbol, free he->sym->hist to signal we already
550 * processed this symbol.
551 */
552 free(he->sym->hist);
553 he->sym->hist = NULL;
554 592
555 } 593 priv = dso__sym_priv(he->map->dso, he->sym);
594 if (priv->hist == NULL)
595 continue;
596
597 annotate_sym(he);
598 count++;
599 /*
600 * Since we have a hist_entry per IP for the same symbol, free
601 * he->sym->hist to signal we already processed this symbol.
602 */
603 free(priv->hist);
604 priv->hist = NULL;
556 } 605 }
557 606
558 if (!count) 607 if (!count)
@@ -593,7 +642,7 @@ static int __cmd_annotate(void)
593 exit(0); 642 exit(0);
594 } 643 }
595 644
596 if (load_kernel() < 0) { 645 if (load_kernel(sizeof(struct sym_priv), symbol_filter) < 0) {
597 perror("failed to load kernel symbols"); 646 perror("failed to load kernel symbols");
598 return EXIT_FAILURE; 647 return EXIT_FAILURE;
599 } 648 }
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index a4f8cc209151..bee207ce589a 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -680,7 +680,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
680static int 680static int
681process_mmap_event(event_t *event, unsigned long offset, unsigned long head) 681process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
682{ 682{
683 struct map *map = map__new(&event->mmap, cwd, cwdlen); 683 struct map *map = map__new(&event->mmap, cwd, cwdlen, 0, NULL, verbose);
684 struct thread *thread = threads__findnew(event->mmap.pid); 684 struct thread *thread = threads__findnew(event->mmap.pid);
685 685
686 dump_printf("%p [%p]: PERF_RECORD_MMAP %d/%d: [%p(%p) @ %p]: %s\n", 686 dump_printf("%p [%p]: PERF_RECORD_MMAP %d/%d: [%p(%p) @ %p]: %s\n",
diff --git a/tools/perf/util/data_map.c b/tools/perf/util/data_map.c
index 242b0555ab91..18accb8fee4d 100644
--- a/tools/perf/util/data_map.c
+++ b/tools/perf/util/data_map.c
@@ -130,7 +130,7 @@ int mmap_dispatch_perf_file(struct perf_header **pheader,
130 if (curr_handler->sample_type_check(sample_type) < 0) 130 if (curr_handler->sample_type_check(sample_type) < 0)
131 exit(-1); 131 exit(-1);
132 132
133 if (load_kernel() < 0) { 133 if (load_kernel(0, NULL) < 0) {
134 perror("failed to load kernel symbols"); 134 perror("failed to load kernel symbols");
135 return EXIT_FAILURE; 135 return EXIT_FAILURE;
136 } 136 }
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
index 6b5be56a8271..db59c8bbe49a 100644
--- a/tools/perf/util/event.h
+++ b/tools/perf/util/event.h
@@ -101,7 +101,13 @@ static inline u64 identity__map_ip(struct map *map __used, u64 ip)
101 return ip; 101 return ip;
102} 102}
103 103
104struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen); 104struct symbol;
105
106typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym);
107
108struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen,
109 unsigned int sym_priv_size, symbol_filter_t filter,
110 int v);
105struct map *map__clone(struct map *self); 111struct map *map__clone(struct map *self);
106int map__overlap(struct map *l, struct map *r); 112int map__overlap(struct map *l, struct map *r);
107size_t map__fprintf(struct map *self, FILE *fp); 113size_t map__fprintf(struct map *self, FILE *fp);
diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c
index 4e203d144f9e..55079c0200e0 100644
--- a/tools/perf/util/map.c
+++ b/tools/perf/util/map.c
@@ -3,6 +3,7 @@
3#include <stdlib.h> 3#include <stdlib.h>
4#include <string.h> 4#include <string.h>
5#include <stdio.h> 5#include <stdio.h>
6#include "debug.h"
6 7
7static inline int is_anon_memory(const char *filename) 8static inline int is_anon_memory(const char *filename)
8{ 9{
@@ -19,7 +20,9 @@ static int strcommon(const char *pathname, char *cwd, int cwdlen)
19 return n; 20 return n;
20} 21}
21 22
22 struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen) 23struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen,
24 unsigned int sym_priv_size, symbol_filter_t filter,
25 int v)
23{ 26{
24 struct map *self = malloc(sizeof(*self)); 27 struct map *self = malloc(sizeof(*self));
25 28
@@ -27,6 +30,7 @@ static int strcommon(const char *pathname, char *cwd, int cwdlen)
27 const char *filename = event->filename; 30 const char *filename = event->filename;
28 char newfilename[PATH_MAX]; 31 char newfilename[PATH_MAX];
29 int anon; 32 int anon;
33 bool new_dso;
30 34
31 if (cwd) { 35 if (cwd) {
32 int n = strcommon(filename, cwd, cwdlen); 36 int n = strcommon(filename, cwd, cwdlen);
@@ -49,10 +53,23 @@ static int strcommon(const char *pathname, char *cwd, int cwdlen)
49 self->end = event->start + event->len; 53 self->end = event->start + event->len;
50 self->pgoff = event->pgoff; 54 self->pgoff = event->pgoff;
51 55
52 self->dso = dsos__findnew(filename); 56 self->dso = dsos__findnew(filename, sym_priv_size, &new_dso);
53 if (self->dso == NULL) 57 if (self->dso == NULL)
54 goto out_delete; 58 goto out_delete;
55 59
60 if (new_dso) {
61 int nr = dso__load(self->dso, self, filter, v);
62
63 if (nr < 0)
64 eprintf("Failed to open %s, continuing "
65 "without symbols\n",
66 self->dso->long_name);
67 else if (nr == 0)
68 eprintf("No symbols found in %s, maybe "
69 "install a debug package?\n",
70 self->dso->long_name);
71 }
72
56 if (self->dso == vdso || anon) 73 if (self->dso == vdso || anon)
57 self->map_ip = self->unmap_ip = identity__map_ip; 74 self->map_ip = self->unmap_ip = identity__map_ip;
58 else { 75 else {
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 3350119f6909..0a4898480d6d 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -11,8 +11,6 @@
11#include <elf.h> 11#include <elf.h>
12#include <sys/utsname.h> 12#include <sys/utsname.h>
13 13
14const char *sym_hist_filter;
15
16enum dso_origin { 14enum dso_origin {
17 DSO__ORIG_KERNEL = 0, 15 DSO__ORIG_KERNEL = 0,
18 DSO__ORIG_JAVA_JIT, 16 DSO__ORIG_JAVA_JIT,
@@ -86,22 +84,16 @@ static struct symbol *symbol__new(u64 start, u64 len, const char *name,
86 if (!self) 84 if (!self)
87 return NULL; 85 return NULL;
88 86
89 if (v > 2)
90 printf("new symbol: %016Lx [%08lx]: %s, hist: %p\n",
91 start, (unsigned long)len, name, self->hist);
92
93 self->hist = NULL;
94 self->hist_sum = 0;
95
96 if (sym_hist_filter && !strcmp(name, sym_hist_filter))
97 self->hist = calloc(sizeof(u64), len);
98
99 if (priv_size) { 87 if (priv_size) {
100 memset(self, 0, priv_size); 88 memset(self, 0, priv_size);
101 self = ((void *)self) + priv_size; 89 self = ((void *)self) + priv_size;
102 } 90 }
103 self->start = start; 91 self->start = start;
104 self->end = len ? start + len - 1 : start; 92 self->end = len ? start + len - 1 : start;
93
94 if (v > 2)
95 printf("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end);
96
105 memcpy(self->name, name, namelen); 97 memcpy(self->name, name, namelen);
106 98
107 return self; 99 return self;
@@ -919,7 +911,8 @@ char dso__symtab_origin(const struct dso *self)
919 return origin[self->origin]; 911 return origin[self->origin];
920} 912}
921 913
922int dso__load(struct dso *self, struct map *map, symbol_filter_t filter, int v) 914int dso__load(struct dso *self, struct map *map,
915 symbol_filter_t filter, int v)
923{ 916{
924 int size = PATH_MAX; 917 int size = PATH_MAX;
925 char *name = malloc(size), *build_id = NULL; 918 char *name = malloc(size), *build_id = NULL;
@@ -1335,33 +1328,21 @@ static struct dso *dsos__find(const char *name)
1335 return NULL; 1328 return NULL;
1336} 1329}
1337 1330
1338struct dso *dsos__findnew(const char *name) 1331struct dso *dsos__findnew(const char *name, unsigned int sym_priv_size,
1332 bool *is_new)
1339{ 1333{
1340 struct dso *dso = dsos__find(name); 1334 struct dso *dso = dsos__find(name);
1341 int nr;
1342
1343 if (dso)
1344 return dso;
1345
1346 dso = dso__new(name, 0);
1347 if (!dso)
1348 goto out_delete_dso;
1349 1335
1350 nr = dso__load(dso, NULL, NULL, verbose); 1336 if (!dso) {
1351 if (nr < 0) { 1337 dso = dso__new(name, sym_priv_size);
1352 eprintf("Failed to open: %s\n", name); 1338 if (dso) {
1353 goto out_delete_dso; 1339 dsos__add(dso);
1354 } 1340 *is_new = true;
1355 if (!nr) 1341 }
1356 eprintf("No symbols found in: %s, maybe install a debug package?\n", name); 1342 } else
1357 1343 *is_new = false;
1358 dsos__add(dso);
1359 1344
1360 return dso; 1345 return dso;
1361
1362out_delete_dso:
1363 dso__delete(dso);
1364 return NULL;
1365} 1346}
1366 1347
1367void dsos__fprintf(FILE *fp) 1348void dsos__fprintf(FILE *fp)
@@ -1372,9 +1353,10 @@ void dsos__fprintf(FILE *fp)
1372 dso__fprintf(pos, fp); 1353 dso__fprintf(pos, fp);
1373} 1354}
1374 1355
1375int load_kernel(void) 1356int load_kernel(unsigned int sym_priv_size, symbol_filter_t filter)
1376{ 1357{
1377 if (dsos__load_kernel(vmlinux_name, 0, NULL, verbose, modules) <= 0) 1358 if (dsos__load_kernel(vmlinux_name, sym_priv_size,
1359 filter, verbose, modules) <= 0)
1378 return -1; 1360 return -1;
1379 1361
1380 vdso = dso__new("[vdso]", 0); 1362 vdso = dso__new("[vdso]", 0);
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
index 2e4522edeb07..c2a777de9b7e 100644
--- a/tools/perf/util/symbol.h
+++ b/tools/perf/util/symbol.h
@@ -2,6 +2,7 @@
2#define __PERF_SYMBOL 1 2#define __PERF_SYMBOL 1
3 3
4#include <linux/types.h> 4#include <linux/types.h>
5#include <stdbool.h>
5#include "types.h" 6#include "types.h"
6#include <linux/list.h> 7#include <linux/list.h>
7#include <linux/rbtree.h> 8#include <linux/rbtree.h>
@@ -35,9 +36,6 @@ struct symbol {
35 struct rb_node rb_node; 36 struct rb_node rb_node;
36 u64 start; 37 u64 start;
37 u64 end; 38 u64 end;
38 u64 hist_sum;
39 u64 *hist;
40 void *priv;
41 char name[0]; 39 char name[0];
42}; 40};
43 41
@@ -54,10 +52,6 @@ struct dso {
54 char name[0]; 52 char name[0];
55}; 53};
56 54
57extern const char *sym_hist_filter;
58
59typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym);
60
61struct dso *dso__new(const char *name, unsigned int sym_priv_size); 55struct dso *dso__new(const char *name, unsigned int sym_priv_size);
62void dso__delete(struct dso *self); 56void dso__delete(struct dso *self);
63 57
@@ -70,15 +64,16 @@ struct symbol *dso__find_symbol(struct dso *self, u64 ip);
70 64
71int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size, 65int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size,
72 symbol_filter_t filter, int verbose, int modules); 66 symbol_filter_t filter, int verbose, int modules);
73int dso__load(struct dso *self, struct map *map, symbol_filter_t filter, 67struct dso *dsos__findnew(const char *name, unsigned int sym_priv_size,
74 int verbose); 68 bool *is_new);
75struct dso *dsos__findnew(const char *name); 69int dso__load(struct dso *self, struct map *map,
70 symbol_filter_t filter, int v);
76void dsos__fprintf(FILE *fp); 71void dsos__fprintf(FILE *fp);
77 72
78size_t dso__fprintf(struct dso *self, FILE *fp); 73size_t dso__fprintf(struct dso *self, FILE *fp);
79char dso__symtab_origin(const struct dso *self); 74char dso__symtab_origin(const struct dso *self);
80 75
81int load_kernel(void); 76int load_kernel(unsigned int sym_priv_size, symbol_filter_t filter);
82 77
83void symbol__init(void); 78void symbol__init(void);
84 79